]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/tiny_mce_src.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / tiny_mce_src.js
1 (function(win) {
2         var whiteSpaceRe = /^\s*|\s*$/g,
3                 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
4
5         var tinymce = {
6                 majorVersion : '3',
7
8                 minorVersion : '4.2',
9
10                 releaseDate : '2011-04-07',
11
12                 _init : function() {
13                         var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
14
15                         t.isOpera = win.opera && opera.buildNumber;
16
17                         t.isWebKit = /WebKit/.test(ua);
18
19                         t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
20
21                         t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
22
23                         t.isGecko = !t.isWebKit && /Gecko/.test(ua);
24
25                         t.isMac = ua.indexOf('Mac') != -1;
26
27                         t.isAir = /adobeair/i.test(ua);
28
29                         t.isIDevice = /(iPad|iPhone)/.test(ua);
30
31                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE
32                         if (win.tinyMCEPreInit) {
33                                 t.suffix = tinyMCEPreInit.suffix;
34                                 t.baseURL = tinyMCEPreInit.base;
35                                 t.query = tinyMCEPreInit.query;
36                                 return;
37                         }
38
39                         // Get suffix and base
40                         t.suffix = '';
41
42                         // If base element found, add that infront of baseURL
43                         nl = d.getElementsByTagName('base');
44                         for (i=0; i<nl.length; i++) {
45                                 if (v = nl[i].href) {
46                                         // Host only value like http://site.com or http://site.com:8008
47                                         if (/^https?:\/\/[^\/]+$/.test(v))
48                                                 v += '/';
49
50                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory
51                                 }
52                         }
53
54                         function getBase(n) {
55                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
56                                         if (/_(src|dev)\.js/g.test(n.src))
57                                                 t.suffix = '_src';
58
59                                         if ((p = n.src.indexOf('?')) != -1)
60                                                 t.query = n.src.substring(p + 1);
61
62                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
63
64                                         // If path to script is relative and a base href was found add that one infront
65                                         // the src property will always be an absolute one on non IE browsers and IE 8
66                                         // so this logic will basically only be executed on older IE versions
67                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
68                                                 t.baseURL = base + t.baseURL;
69
70                                         return t.baseURL;
71                                 }
72
73                                 return null;
74                         };
75
76                         // Check document
77                         nl = d.getElementsByTagName('script');
78                         for (i=0; i<nl.length; i++) {
79                                 if (getBase(nl[i]))
80                                         return;
81                         }
82
83                         // Check head
84                         n = d.getElementsByTagName('head')[0];
85                         if (n) {
86                                 nl = n.getElementsByTagName('script');
87                                 for (i=0; i<nl.length; i++) {
88                                         if (getBase(nl[i]))
89                                                 return;
90                                 }
91                         }
92
93                         return;
94                 },
95
96                 is : function(o, t) {
97                         if (!t)
98                                 return o !== undefined;
99
100                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
101                                 return true;
102
103                         return typeof(o) == t;
104                 },
105
106                 makeMap : function(items, delim, map) {
107                         var i;
108
109                         items = items || [];
110                         delim = delim || ',';
111
112                         if (typeof(items) == "string")
113                                 items = items.split(delim);
114
115                         map = map || {};
116
117                         i = items.length;
118                         while (i--)
119                                 map[items[i]] = {};
120
121                         return map;
122                 },
123
124                 each : function(o, cb, s) {
125                         var n, l;
126
127                         if (!o)
128                                 return 0;
129
130                         s = s || o;
131
132                         if (o.length !== undefined) {
133                                 // Indexed arrays, needed for Safari
134                                 for (n=0, l = o.length; n < l; n++) {
135                                         if (cb.call(s, o[n], n, o) === false)
136                                                 return 0;
137                                 }
138                         } else {
139                                 // Hashtables
140                                 for (n in o) {
141                                         if (o.hasOwnProperty(n)) {
142                                                 if (cb.call(s, o[n], n, o) === false)
143                                                         return 0;
144                                         }
145                                 }
146                         }
147
148                         return 1;
149                 },
150
151
152                 map : function(a, f) {
153                         var o = [];
154
155                         tinymce.each(a, function(v) {
156                                 o.push(f(v));
157                         });
158
159                         return o;
160                 },
161
162                 grep : function(a, f) {
163                         var o = [];
164
165                         tinymce.each(a, function(v) {
166                                 if (!f || f(v))
167                                         o.push(v);
168                         });
169
170                         return o;
171                 },
172
173                 inArray : function(a, v) {
174                         var i, l;
175
176                         if (a) {
177                                 for (i = 0, l = a.length; i < l; i++) {
178                                         if (a[i] === v)
179                                                 return i;
180                                 }
181                         }
182
183                         return -1;
184                 },
185
186                 extend : function(o, e) {
187                         var i, l, a = arguments;
188
189                         for (i = 1, l = a.length; i < l; i++) {
190                                 e = a[i];
191
192                                 tinymce.each(e, function(v, n) {
193                                         if (v !== undefined)
194                                                 o[n] = v;
195                                 });
196                         }
197
198                         return o;
199                 },
200
201
202                 trim : function(s) {
203                         return (s ? '' + s : '').replace(whiteSpaceRe, '');
204                 },
205
206                 create : function(s, p, root) {
207                         var t = this, sp, ns, cn, scn, c, de = 0;
208
209                         // Parse : <prefix> <class>:<super class>
210                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
211                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
212
213                         // Create namespace for new class
214                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
215
216                         // Class already exists
217                         if (ns[cn])
218                                 return;
219
220                         // Make pure static class
221                         if (s[2] == 'static') {
222                                 ns[cn] = p;
223
224                                 if (this.onCreate)
225                                         this.onCreate(s[2], s[3], ns[cn]);
226
227                                 return;
228                         }
229
230                         // Create default constructor
231                         if (!p[cn]) {
232                                 p[cn] = function() {};
233                                 de = 1;
234                         }
235
236                         // Add constructor and methods
237                         ns[cn] = p[cn];
238                         t.extend(ns[cn].prototype, p);
239
240                         // Extend
241                         if (s[5]) {
242                                 sp = t.resolve(s[5]).prototype;
243                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
244
245                                 // Extend constructor
246                                 c = ns[cn];
247                                 if (de) {
248                                         // Add passthrough constructor
249                                         ns[cn] = function() {
250                                                 return sp[scn].apply(this, arguments);
251                                         };
252                                 } else {
253                                         // Add inherit constructor
254                                         ns[cn] = function() {
255                                                 this.parent = sp[scn];
256                                                 return c.apply(this, arguments);
257                                         };
258                                 }
259                                 ns[cn].prototype[cn] = ns[cn];
260
261                                 // Add super methods
262                                 t.each(sp, function(f, n) {
263                                         ns[cn].prototype[n] = sp[n];
264                                 });
265
266                                 // Add overridden methods
267                                 t.each(p, function(f, n) {
268                                         // Extend methods if needed
269                                         if (sp[n]) {
270                                                 ns[cn].prototype[n] = function() {
271                                                         this.parent = sp[n];
272                                                         return f.apply(this, arguments);
273                                                 };
274                                         } else {
275                                                 if (n != cn)
276                                                         ns[cn].prototype[n] = f;
277                                         }
278                                 });
279                         }
280
281                         // Add static methods
282                         t.each(p['static'], function(f, n) {
283                                 ns[cn][n] = f;
284                         });
285
286                         if (this.onCreate)
287                                 this.onCreate(s[2], s[3], ns[cn].prototype);
288                 },
289
290                 walk : function(o, f, n, s) {
291                         s = s || this;
292
293                         if (o) {
294                                 if (n)
295                                         o = o[n];
296
297                                 tinymce.each(o, function(o, i) {
298                                         if (f.call(s, o, i, n) === false)
299                                                 return false;
300
301                                         tinymce.walk(o, f, n, s);
302                                 });
303                         }
304                 },
305
306                 createNS : function(n, o) {
307                         var i, v;
308
309                         o = o || win;
310
311                         n = n.split('.');
312                         for (i=0; i<n.length; i++) {
313                                 v = n[i];
314
315                                 if (!o[v])
316                                         o[v] = {};
317
318                                 o = o[v];
319                         }
320
321                         return o;
322                 },
323
324                 resolve : function(n, o) {
325                         var i, l;
326
327                         o = o || win;
328
329                         n = n.split('.');
330                         for (i = 0, l = n.length; i < l; i++) {
331                                 o = o[n[i]];
332
333                                 if (!o)
334                                         break;
335                         }
336
337                         return o;
338                 },
339
340                 addUnload : function(f, s) {
341                         var t = this;
342
343                         f = {func : f, scope : s || this};
344
345                         if (!t.unloads) {
346                                 function unload() {
347                                         var li = t.unloads, o, n;
348
349                                         if (li) {
350                                                 // Call unload handlers
351                                                 for (n in li) {
352                                                         o = li[n];
353
354                                                         if (o && o.func)
355                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
356                                                 }
357
358                                                 // Detach unload function
359                                                 if (win.detachEvent) {
360                                                         win.detachEvent('onbeforeunload', fakeUnload);
361                                                         win.detachEvent('onunload', unload);
362                                                 } else if (win.removeEventListener)
363                                                         win.removeEventListener('unload', unload, false);
364
365                                                 // Destroy references
366                                                 t.unloads = o = li = w = unload = 0;
367
368                                                 // Run garbarge collector on IE
369                                                 if (win.CollectGarbage)
370                                                         CollectGarbage();
371                                         }
372                                 };
373
374                                 function fakeUnload() {
375                                         var d = document;
376
377                                         // Is there things still loading, then do some magic
378                                         if (d.readyState == 'interactive') {
379                                                 function stop() {
380                                                         // Prevent memory leak
381                                                         d.detachEvent('onstop', stop);
382
383                                                         // Call unload handler
384                                                         if (unload)
385                                                                 unload();
386
387                                                         d = 0;
388                                                 };
389
390                                                 // Fire unload when the currently loading page is stopped
391                                                 if (d)
392                                                         d.attachEvent('onstop', stop);
393
394                                                 // Remove onstop listener after a while to prevent the unload function
395                                                 // to execute if the user presses cancel in an onbeforeunload
396                                                 // confirm dialog and then presses the browser stop button
397                                                 win.setTimeout(function() {
398                                                         if (d)
399                                                                 d.detachEvent('onstop', stop);
400                                                 }, 0);
401                                         }
402                                 };
403
404                                 // Attach unload handler
405                                 if (win.attachEvent) {
406                                         win.attachEvent('onunload', unload);
407                                         win.attachEvent('onbeforeunload', fakeUnload);
408                                 } else if (win.addEventListener)
409                                         win.addEventListener('unload', unload, false);
410
411                                 // Setup initial unload handler array
412                                 t.unloads = [f];
413                         } else
414                                 t.unloads.push(f);
415
416                         return f;
417                 },
418
419                 removeUnload : function(f) {
420                         var u = this.unloads, r = null;
421
422                         tinymce.each(u, function(o, i) {
423                                 if (o && o.func == f) {
424                                         u.splice(i, 1);
425                                         r = f;
426                                         return false;
427                                 }
428                         });
429
430                         return r;
431                 },
432
433                 explode : function(s, d) {
434                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
435                 },
436
437                 _addVer : function(u) {
438                         var v;
439
440                         if (!this.query)
441                                 return u;
442
443                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
444
445                         if (u.indexOf('#') == -1)
446                                 return u + v;
447
448                         return u.replace('#', v + '#');
449                 },
450
451                 // Fix function for IE 9 where regexps isn't working correctly
452                 // Todo: remove me once MS fixes the bug
453                 _replace : function(find, replace, str) {
454                         // On IE9 we have to fake $x replacement
455                         if (isRegExpBroken) {
456                                 return str.replace(find, function() {
457                                         var val = replace, args = arguments, i;
458
459                                         for (i = 0; i < args.length - 2; i++) {
460                                                 if (args[i] === undefined) {
461                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');
462                                                 } else {
463                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
464                                                 }
465                                         }
466
467                                         return val;
468                                 });
469                         }
470
471                         return str.replace(find, replace);
472                 }
473
474                 };
475
476         // Initialize the API
477         tinymce._init();
478
479         // Expose tinymce namespace to the global namespace (window)
480         win.tinymce = win.tinyMCE = tinymce;
481
482         // Describe the different namespaces
483
484         })(window);
485
486
487 tinymce.create('tinymce.util.Dispatcher', {
488         scope : null,
489         listeners : null,
490
491         Dispatcher : function(s) {
492                 this.scope = s || this;
493                 this.listeners = [];
494         },
495
496         add : function(cb, s) {
497                 this.listeners.push({cb : cb, scope : s || this.scope});
498
499                 return cb;
500         },
501
502         addToTop : function(cb, s) {
503                 this.listeners.unshift({cb : cb, scope : s || this.scope});
504
505                 return cb;
506         },
507
508         remove : function(cb) {
509                 var l = this.listeners, o = null;
510
511                 tinymce.each(l, function(c, i) {
512                         if (cb == c.cb) {
513                                 o = cb;
514                                 l.splice(i, 1);
515                                 return false;
516                         }
517                 });
518
519                 return o;
520         },
521
522         dispatch : function() {
523                 var s, a = arguments, i, li = this.listeners, c;
524
525                 // Needs to be a real loop since the listener count might change while looping
526                 // And this is also more efficient
527                 for (i = 0; i<li.length; i++) {
528                         c = li[i];
529                         s = c.cb.apply(c.scope, a);
530
531                         if (s === false)
532                                 break;
533                 }
534
535                 return s;
536         }
537
538         });
539
540 (function() {
541         var each = tinymce.each;
542
543         tinymce.create('tinymce.util.URI', {
544                 URI : function(u, s) {
545                         var t = this, o, a, b;
546
547                         // Trim whitespace
548                         u = tinymce.trim(u);
549
550                         // Default settings
551                         s = t.settings = s || {};
552
553                         // Strange app protocol or local anchor
554                         if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
555                                 t.source = u;
556                                 return;
557                         }
558
559                         // Absolute path with no host, fake host and protocol
560                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
561                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
562
563                         // Relative path http:// or protocol relative //path
564                         if (!/^\w*:?\/\//.test(u))
565                                 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
566
567                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
568                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
569                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
570                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
571                                 var s = u[i];
572
573                                 // Zope 3 workaround, they use @@something
574                                 if (s)
575                                         s = s.replace(/\(mce_at\)/g, '@@');
576
577                                 t[v] = s;
578                         });
579
580                         if (b = s.base_uri) {
581                                 if (!t.protocol)
582                                         t.protocol = b.protocol;
583
584                                 if (!t.userInfo)
585                                         t.userInfo = b.userInfo;
586
587                                 if (!t.port && t.host == 'mce_host')
588                                         t.port = b.port;
589
590                                 if (!t.host || t.host == 'mce_host')
591                                         t.host = b.host;
592
593                                 t.source = '';
594                         }
595
596                         //t.path = t.path || '/';
597                 },
598
599                 setPath : function(p) {
600                         var t = this;
601
602                         p = /^(.*?)\/?(\w+)?$/.exec(p);
603
604                         // Update path parts
605                         t.path = p[0];
606                         t.directory = p[1];
607                         t.file = p[2];
608
609                         // Rebuild source
610                         t.source = '';
611                         t.getURI();
612                 },
613
614                 toRelative : function(u) {
615                         var t = this, o;
616
617                         if (u === "./")
618                                 return u;
619
620                         u = new tinymce.util.URI(u, {base_uri : t});
621
622                         // Not on same domain/port or protocol
623                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
624                                 return u.getURI();
625
626                         o = t.toRelPath(t.path, u.path);
627
628                         // Add query
629                         if (u.query)
630                                 o += '?' + u.query;
631
632                         // Add anchor
633                         if (u.anchor)
634                                 o += '#' + u.anchor;
635
636                         return o;
637                 },
638         
639                 toAbsolute : function(u, nh) {
640                         var u = new tinymce.util.URI(u, {base_uri : this});
641
642                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
643                 },
644
645                 toRelPath : function(base, path) {
646                         var items, bp = 0, out = '', i, l;
647
648                         // Split the paths
649                         base = base.substring(0, base.lastIndexOf('/'));
650                         base = base.split('/');
651                         items = path.split('/');
652
653                         if (base.length >= items.length) {
654                                 for (i = 0, l = base.length; i < l; i++) {
655                                         if (i >= items.length || base[i] != items[i]) {
656                                                 bp = i + 1;
657                                                 break;
658                                         }
659                                 }
660                         }
661
662                         if (base.length < items.length) {
663                                 for (i = 0, l = items.length; i < l; i++) {
664                                         if (i >= base.length || base[i] != items[i]) {
665                                                 bp = i + 1;
666                                                 break;
667                                         }
668                                 }
669                         }
670
671                         if (bp == 1)
672                                 return path;
673
674                         for (i = 0, l = base.length - (bp - 1); i < l; i++)
675                                 out += "../";
676
677                         for (i = bp - 1, l = items.length; i < l; i++) {
678                                 if (i != bp - 1)
679                                         out += "/" + items[i];
680                                 else
681                                         out += items[i];
682                         }
683
684                         return out;
685                 },
686
687                 toAbsPath : function(base, path) {
688                         var i, nb = 0, o = [], tr, outPath;
689
690                         // Split paths
691                         tr = /\/$/.test(path) ? '/' : '';
692                         base = base.split('/');
693                         path = path.split('/');
694
695                         // Remove empty chunks
696                         each(base, function(k) {
697                                 if (k)
698                                         o.push(k);
699                         });
700
701                         base = o;
702
703                         // Merge relURLParts chunks
704                         for (i = path.length - 1, o = []; i >= 0; i--) {
705                                 // Ignore empty or .
706                                 if (path[i].length == 0 || path[i] == ".")
707                                         continue;
708
709                                 // Is parent
710                                 if (path[i] == '..') {
711                                         nb++;
712                                         continue;
713                                 }
714
715                                 // Move up
716                                 if (nb > 0) {
717                                         nb--;
718                                         continue;
719                                 }
720
721                                 o.push(path[i]);
722                         }
723
724                         i = base.length - nb;
725
726                         // If /a/b/c or /
727                         if (i <= 0)
728                                 outPath = o.reverse().join('/');
729                         else
730                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
731
732                         // Add front / if it's needed
733                         if (outPath.indexOf('/') !== 0)
734                                 outPath = '/' + outPath;
735
736                         // Add traling / if it's needed
737                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
738                                 outPath += tr;
739
740                         return outPath;
741                 },
742
743                 getURI : function(nh) {
744                         var s, t = this;
745
746                         // Rebuild source
747                         if (!t.source || nh) {
748                                 s = '';
749
750                                 if (!nh) {
751                                         if (t.protocol)
752                                                 s += t.protocol + '://';
753
754                                         if (t.userInfo)
755                                                 s += t.userInfo + '@';
756
757                                         if (t.host)
758                                                 s += t.host;
759
760                                         if (t.port)
761                                                 s += ':' + t.port;
762                                 }
763
764                                 if (t.path)
765                                         s += t.path;
766
767                                 if (t.query)
768                                         s += '?' + t.query;
769
770                                 if (t.anchor)
771                                         s += '#' + t.anchor;
772
773                                 t.source = s;
774                         }
775
776                         return t.source;
777                 }
778         });
779 })();
780
781 (function() {
782         var each = tinymce.each;
783
784         tinymce.create('static tinymce.util.Cookie', {
785                 getHash : function(n) {
786                         var v = this.get(n), h;
787
788                         if (v) {
789                                 each(v.split('&'), function(v) {
790                                         v = v.split('=');
791                                         h = h || {};
792                                         h[unescape(v[0])] = unescape(v[1]);
793                                 });
794                         }
795
796                         return h;
797                 },
798
799                 setHash : function(n, v, e, p, d, s) {
800                         var o = '';
801
802                         each(v, function(v, k) {
803                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
804                         });
805
806                         this.set(n, o, e, p, d, s);
807                 },
808
809                 get : function(n) {
810                         var c = document.cookie, e, p = n + "=", b;
811
812                         // Strict mode
813                         if (!c)
814                                 return;
815
816                         b = c.indexOf("; " + p);
817
818                         if (b == -1) {
819                                 b = c.indexOf(p);
820
821                                 if (b != 0)
822                                         return null;
823                         } else
824                                 b += 2;
825
826                         e = c.indexOf(";", b);
827
828                         if (e == -1)
829                                 e = c.length;
830
831                         return unescape(c.substring(b + p.length, e));
832                 },
833
834                 set : function(n, v, e, p, d, s) {
835                         document.cookie = n + "=" + escape(v) +
836                                 ((e) ? "; expires=" + e.toGMTString() : "") +
837                                 ((p) ? "; path=" + escape(p) : "") +
838                                 ((d) ? "; domain=" + d : "") +
839                                 ((s) ? "; secure" : "");
840                 },
841
842                 remove : function(n, p) {
843                         var d = new Date();
844
845                         d.setTime(d.getTime() - 1000);
846
847                         this.set(n, '', d, p, d);
848                 }
849         });
850 })();
851
852 (function() {
853         function serialize(o, quote) {
854                 var i, v, t;
855
856                 quote = quote || '"';
857
858                 if (o == null)
859                         return 'null';
860
861                 t = typeof o;
862
863                 if (t == 'string') {
864                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
865
866                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
867                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
868                                 if (quote === '"' && a === "'")
869                                         return a;
870
871                                 i = v.indexOf(b);
872
873                                 if (i + 1)
874                                         return '\\' + v.charAt(i + 1);
875
876                                 a = b.charCodeAt().toString(16);
877
878                                 return '\\u' + '0000'.substring(a.length) + a;
879                         }) + quote;
880                 }
881
882                 if (t == 'object') {
883                         if (o.hasOwnProperty && o instanceof Array) {
884                                         for (i=0, v = '['; i<o.length; i++)
885                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
886
887                                         return v + ']';
888                                 }
889
890                                 v = '{';
891
892                                 for (i in o)
893                                         v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
894
895                                 return v + '}';
896                 }
897
898                 return '' + o;
899         };
900
901         tinymce.util.JSON = {
902                 serialize: serialize,
903
904                 parse: function(s) {
905                         try {
906                                 return eval('(' + s + ')');
907                         } catch (ex) {
908                                 // Ignore
909                         }
910                 }
911
912                 };
913 })();
914 tinymce.create('static tinymce.util.XHR', {
915         send : function(o) {
916                 var x, t, w = window, c = 0;
917
918                 // Default settings
919                 o.scope = o.scope || this;
920                 o.success_scope = o.success_scope || o.scope;
921                 o.error_scope = o.error_scope || o.scope;
922                 o.async = o.async === false ? false : true;
923                 o.data = o.data || '';
924
925                 function get(s) {
926                         x = 0;
927
928                         try {
929                                 x = new ActiveXObject(s);
930                         } catch (ex) {
931                         }
932
933                         return x;
934                 };
935
936                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
937
938                 if (x) {
939                         if (x.overrideMimeType)
940                                 x.overrideMimeType(o.content_type);
941
942                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
943
944                         if (o.content_type)
945                                 x.setRequestHeader('Content-Type', o.content_type);
946
947                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
948
949                         x.send(o.data);
950
951                         function ready() {
952                                 if (!o.async || x.readyState == 4 || c++ > 10000) {
953                                         if (o.success && c < 10000 && x.status == 200)
954                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);
955                                         else if (o.error)
956                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
957
958                                         x = null;
959                                 } else
960                                         w.setTimeout(ready, 10);
961                         };
962
963                         // Syncronous request
964                         if (!o.async)
965                                 return ready();
966
967                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
968                         t = w.setTimeout(ready, 10);
969                 }
970         }
971 });
972
973 (function() {
974         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
975
976         tinymce.create('tinymce.util.JSONRequest', {
977                 JSONRequest : function(s) {
978                         this.settings = extend({
979                         }, s);
980                         this.count = 0;
981                 },
982
983                 send : function(o) {
984                         var ecb = o.error, scb = o.success;
985
986                         o = extend(this.settings, o);
987
988                         o.success = function(c, x) {
989                                 c = JSON.parse(c);
990
991                                 if (typeof(c) == 'undefined') {
992                                         c = {
993                                                 error : 'JSON Parse error.'
994                                         };
995                                 }
996
997                                 if (c.error)
998                                         ecb.call(o.error_scope || o.scope, c.error, x);
999                                 else
1000                                         scb.call(o.success_scope || o.scope, c.result);
1001                         };
1002
1003                         o.error = function(ty, x) {
1004                                 if (ecb)
1005                                         ecb.call(o.error_scope || o.scope, ty, x);
1006                         };
1007
1008                         o.data = JSON.serialize({
1009                                 id : o.id || 'c' + (this.count++),
1010                                 method : o.method,
1011                                 params : o.params
1012                         });
1013
1014                         // JSON content type for Ruby on rails. Bug: #1883287
1015                         o.content_type = 'application/json';
1016
1017                         XHR.send(o);
1018                 },
1019
1020                 'static' : {
1021                         sendRPC : function(o) {
1022                                 return new tinymce.util.JSONRequest().send(o);
1023                         }
1024                 }
1025         });
1026 }());
1027 (function(tinymce) {
1028         var namedEntities, baseEntities, reverseEntities,
1029                 attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1030                 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1031                 rawCharsRegExp = /[<>&\"\']/g,
1032                 entityRegExp = /&(#)?([\w]+);/g,
1033                 asciiMap = {
1034                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1035                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1036                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1037                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1038                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1039                 };
1040
1041         // Raw entities
1042         baseEntities = {
1043                 '"' : '&quot;',
1044                 "'" : '&#39;',
1045                 '<' : '&lt;',
1046                 '>' : '&gt;',
1047                 '&' : '&amp;'
1048         };
1049
1050         // Reverse lookup table for raw entities
1051         reverseEntities = {
1052                 '&lt;' : '<',
1053                 '&gt;' : '>',
1054                 '&amp;' : '&',
1055                 '&quot;' : '"',
1056                 '&apos;' : "'"
1057         };
1058
1059         // Decodes text by using the browser
1060         function nativeDecode(text) {
1061                 var elm;
1062
1063                 elm = document.createElement("div");
1064                 elm.innerHTML = text;
1065
1066                 return elm.textContent || elm.innerText || text;
1067         };
1068
1069         // Build a two way lookup table for the entities
1070         function buildEntitiesLookup(items, radix) {
1071                 var i, chr, entity, lookup = {};
1072
1073                 if (items) {
1074                         items = items.split(',');
1075                         radix = radix || 10;
1076
1077                         // Build entities lookup table
1078                         for (i = 0; i < items.length; i += 2) {
1079                                 chr = String.fromCharCode(parseInt(items[i], radix));
1080
1081                                 // Only add non base entities
1082                                 if (!baseEntities[chr]) {
1083                                         entity = '&' + items[i + 1] + ';';
1084                                         lookup[chr] = entity;
1085                                         lookup[entity] = chr;
1086                                 }
1087                         }
1088
1089                         return lookup;
1090                 }
1091         };
1092
1093         // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1094         namedEntities = buildEntitiesLookup(
1095                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1096                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1097                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1098                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1099                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1100                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1101                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1102                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1103                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1104                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1105                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1106                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1107                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1108                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1109                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1110                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1111                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1112                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1113                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1114                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1115                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1116                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1117                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1118                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1119                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1120         , 32);
1121
1122         tinymce.html = tinymce.html || {};
1123
1124         tinymce.html.Entities = {
1125                 encodeRaw : function(text, attr) {
1126                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1127                                 return baseEntities[chr] || chr;
1128                         });
1129                 },
1130
1131                 encodeAllRaw : function(text) {
1132                         return ('' + text).replace(rawCharsRegExp, function(chr) {
1133                                 return baseEntities[chr] || chr;
1134                         });
1135                 },
1136
1137                 encodeNumeric : function(text, attr) {
1138                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1139                                 // Multi byte sequence convert it to a single entity
1140                                 if (chr.length > 1)
1141                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1142
1143                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1144                         });
1145                 },
1146
1147                 encodeNamed : function(text, attr, entities) {
1148                         entities = entities || namedEntities;
1149
1150                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1151                                 return baseEntities[chr] || entities[chr] || chr;
1152                         });
1153                 },
1154
1155                 getEncodeFunc : function(name, entities) {
1156                         var Entities = tinymce.html.Entities;
1157
1158                         entities = buildEntitiesLookup(entities) || namedEntities;
1159
1160                         function encodeNamedAndNumeric(text, attr) {
1161                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1162                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1163                                 });
1164                         };
1165
1166                         function encodeCustomNamed(text, attr) {
1167                                 return Entities.encodeNamed(text, attr, entities);
1168                         };
1169
1170                         // Replace + with , to be compatible with previous TinyMCE versions
1171                         name = tinymce.makeMap(name.replace(/\+/g, ','));
1172
1173                         // Named and numeric encoder
1174                         if (name.named && name.numeric)
1175                                 return encodeNamedAndNumeric;
1176
1177                         // Named encoder
1178                         if (name.named) {
1179                                 // Custom names
1180                                 if (entities)
1181                                         return encodeCustomNamed;
1182
1183                                 return Entities.encodeNamed;
1184                         }
1185
1186                         // Numeric
1187                         if (name.numeric)
1188                                 return Entities.encodeNumeric;
1189
1190                         // Raw encoder
1191                         return Entities.encodeRaw;
1192                 },
1193
1194                 decode : function(text) {
1195                         return text.replace(entityRegExp, function(all, numeric, value) {
1196                                 if (numeric) {
1197                                         value = parseInt(value);
1198
1199                                         // Support upper UTF
1200                                         if (value > 0xFFFF) {
1201                                                 value -= 0x10000;
1202
1203                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1204                                         } else
1205                                                 return asciiMap[value] || String.fromCharCode(value);
1206                                 }
1207
1208                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1209                         });
1210                 }
1211         };
1212 })(tinymce);
1213
1214 tinymce.html.Styles = function(settings, schema) {
1215         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1216                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1217                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1218                 trimRightRegExp = /\s+$/,
1219                 urlColorRegExp = /rgb/,
1220                 undef, i, encodingLookup = {}, encodingItems;
1221
1222         settings = settings || {};
1223
1224         encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
1225         for (i = 0; i < encodingItems.length; i++) {
1226                 encodingLookup[encodingItems[i]] = '_' + i;
1227                 encodingLookup['_' + i] = encodingItems[i];
1228         }
1229
1230         function toHex(match, r, g, b) {
1231                 function hex(val) {
1232                         val = parseInt(val).toString(16);
1233
1234                         return val.length > 1 ? val : '0' + val; // 0 -> 00
1235                 };
1236
1237                 return '#' + hex(r) + hex(g) + hex(b);
1238         };
1239
1240         return {
1241                 toHex : function(color) {
1242                         return color.replace(rgbRegExp, toHex);
1243                 },
1244
1245                 parse : function(css) {
1246                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1247
1248                         function compress(prefix, suffix) {
1249                                 var top, right, bottom, left;
1250
1251                                 // Get values and check it it needs compressing
1252                                 top = styles[prefix + '-top' + suffix];
1253                                 if (!top)
1254                                         return;
1255
1256                                 right = styles[prefix + '-right' + suffix];
1257                                 if (top != right)
1258                                         return;
1259
1260                                 bottom = styles[prefix + '-bottom' + suffix];
1261                                 if (right != bottom)
1262                                         return;
1263
1264                                 left = styles[prefix + '-left' + suffix];
1265                                 if (bottom != left)
1266                                         return;
1267
1268                                 // Compress
1269                                 styles[prefix + suffix] = left;
1270                                 delete styles[prefix + '-top' + suffix];
1271                                 delete styles[prefix + '-right' + suffix];
1272                                 delete styles[prefix + '-bottom' + suffix];
1273                                 delete styles[prefix + '-left' + suffix];
1274                         };
1275
1276                         function canCompress(key) {
1277                                 var value = styles[key], i;
1278
1279                                 if (!value || value.indexOf(' ') < 0)
1280                                         return;
1281
1282                                 value = value.split(' ');
1283                                 i = value.length;
1284                                 while (i--) {
1285                                         if (value[i] !== value[0])
1286                                                 return false;
1287                                 }
1288
1289                                 styles[key] = value[0];
1290
1291                                 return true;
1292                         };
1293
1294                         function compress2(target, a, b, c) {
1295                                 if (!canCompress(a))
1296                                         return;
1297
1298                                 if (!canCompress(b))
1299                                         return;
1300
1301                                 if (!canCompress(c))
1302                                         return;
1303
1304                                 // Compress
1305                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1306                                 delete styles[a];
1307                                 delete styles[b];
1308                                 delete styles[c];
1309                         };
1310
1311                         // Encodes the specified string by replacing all \" \' ; : with _<num>
1312                         function encode(str) {
1313                                 isEncoded = true;
1314
1315                                 return encodingLookup[str];
1316                         };
1317
1318                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1319                         // It will also decode the \" \' if keep_slashes is set to fale or omitted
1320                         function decode(str, keep_slashes) {
1321                                 if (isEncoded) {
1322                                         str = str.replace(/_[0-9]/g, function(str) {
1323                                                 return encodingLookup[str];
1324                                         });
1325                                 }
1326
1327                                 if (!keep_slashes)
1328                                         str = str.replace(/\\([\'\";:])/g, "$1");
1329
1330                                 return str;
1331                         }
1332
1333                         if (css) {
1334                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1335                                 css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1336                                         return str.replace(/[;:]/g, encode);
1337                                 });
1338
1339                                 // Parse styles
1340                                 while (matches = styleRegExp.exec(css)) {
1341                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1342                                         value = matches[2].replace(trimRightRegExp, '');
1343
1344                                         if (name && value.length > 0) {
1345                                                 // Opera will produce 700 instead of bold in their style values
1346                                                 if (name === 'font-weight' && value === '700')
1347                                                         value = 'bold';
1348                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1349                                                         value = value.toLowerCase();            
1350
1351                                                 // Convert RGB colors to HEX
1352                                                 value = value.replace(rgbRegExp, toHex);
1353
1354                                                 // Convert URLs and force them into url('value') format
1355                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1356                                                         str = str || str2;
1357
1358                                                         if (str) {
1359                                                                 str = decode(str);
1360
1361                                                                 // Force strings into single quote format
1362                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";
1363                                                         }
1364
1365                                                         url = decode(url || url2 || url3);
1366
1367                                                         // Convert the URL to relative/absolute depending on config
1368                                                         if (urlConverter)
1369                                                                 url = urlConverter.call(urlConverterScope, url, 'style');
1370
1371                                                         // Output new URL format
1372                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";
1373                                                 });
1374
1375                                                 styles[name] = isEncoded ? decode(value, true) : value;
1376                                         }
1377
1378                                         styleRegExp.lastIndex = matches.index + matches[0].length;
1379                                 }
1380
1381                                 // Compress the styles to reduce it's size for example IE will expand styles
1382                                 compress("border", "");
1383                                 compress("border", "-width");
1384                                 compress("border", "-color");
1385                                 compress("border", "-style");
1386                                 compress("padding", "");
1387                                 compress("margin", "");
1388                                 compress2('border', 'border-width', 'border-style', 'border-color');
1389
1390                                 // Remove pointless border, IE produces these
1391                                 if (styles.border === 'medium none')
1392                                         delete styles.border;
1393                         }
1394
1395                         return styles;
1396                 },
1397
1398                 serialize : function(styles, element_name) {
1399                         var css = '', name, value;
1400
1401                         function serializeStyles(name) {
1402                                 var styleList, i, l, name, value;
1403
1404                                 styleList = schema.styles[name];
1405                                 if (styleList) {
1406                                         for (i = 0, l = styleList.length; i < l; i++) {
1407                                                 name = styleList[i];
1408                                                 value = styles[name];
1409
1410                                                 if (value !== undef && value.length > 0)
1411                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1412                                         }
1413                                 }
1414                         };
1415
1416                         // Serialize styles according to schema
1417                         if (element_name && schema && schema.styles) {
1418                                 // Serialize global styles and element specific styles
1419                                 serializeStyles('*');
1420                                 serializeStyles(name);
1421                         } else {
1422                                 // Output the styles in the order they are inside the object
1423                                 for (name in styles) {
1424                                         value = styles[name];
1425
1426                                         if (value !== undef && value.length > 0)
1427                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1428                                 }
1429                         }
1430
1431                         return css;
1432                 }
1433         };
1434 };
1435
1436 (function(tinymce) {
1437         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
1438                 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1439
1440         function split(str, delim) {
1441                 return str.split(delim || ',');
1442         };
1443
1444         function unpack(lookup, data) {
1445                 var key, elements = {};
1446
1447                 function replace(value) {
1448                         return value.replace(/[A-Z]+/g, function(key) {
1449                                 return replace(lookup[key]);
1450                         });
1451                 };
1452
1453                 // Unpack lookup
1454                 for (key in lookup) {
1455                         if (lookup.hasOwnProperty(key))
1456                                 lookup[key] = replace(lookup[key]);
1457                 }
1458
1459                 // Unpack and parse data into object map
1460                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1461                         attributes = split(attributes, '|');
1462
1463                         elements[name] = {
1464                                 attributes : makeMap(attributes),
1465                                 attributesOrder : attributes,
1466                                 children : makeMap(children, '|', {'#comment' : {}})
1467                         }
1468                 });
1469
1470                 return elements;
1471         };
1472
1473         // Build a lookup table for block elements both lowercase and uppercase
1474         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + 
1475                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + 
1476                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1477         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1478
1479         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1480         transitional = unpack({
1481                 Z : 'H|K|N|O|P',
1482                 Y : 'X|form|R|Q',
1483                 ZG : 'E|span|width|align|char|charoff|valign',
1484                 X : 'p|T|div|U|W|isindex|fieldset|table',
1485                 ZF : 'E|align|char|charoff|valign',
1486                 W : 'pre|hr|blockquote|address|center|noframes',
1487                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1488                 ZD : '[E][S]',
1489                 U : 'ul|ol|dl|menu|dir',
1490                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1491                 T : 'h1|h2|h3|h4|h5|h6',
1492                 ZB : 'X|S|Q',
1493                 S : 'R|P',
1494                 ZA : 'a|G|J|M|O|P',
1495                 R : 'a|H|K|N|O',
1496                 Q : 'noscript|P',
1497                 P : 'ins|del|script',
1498                 O : 'input|select|textarea|label|button',
1499                 N : 'M|L',
1500                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1501                 L : 'sub|sup',
1502                 K : 'J|I',
1503                 J : 'tt|i|b|u|s|strike',
1504                 I : 'big|small|font|basefont',
1505                 H : 'G|F',
1506                 G : 'br|span|bdo',
1507                 F : 'object|applet|img|map|iframe',
1508                 E : 'A|B|C',
1509                 D : 'accesskey|tabindex|onfocus|onblur',
1510                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1511                 B : 'lang|xml:lang|dir',
1512                 A : 'id|class|style|title'
1513         }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
1514                 'style[B|id|type|media|title|xml:space][]' + 
1515                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
1516                 'param[id|name|value|valuetype|type][]' + 
1517                 'p[E|align][#|S]' + 
1518                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
1519                 'br[A|clear][]' + 
1520                 'span[E][#|S]' + 
1521                 'bdo[A|C|B][#|S]' + 
1522                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
1523                 'h1[E|align][#|S]' + 
1524                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
1525                 'map[B|C|A|name][X|form|Q|area]' + 
1526                 'h2[E|align][#|S]' + 
1527                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
1528                 'h3[E|align][#|S]' + 
1529                 'tt[E][#|S]' + 
1530                 'i[E][#|S]' + 
1531                 'b[E][#|S]' + 
1532                 'u[E][#|S]' + 
1533                 's[E][#|S]' + 
1534                 'strike[E][#|S]' + 
1535                 'big[E][#|S]' + 
1536                 'small[E][#|S]' + 
1537                 'font[A|B|size|color|face][#|S]' + 
1538                 'basefont[id|size|color|face][]' + 
1539                 'em[E][#|S]' + 
1540                 'strong[E][#|S]' + 
1541                 'dfn[E][#|S]' + 
1542                 'code[E][#|S]' + 
1543                 'q[E|cite][#|S]' + 
1544                 'samp[E][#|S]' + 
1545                 'kbd[E][#|S]' + 
1546                 'var[E][#|S]' + 
1547                 'cite[E][#|S]' + 
1548                 'abbr[E][#|S]' + 
1549                 'acronym[E][#|S]' + 
1550                 'sub[E][#|S]' + 
1551                 'sup[E][#|S]' + 
1552                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
1553                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
1554                 'optgroup[E|disabled|label][option]' + 
1555                 'option[E|selected|disabled|label|value][]' + 
1556                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
1557                 'label[E|for|accesskey|onfocus|onblur][#|S]' + 
1558                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
1559                 'h4[E|align][#|S]' + 
1560                 'ins[E|cite|datetime][#|Y]' + 
1561                 'h5[E|align][#|S]' + 
1562                 'del[E|cite|datetime][#|Y]' + 
1563                 'h6[E|align][#|S]' + 
1564                 'div[E|align][#|Y]' + 
1565                 'ul[E|type|compact][li]' + 
1566                 'li[E|type|value][#|Y]' + 
1567                 'ol[E|type|compact|start][li]' + 
1568                 'dl[E|compact][dt|dd]' + 
1569                 'dt[E][#|S]' + 
1570                 'dd[E][#|Y]' + 
1571                 'menu[E|compact][li]' + 
1572                 'dir[E|compact][li]' + 
1573                 'pre[E|width|xml:space][#|ZA]' + 
1574                 'hr[E|align|noshade|size|width][]' + 
1575                 'blockquote[E|cite][#|Y]' + 
1576                 'address[E][#|S|p]' + 
1577                 'center[E][#|Y]' + 
1578                 'noframes[E][#|Y]' + 
1579                 'isindex[A|B|prompt][]' + 
1580                 'fieldset[E][#|legend|Y]' + 
1581                 'legend[E|accesskey|align][#|S]' + 
1582                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
1583                 'caption[E|align][#|S]' + 
1584                 'col[ZG][]' + 
1585                 'colgroup[ZG][col]' + 
1586                 'thead[ZF][tr]' + 
1587                 'tr[ZF|bgcolor][th|td]' + 
1588                 'th[E|ZE][#|Y]' + 
1589                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
1590                 'noscript[E][#|Y]' + 
1591                 'td[E|ZE][#|Y]' + 
1592                 'tfoot[ZF][tr]' + 
1593                 'tbody[ZF][tr]' + 
1594                 'area[E|D|shape|coords|href|nohref|alt|target][]' + 
1595                 'base[id|href|target][]' + 
1596                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1597         );
1598
1599         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
1600         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1601         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
1602         whiteSpaceElementsMap = makeMap('pre,script,style');
1603         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1604
1605         tinymce.html.Schema = function(settings) {
1606                 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1607
1608                 settings = settings || {};
1609
1610                 // Allow all elements and attributes if verify_html is set to false
1611                 if (settings.verify_html === false)
1612                         settings.valid_elements = '*[*]';
1613
1614                 // Build styles list
1615                 if (settings.valid_styles) {
1616                         validStyles = {};
1617
1618                         // Convert styles into a rule list
1619                         each(settings.valid_styles, function(value, key) {
1620                                 validStyles[key] = tinymce.explode(value);
1621                         });
1622                 }
1623
1624                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1625                 function patternToRegExp(str) {
1626                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1627                 };
1628
1629                 // Parses the specified valid_elements string and adds to the current rules
1630                 // This function is a bit hard to read since it's heavily optimized for speed
1631                 function addValidElements(valid_elements) {
1632                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1633                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1634                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1635                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1636                                 hasPatternsRegExp = /[*?+]/;
1637
1638                         if (valid_elements) {
1639                                 // Split valid elements into an array with rules
1640                                 valid_elements = split(valid_elements);
1641
1642                                 if (elements['@']) {
1643                                         globalAttributes = elements['@'].attributes;
1644                                         globalAttributesOrder = elements['@'].attributesOrder;
1645                                 }
1646
1647                                 // Loop all rules
1648                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1649                                         // Parse element rule
1650                                         matches = elementRuleRegExp.exec(valid_elements[ei]);
1651                                         if (matches) {
1652                                                 // Setup local names for matches
1653                                                 prefix = matches[1];
1654                                                 elementName = matches[2];
1655                                                 outputName = matches[3];
1656                                                 attrData = matches[4];
1657
1658                                                 // Create new attributes and attributesOrder
1659                                                 attributes = {};
1660                                                 attributesOrder = [];
1661
1662                                                 // Create the new element
1663                                                 element = {
1664                                                         attributes : attributes,
1665                                                         attributesOrder : attributesOrder
1666                                                 };
1667
1668                                                 // Padd empty elements prefix
1669                                                 if (prefix === '#')
1670                                                         element.paddEmpty = true;
1671
1672                                                 // Remove empty elements prefix
1673                                                 if (prefix === '-')
1674                                                         element.removeEmpty = true;
1675
1676                                                 // Copy attributes from global rule into current rule
1677                                                 if (globalAttributes) {
1678                                                         for (key in globalAttributes)
1679                                                                 attributes[key] = globalAttributes[key];
1680
1681                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
1682                                                 }
1683
1684                                                 // Attributes defined
1685                                                 if (attrData) {
1686                                                         attrData = split(attrData, '|');
1687                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {
1688                                                                 matches = attrRuleRegExp.exec(attrData[ai]);
1689                                                                 if (matches) {
1690                                                                         attr = {};
1691                                                                         attrType = matches[1];
1692                                                                         attrName = matches[2].replace(/::/g, ':');
1693                                                                         prefix = matches[3];
1694                                                                         value = matches[4];
1695
1696                                                                         // Required
1697                                                                         if (attrType === '!') {
1698                                                                                 element.attributesRequired = element.attributesRequired || [];
1699                                                                                 element.attributesRequired.push(attrName);
1700                                                                                 attr.required = true;
1701                                                                         }
1702
1703                                                                         // Denied from global
1704                                                                         if (attrType === '-') {
1705                                                                                 delete attributes[attrName];
1706                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
1707                                                                                 continue;
1708                                                                         }
1709
1710                                                                         // Default value
1711                                                                         if (prefix) {
1712                                                                                 // Default value
1713                                                                                 if (prefix === '=') {
1714                                                                                         element.attributesDefault = element.attributesDefault || [];
1715                                                                                         element.attributesDefault.push({name: attrName, value: value});
1716                                                                                         attr.defaultValue = value;
1717                                                                                 }
1718
1719                                                                                 // Forced value
1720                                                                                 if (prefix === ':') {
1721                                                                                         element.attributesForced = element.attributesForced || [];
1722                                                                                         element.attributesForced.push({name: attrName, value: value});
1723                                                                                         attr.forcedValue = value;
1724                                                                                 }
1725
1726                                                                                 // Required values
1727                                                                                 if (prefix === '<')
1728                                                                                         attr.validValues = makeMap(value, '?');
1729                                                                         }
1730
1731                                                                         // Check for attribute patterns
1732                                                                         if (hasPatternsRegExp.test(attrName)) {
1733                                                                                 element.attributePatterns = element.attributePatterns || [];
1734                                                                                 attr.pattern = patternToRegExp(attrName);
1735                                                                                 element.attributePatterns.push(attr);
1736                                                                         } else {
1737                                                                                 // Add attribute to order list if it doesn't already exist
1738                                                                                 if (!attributes[attrName])
1739                                                                                         attributesOrder.push(attrName);
1740
1741                                                                                 attributes[attrName] = attr;
1742                                                                         }
1743                                                                 }
1744                                                         }
1745                                                 }
1746
1747                                                 // Global rule, store away these for later usage
1748                                                 if (!globalAttributes && elementName == '@') {
1749                                                         globalAttributes = attributes;
1750                                                         globalAttributesOrder = attributesOrder;
1751                                                 }
1752
1753                                                 // Handle substitute elements such as b/strong
1754                                                 if (outputName) {
1755                                                         element.outputName = elementName;
1756                                                         elements[outputName] = element;
1757                                                 }
1758
1759                                                 // Add pattern or exact element
1760                                                 if (hasPatternsRegExp.test(elementName)) {
1761                                                         element.pattern = patternToRegExp(elementName);
1762                                                         patternElements.push(element);
1763                                                 } else
1764                                                         elements[elementName] = element;
1765                                         }
1766                                 }
1767                         }
1768                 };
1769
1770                 function setValidElements(valid_elements) {
1771                         elements = {};
1772                         patternElements = [];
1773
1774                         addValidElements(valid_elements);
1775
1776                         each(transitional, function(element, name) {
1777                                 children[name] = element.children;
1778                         });
1779                 };
1780
1781                 // Adds custom non HTML elements to the schema
1782                 function addCustomElements(custom_elements) {
1783                         var customElementRegExp = /^(~)?(.+)$/;
1784
1785                         if (custom_elements) {
1786                                 each(split(custom_elements), function(rule) {
1787                                         var matches = customElementRegExp.exec(rule),
1788                                                 cloneName = matches[1] === '~' ? 'span' : 'div',
1789                                                 name = matches[2];
1790
1791                                         children[name] = children[cloneName];
1792
1793                                         // Add custom elements at span/div positions
1794                                         each(children, function(element, child) {
1795                                                 if (element[cloneName])
1796                                                         element[name] = element[cloneName];
1797                                         });
1798                                 });
1799                         }
1800                 };
1801
1802                 // Adds valid children to the schema object
1803                 function addValidChildren(valid_children) {
1804                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
1805
1806                         if (valid_children) {
1807                                 each(split(valid_children), function(rule) {
1808                                         var matches = childRuleRegExp.exec(rule), parent, prefix;
1809
1810                                         if (matches) {
1811                                                 prefix = matches[1];
1812
1813                                                 // Add/remove items from default
1814                                                 if (prefix)
1815                                                         parent = children[matches[2]];
1816                                                 else
1817                                                         parent = children[matches[2]] = {'#comment' : {}};
1818
1819                                                 parent = children[matches[2]];
1820
1821                                                 each(split(matches[3], '|'), function(child) {
1822                                                         if (prefix === '-')
1823                                                                 delete parent[child];
1824                                                         else
1825                                                                 parent[child] = {};
1826                                                 });
1827                                         }
1828                                 });
1829                         }
1830                 }
1831
1832                 if (!settings.valid_elements) {
1833                         // No valid elements defined then clone the elements from the transitional spec
1834                         each(transitional, function(element, name) {
1835                                 elements[name] = {
1836                                         attributes : element.attributes,
1837                                         attributesOrder : element.attributesOrder
1838                                 };
1839
1840                                 children[name] = element.children;
1841                         });
1842
1843                         // Switch these
1844                         each(split('strong/b,em/i'), function(item) {
1845                                 item = split(item, '/');
1846                                 elements[item[1]].outputName = item[0];
1847                         });
1848
1849                         // Add default alt attribute for images
1850                         elements.img.attributesDefault = [{name: 'alt', value: ''}];
1851
1852                         // Remove these if they are empty by default
1853                         each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
1854                                 elements[name].removeEmpty = true;
1855                         });
1856
1857                         // Padd these by default
1858                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
1859                                 elements[name].paddEmpty = true;
1860                         });
1861                 } else
1862                         setValidElements(settings.valid_elements);
1863
1864                 addCustomElements(settings.custom_elements);
1865                 addValidChildren(settings.valid_children);
1866                 addValidElements(settings.extended_valid_elements);
1867
1868                 // Todo: Remove this when we fix list handling to be valid
1869                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
1870
1871                 // Delete invalid elements
1872                 if (settings.invalid_elements) {
1873                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
1874                                 if (elements[item])
1875                                         delete elements[item];
1876                         });
1877                 }
1878
1879                 self.children = children;
1880
1881                 self.styles = validStyles;
1882
1883                 self.getBoolAttrs = function() {
1884                         return boolAttrMap;
1885                 };
1886
1887                 self.getBlockElements = function() {
1888                         return blockElementsMap;
1889                 };
1890
1891                 self.getShortEndedElements = function() {
1892                         return shortEndedElementsMap;
1893                 };
1894
1895                 self.getSelfClosingElements = function() {
1896                         return selfClosingElementsMap;
1897                 };
1898
1899                 self.getNonEmptyElements = function() {
1900                         return nonEmptyElementsMap;
1901                 };
1902
1903                 self.getWhiteSpaceElements = function() {
1904                         return whiteSpaceElementsMap;
1905                 };
1906
1907                 self.isValidChild = function(name, child) {
1908                         var parent = children[name];
1909
1910                         return !!(parent && parent[child]);
1911                 };
1912
1913                 self.getElementRule = function(name) {
1914                         var element = elements[name], i;
1915
1916                         // Exact match found
1917                         if (element)
1918                                 return element;
1919
1920                         // No exact match then try the patterns
1921                         i = patternElements.length;
1922                         while (i--) {
1923                                 element = patternElements[i];
1924
1925                                 if (element.pattern.test(name))
1926                                         return element;
1927                         }
1928                 };
1929
1930                 self.addValidElements = addValidElements;
1931
1932                 self.setValidElements = setValidElements;
1933
1934                 self.addCustomElements = addCustomElements;
1935
1936                 self.addValidChildren = addValidChildren;
1937         };
1938
1939         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
1940         tinymce.html.Schema.boolAttrMap = boolAttrMap;
1941         tinymce.html.Schema.blockElementsMap = blockElementsMap;
1942 })(tinymce);
1943
1944 (function(tinymce) {
1945         tinymce.html.SaxParser = function(settings, schema) {
1946                 var self = this, noop = function() {};
1947
1948                 settings = settings || {};
1949                 self.schema = schema = schema || new tinymce.html.Schema();
1950
1951                 if (settings.fix_self_closing !== false)
1952                         settings.fix_self_closing = true;
1953
1954                 // Add handler functions from settings and setup default handlers
1955                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
1956                         if (name)
1957                                 self[name] = settings[name] || noop;
1958                 });
1959
1960                 self.parse = function(html) {
1961                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
1962                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
1963                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
1964                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
1965
1966                         function processEndTag(name) {
1967                                 var pos, i;
1968
1969                                 // Find position of parent of the same type
1970                                 pos = stack.length;
1971                                 while (pos--) {
1972                                         if (stack[pos].name === name)
1973                                                 break;                                          
1974                                 }
1975
1976                                 // Found parent
1977                                 if (pos >= 0) {
1978                                         // Close all the open elements
1979                                         for (i = stack.length - 1; i >= pos; i--) {
1980                                                 name = stack[i];
1981
1982                                                 if (name.valid)
1983                                                         self.end(name.name);
1984                                         }
1985
1986                                         // Remove the open elements from the stack
1987                                         stack.length = pos;
1988                                 }
1989                         };
1990
1991                         // Precompile RegExps and map objects
1992                         tokenRegExp = new RegExp('<(?:' +
1993                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
1994                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
1995                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
1996                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
1997                                 '(?:\\/([^>]+)>)|' + // End element
1998                                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
1999                         ')', 'g');
2000
2001                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2002                         specialElements = {
2003                                 'script' : /<\/script[^>]*>/gi,
2004                                 'style' : /<\/style[^>]*>/gi,
2005                                 'noscript' : /<\/noscript[^>]*>/gi
2006                         };
2007
2008                         // Setup lookup tables for empty elements and boolean attributes
2009                         shortEndedElements = schema.getShortEndedElements();
2010                         selfClosing = schema.getSelfClosingElements();
2011                         fillAttrsMap = schema.getBoolAttrs();
2012                         validate = settings.validate;
2013                         fixSelfClosing = settings.fix_self_closing;
2014
2015                         while (matches = tokenRegExp.exec(html)) {
2016                                 // Text
2017                                 if (index < matches.index)
2018                                         self.text(decode(html.substr(index, matches.index - index)));
2019
2020                                 if (value = matches[6]) { // End element
2021                                         processEndTag(value.toLowerCase());
2022                                 } else if (value = matches[7]) { // Start element
2023                                         value = value.toLowerCase();
2024                                         isShortEnded = value in shortEndedElements;
2025
2026                                         // Is self closing tag for example an <li> after an open <li>
2027                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2028                                                 processEndTag(value);
2029
2030                                         // Validate element
2031                                         if (!validate || (elementRule = schema.getElementRule(value))) {
2032                                                 isValidElement = true;
2033
2034                                                 // Grab attributes map and patters when validation is enabled
2035                                                 if (validate) {
2036                                                         validAttributesMap = elementRule.attributes;
2037                                                         validAttributePatterns = elementRule.attributePatterns;
2038                                                 }
2039
2040                                                 // Parse attributes
2041                                                 if (attribsValue = matches[8]) {
2042                                                         attrList = [];
2043                                                         attrList.map = {};
2044
2045                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2046                                                                 var attrRule, i;
2047
2048                                                                 name = name.toLowerCase();
2049                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2050
2051                                                                 // Validate name and value
2052                                                                 if (validate && name.indexOf('data-') !== 0) {
2053                                                                         attrRule = validAttributesMap[name];
2054
2055                                                                         // Find rule by pattern matching
2056                                                                         if (!attrRule && validAttributePatterns) {
2057                                                                                 i = validAttributePatterns.length;
2058                                                                                 while (i--) {
2059                                                                                         attrRule = validAttributePatterns[i];
2060                                                                                         if (attrRule.pattern.test(name))
2061                                                                                                 break;
2062                                                                                 }
2063
2064                                                                                 // No rule matched
2065                                                                                 if (i === -1)
2066                                                                                         attrRule = null;
2067                                                                         }
2068
2069                                                                         // No attribute rule found
2070                                                                         if (!attrRule)
2071                                                                                 return;
2072
2073                                                                         // Validate value
2074                                                                         if (attrRule.validValues && !(value in attrRule.validValues))
2075                                                                                 return;
2076                                                                 }
2077
2078                                                                 // Add attribute to list and map
2079                                                                 attrList.map[name] = value;
2080                                                                 attrList.push({
2081                                                                         name: name,
2082                                                                         value: value
2083                                                                 });
2084                                                         });
2085                                                 } else {
2086                                                         attrList = [];
2087                                                         attrList.map = {};
2088                                                 }
2089
2090                                                 // Process attributes if validation is enabled
2091                                                 if (validate) {
2092                                                         attributesRequired = elementRule.attributesRequired;
2093                                                         attributesDefault = elementRule.attributesDefault;
2094                                                         attributesForced = elementRule.attributesForced;
2095
2096                                                         // Handle forced attributes
2097                                                         if (attributesForced) {
2098                                                                 i = attributesForced.length;
2099                                                                 while (i--) {
2100                                                                         attr = attributesForced[i];
2101                                                                         name = attr.name;
2102                                                                         attrValue = attr.value;
2103
2104                                                                         if (attrValue === '{$uid}')
2105                                                                                 attrValue = 'mce_' + idCount++;
2106
2107                                                                         attrList.map[name] = attrValue;
2108                                                                         attrList.push({name: name, value: attrValue});
2109                                                                 }
2110                                                         }
2111
2112                                                         // Handle default attributes
2113                                                         if (attributesDefault) {
2114                                                                 i = attributesDefault.length;
2115                                                                 while (i--) {
2116                                                                         attr = attributesDefault[i];
2117                                                                         name = attr.name;
2118
2119                                                                         if (!(name in attrList.map)) {
2120                                                                                 attrValue = attr.value;
2121
2122                                                                                 if (attrValue === '{$uid}')
2123                                                                                         attrValue = 'mce_' + idCount++;
2124
2125                                                                                 attrList.map[name] = attrValue;
2126                                                                                 attrList.push({name: name, value: attrValue});
2127                                                                         }
2128                                                                 }
2129                                                         }
2130
2131                                                         // Handle required attributes
2132                                                         if (attributesRequired) {
2133                                                                 i = attributesRequired.length;
2134                                                                 while (i--) {
2135                                                                         if (attributesRequired[i] in attrList.map)
2136                                                                                 break;
2137                                                                 }
2138
2139                                                                 // None of the required attributes where found
2140                                                                 if (i === -1)
2141                                                                         isValidElement = false;
2142                                                         }
2143
2144                                                         // Invalidate element if it's marked as bogus
2145                                                         if (attrList.map['data-mce-bogus'])
2146                                                                 isValidElement = false;
2147                                                 }
2148
2149                                                 if (isValidElement)
2150                                                         self.start(value, attrList, isShortEnded);
2151                                         } else
2152                                                 isValidElement = false;
2153
2154                                         // Treat script, noscript and style a bit different since they may include code that looks like elements
2155                                         if (endRegExp = specialElements[value]) {
2156                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;
2157
2158                                                 if (matches = endRegExp.exec(html)) {
2159                                                         if (isValidElement)
2160                                                                 text = html.substr(index, matches.index - index);
2161
2162                                                         index = matches.index + matches[0].length;
2163                                                 } else {
2164                                                         text = html.substr(index);
2165                                                         index = html.length;
2166                                                 }
2167
2168                                                 if (isValidElement && text.length > 0)
2169                                                         self.text(text, true);
2170
2171                                                 if (isValidElement)
2172                                                         self.end(value);
2173
2174                                                 tokenRegExp.lastIndex = index;
2175                                                 continue;
2176                                         }
2177
2178                                         // Push value on to stack
2179                                         if (!isShortEnded) {
2180                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2181                                                         stack.push({name: value, valid: isValidElement});
2182                                                 else if (isValidElement)
2183                                                         self.end(value);
2184                                         }
2185                                 } else if (value = matches[1]) { // Comment
2186                                         self.comment(value);
2187                                 } else if (value = matches[2]) { // CDATA
2188                                         self.cdata(value);
2189                                 } else if (value = matches[3]) { // DOCTYPE
2190                                         self.doctype(value);
2191                                 } else if (value = matches[4]) { // PI
2192                                         self.pi(value, matches[5]);
2193                                 }
2194
2195                                 index = matches.index + matches[0].length;
2196                         }
2197
2198                         // Text
2199                         if (index < html.length)
2200                                 self.text(decode(html.substr(index)));
2201
2202                         // Close any open elements
2203                         for (i = stack.length - 1; i >= 0; i--) {
2204                                 value = stack[i];
2205
2206                                 if (value.valid)
2207                                         self.end(value.name);
2208                         }
2209                 };
2210         }
2211 })(tinymce);
2212
2213 (function(tinymce) {
2214         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2215                 '#text' : 3,
2216                 '#comment' : 8,
2217                 '#cdata' : 4,
2218                 '#pi' : 7,
2219                 '#doctype' : 10,
2220                 '#document-fragment' : 11
2221         };
2222
2223         // Walks the tree left/right
2224         function walk(node, root_node, prev) {
2225                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2226
2227                 // Walk into nodes if it has a start
2228                 if (node[startName])
2229                         return node[startName];
2230
2231                 // Return the sibling if it has one
2232                 if (node !== root_node) {
2233                         sibling = node[siblingName];
2234
2235                         if (sibling)
2236                                 return sibling;
2237
2238                         // Walk up the parents to look for siblings
2239                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2240                                 sibling = parent[siblingName];
2241
2242                                 if (sibling)
2243                                         return sibling;
2244                         }
2245                 }
2246         };
2247
2248         function Node(name, type) {
2249                 this.name = name;
2250                 this.type = type;
2251
2252                 if (type === 1) {
2253                         this.attributes = [];
2254                         this.attributes.map = {};
2255                 }
2256         }
2257
2258         tinymce.extend(Node.prototype, {
2259                 replace : function(node) {
2260                         var self = this;
2261
2262                         if (node.parent)
2263                                 node.remove();
2264
2265                         self.insert(node, self);
2266                         self.remove();
2267
2268                         return self;
2269                 },
2270
2271                 attr : function(name, value) {
2272                         var self = this, attrs, i, undef;
2273
2274                         if (typeof name !== "string") {
2275                                 for (i in name)
2276                                         self.attr(i, name[i]);
2277
2278                                 return self;
2279                         }
2280
2281                         if (attrs = self.attributes) {
2282                                 if (value !== undef) {
2283                                         // Remove attribute
2284                                         if (value === null) {
2285                                                 if (name in attrs.map) {
2286                                                         delete attrs.map[name];
2287
2288                                                         i = attrs.length;
2289                                                         while (i--) {
2290                                                                 if (attrs[i].name === name) {
2291                                                                         attrs = attrs.splice(i, 1);
2292                                                                         return self;
2293                                                                 }
2294                                                         }
2295                                                 }
2296
2297                                                 return self;
2298                                         }
2299
2300                                         // Set attribute
2301                                         if (name in attrs.map) {
2302                                                 // Set attribute
2303                                                 i = attrs.length;
2304                                                 while (i--) {
2305                                                         if (attrs[i].name === name) {
2306                                                                 attrs[i].value = value;
2307                                                                 break;
2308                                                         }
2309                                                 }
2310                                         } else
2311                                                 attrs.push({name: name, value: value});
2312
2313                                         attrs.map[name] = value;
2314
2315                                         return self;
2316                                 } else {
2317                                         return attrs.map[name];
2318                                 }
2319                         }
2320                 },
2321
2322                 clone : function() {
2323                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2324
2325                         // Clone element attributes
2326                         if (selfAttrs = self.attributes) {
2327                                 cloneAttrs = [];
2328                                 cloneAttrs.map = {};
2329
2330                                 for (i = 0, l = selfAttrs.length; i < l; i++) {
2331                                         selfAttr = selfAttrs[i];
2332
2333                                         // Clone everything except id
2334                                         if (selfAttr.name !== 'id') {
2335                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2336                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2337                                         }
2338                                 }
2339
2340                                 clone.attributes = cloneAttrs;
2341                         }
2342
2343                         clone.value = self.value;
2344                         clone.shortEnded = self.shortEnded;
2345
2346                         return clone;
2347                 },
2348
2349                 wrap : function(wrapper) {
2350                         var self = this;
2351
2352                         self.parent.insert(wrapper, self);
2353                         wrapper.append(self);
2354
2355                         return self;
2356                 },
2357
2358                 unwrap : function() {
2359                         var self = this, node, next;
2360
2361                         for (node = self.firstChild; node; ) {
2362                                 next = node.next;
2363                                 self.insert(node, self, true);
2364                                 node = next;
2365                         }
2366
2367                         self.remove();
2368                 },
2369
2370                 remove : function() {
2371                         var self = this, parent = self.parent, next = self.next, prev = self.prev;
2372
2373                         if (parent) {
2374                                 if (parent.firstChild === self) {
2375                                         parent.firstChild = next;
2376
2377                                         if (next)
2378                                                 next.prev = null;
2379                                 } else {
2380                                         prev.next = next;
2381                                 }
2382
2383                                 if (parent.lastChild === self) {
2384                                         parent.lastChild = prev;
2385
2386                                         if (prev)
2387                                                 prev.next = null;
2388                                 } else {
2389                                         next.prev = prev;
2390                                 }
2391
2392                                 self.parent = self.next = self.prev = null;
2393                         }
2394
2395                         return self;
2396                 },
2397
2398                 append : function(node) {
2399                         var self = this, last;
2400
2401                         if (node.parent)
2402                                 node.remove();
2403
2404                         last = self.lastChild;
2405                         if (last) {
2406                                 last.next = node;
2407                                 node.prev = last;
2408                                 self.lastChild = node;
2409                         } else
2410                                 self.lastChild = self.firstChild = node;
2411
2412                         node.parent = self;
2413
2414                         return node;
2415                 },
2416
2417                 insert : function(node, ref_node, before) {
2418                         var parent;
2419
2420                         if (node.parent)
2421                                 node.remove();
2422
2423                         parent = ref_node.parent || this;
2424
2425                         if (before) {
2426                                 if (ref_node === parent.firstChild)
2427                                         parent.firstChild = node;
2428                                 else
2429                                         ref_node.prev.next = node;
2430
2431                                 node.prev = ref_node.prev;
2432                                 node.next = ref_node;
2433                                 ref_node.prev = node;
2434                         } else {
2435                                 if (ref_node === parent.lastChild)
2436                                         parent.lastChild = node;
2437                                 else
2438                                         ref_node.next.prev = node;
2439
2440                                 node.next = ref_node.next;
2441                                 node.prev = ref_node;
2442                                 ref_node.next = node;
2443                         }
2444
2445                         node.parent = parent;
2446
2447                         return node;
2448                 },
2449
2450                 getAll : function(name) {
2451                         var self = this, node, collection = [];
2452
2453                         for (node = self.firstChild; node; node = walk(node, self)) {
2454                                 if (node.name === name)
2455                                         collection.push(node);
2456                         }
2457
2458                         return collection;
2459                 },
2460
2461                 empty : function() {
2462                         var self = this, nodes, i, node;
2463
2464                         // Remove all children
2465                         if (self.firstChild) {
2466                                 nodes = [];
2467
2468                                 // Collect the children
2469                                 for (node = self.firstChild; node; node = walk(node, self))
2470                                         nodes.push(node);
2471
2472                                 // Remove the children
2473                                 i = nodes.length;
2474                                 while (i--) {
2475                                         node = nodes[i];
2476                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2477                                 }
2478                         }
2479
2480                         self.firstChild = self.lastChild = null;
2481
2482                         return self;
2483                 },
2484
2485                 isEmpty : function(elements) {
2486                         var self = this, node = self.firstChild, i, name;
2487
2488                         if (node) {
2489                                 do {
2490                                         if (node.type === 1) {
2491                                                 // Ignore bogus elements
2492                                                 if (node.attributes.map['data-mce-bogus'])
2493                                                         continue;
2494
2495                                                 // Keep empty elements like <img />
2496                                                 if (elements[node.name])
2497                                                         return false;
2498
2499                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
2500                                                 i = node.attributes.length;
2501                                                 while (i--) {
2502                                                         name = node.attributes[i].name;
2503                                                         if (name === "name" || name.indexOf('data-') === 0)
2504                                                                 return false;
2505                                                 }
2506                                         }
2507
2508                                         // Keep non whitespace text nodes
2509                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2510                                                 return false;
2511                                 } while (node = walk(node, self));
2512                         }
2513
2514                         return true;
2515                 }
2516         });
2517
2518         tinymce.extend(Node, {
2519                 create : function(name, attrs) {
2520                         var node, attrName;
2521
2522                         // Create node
2523                         node = new Node(name, typeLookup[name] || 1);
2524
2525                         // Add attributes if needed
2526                         if (attrs) {
2527                                 for (attrName in attrs)
2528                                         node.attr(attrName, attrs[attrName]);
2529                         }
2530
2531                         return node;
2532                 }
2533         });
2534
2535         tinymce.html.Node = Node;
2536 })(tinymce);
2537
2538 (function(tinymce) {
2539         var Node = tinymce.html.Node;
2540
2541         tinymce.html.DomParser = function(settings, schema) {
2542                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2543
2544                 settings = settings || {};
2545                 settings.validate = "validate" in settings ? settings.validate : true;
2546                 settings.root_name = settings.root_name || 'body';
2547                 self.schema = schema = schema || new tinymce.html.Schema();
2548
2549                 function fixInvalidChildren(nodes) {
2550                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2551                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2552
2553                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2554                         nonEmptyElements = schema.getNonEmptyElements();
2555
2556                         for (ni = 0; ni < nodes.length; ni++) {
2557                                 node = nodes[ni];
2558
2559                                 // Already removed
2560                                 if (!node.parent)
2561                                         continue;
2562
2563                                 // Get list of all parent nodes until we find a valid parent to stick the child into
2564                                 parents = [node];
2565                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2566                                         parents.push(parent);
2567
2568                                 // Found a suitable parent
2569                                 if (parent && parents.length > 1) {
2570                                         // Reverse the array since it makes looping easier
2571                                         parents.reverse();
2572
2573                                         // Clone the related parent and insert that after the moved node
2574                                         newParent = currentNode = self.filterNode(parents[0].clone());
2575
2576                                         // Start cloning and moving children on the left side of the target node
2577                                         for (i = 0; i < parents.length - 1; i++) {
2578                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {
2579                                                         tempNode = self.filterNode(parents[i].clone());
2580                                                         currentNode.append(tempNode);
2581                                                 } else
2582                                                         tempNode = currentNode;
2583
2584                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2585                                                         nextNode = childNode.next;
2586                                                         tempNode.append(childNode);
2587                                                         childNode = nextNode;
2588                                                 }
2589
2590                                                 currentNode = tempNode;
2591                                         }
2592
2593                                         if (!newParent.isEmpty(nonEmptyElements)) {
2594                                                 parent.insert(newParent, parents[0], true);
2595                                                 parent.insert(node, newParent);
2596                                         } else {
2597                                                 parent.insert(node, parents[0], true);
2598                                         }
2599
2600                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2601                                         parent = parents[0];
2602                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2603                                                 parent.empty().remove();
2604                                         }
2605                                 } else if (node.parent) {
2606                                         // If it's an LI try to find a UL/OL for it or wrap it
2607                                         if (node.name === 'li') {
2608                                                 sibling = node.prev;
2609                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2610                                                         sibling.append(node);
2611                                                         continue;
2612                                                 }
2613
2614                                                 sibling = node.next;
2615                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2616                                                         sibling.insert(node, sibling.firstChild, true);
2617                                                         continue;
2618                                                 }
2619
2620                                                 node.wrap(self.filterNode(new Node('ul', 1)));
2621                                                 continue;
2622                                         }
2623
2624                                         // Try wrapping the element in a DIV
2625                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2626                                                 node.wrap(self.filterNode(new Node('div', 1)));
2627                                         } else {
2628                                                 // We failed wrapping it, then remove or unwrap it
2629                                                 if (node.name === 'style' || node.name === 'script')
2630                                                         node.empty().remove();
2631                                                 else
2632                                                         node.unwrap();
2633                                         }
2634                                 }
2635                         }
2636                 };
2637
2638                 self.filterNode = function(node) {
2639                         var i, name, list;
2640
2641                         // Run element filters
2642                         if (name in nodeFilters) {
2643                                 list = matchedNodes[name];
2644
2645                                 if (list)
2646                                         list.push(node);
2647                                 else
2648                                         matchedNodes[name] = [node];
2649                         }
2650
2651                         // Run attribute filters
2652                         i = attributeFilters.length;
2653                         while (i--) {
2654                                 name = attributeFilters[i].name;
2655
2656                                 if (name in node.attributes.map) {
2657                                         list = matchedAttributes[name];
2658
2659                                         if (list)
2660                                                 list.push(node);
2661                                         else
2662                                                 matchedAttributes[name] = [node];
2663                                 }
2664                         }
2665
2666                         return node;
2667                 };
2668
2669                 self.addNodeFilter = function(name, callback) {
2670                         tinymce.each(tinymce.explode(name), function(name) {
2671                                 var list = nodeFilters[name];
2672
2673                                 if (!list)
2674                                         nodeFilters[name] = list = [];
2675
2676                                 list.push(callback);
2677                         });
2678                 };
2679
2680                 self.addAttributeFilter = function(name, callback) {
2681                         tinymce.each(tinymce.explode(name), function(name) {
2682                                 var i;
2683
2684                                 for (i = 0; i < attributeFilters.length; i++) {
2685                                         if (attributeFilters[i].name === name) {
2686                                                 attributeFilters[i].callbacks.push(callback);
2687                                                 return;
2688                                         }
2689                                 }
2690
2691                                 attributeFilters.push({name: name, callbacks: [callback]});
2692                         });
2693                 };
2694
2695                 self.parse = function(html, args) {
2696                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
2697                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
2698                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
2699
2700                         args = args || {};
2701                         matchedNodes = {};
2702                         matchedAttributes = {};
2703                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
2704                         nonEmptyElements = schema.getNonEmptyElements();
2705                         children = schema.children;
2706                         validate = settings.validate;
2707
2708                         whiteSpaceElements = schema.getWhiteSpaceElements();
2709                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;
2710                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;
2711                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;
2712
2713                         function createNode(name, type) {
2714                                 var node = new Node(name, type), list;
2715
2716                                 if (name in nodeFilters) {
2717                                         list = matchedNodes[name];
2718
2719                                         if (list)
2720                                                 list.push(node);
2721                                         else
2722                                                 matchedNodes[name] = [node];
2723                                 }
2724
2725                                 return node;
2726                         };
2727
2728                         function removeWhitespaceBefore(node) {
2729                                 var textNode, textVal, sibling;
2730
2731                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
2732                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
2733
2734                                         if (textVal.length > 0) {
2735                                                 textNode.value = textVal;
2736                                                 textNode = textNode.prev;
2737                                         } else {
2738                                                 sibling = textNode.prev;
2739                                                 textNode.remove();
2740                                                 textNode = sibling;
2741                                         }
2742                                 }
2743                         };
2744
2745                         parser = new tinymce.html.SaxParser({
2746                                 validate : validate,
2747                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
2748
2749                                 cdata: function(text) {
2750                                         node.append(createNode('#cdata', 4)).value = text;
2751                                 },
2752
2753                                 text: function(text, raw) {
2754                                         var textNode;
2755
2756                                         // Trim all redundant whitespace on non white space elements
2757                                         if (!whiteSpaceElements[node.name]) {
2758                                                 text = text.replace(allWhiteSpaceRegExp, ' ');
2759
2760                                                 if (node.lastChild && blockElements[node.lastChild.name])
2761                                                         text = text.replace(startWhiteSpaceRegExp, '');
2762                                         }
2763
2764                                         // Do we need to create the node
2765                                         if (text.length !== 0) {
2766                                                 textNode = createNode('#text', 3);
2767                                                 textNode.raw = !!raw;
2768                                                 node.append(textNode).value = text;
2769                                         }
2770                                 },
2771
2772                                 comment: function(text) {
2773                                         node.append(createNode('#comment', 8)).value = text;
2774                                 },
2775
2776                                 pi: function(name, text) {
2777                                         node.append(createNode(name, 7)).value = text;
2778                                         removeWhitespaceBefore(node);
2779                                 },
2780
2781                                 doctype: function(text) {
2782                                         var newNode;
2783                 
2784                                         newNode = node.append(createNode('#doctype', 10));
2785                                         newNode.value = text;
2786                                         removeWhitespaceBefore(node);
2787                                 },
2788
2789                                 start: function(name, attrs, empty) {
2790                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
2791
2792                                         elementRule = validate ? schema.getElementRule(name) : {};
2793                                         if (elementRule) {
2794                                                 newNode = createNode(elementRule.outputName || name, 1);
2795                                                 newNode.attributes = attrs;
2796                                                 newNode.shortEnded = empty;
2797
2798                                                 node.append(newNode);
2799
2800                                                 // Check if node is valid child of the parent node is the child is
2801                                                 // unknown we don't collect it since it's probably a custom element
2802                                                 parent = children[node.name];
2803                                                 if (parent && children[newNode.name] && !parent[newNode.name])
2804                                                         invalidChildren.push(newNode);
2805
2806                                                 attrFiltersLen = attributeFilters.length;
2807                                                 while (attrFiltersLen--) {
2808                                                         attrName = attributeFilters[attrFiltersLen].name;
2809
2810                                                         if (attrName in attrs.map) {
2811                                                                 list = matchedAttributes[attrName];
2812
2813                                                                 if (list)
2814                                                                         list.push(newNode);
2815                                                                 else
2816                                                                         matchedAttributes[attrName] = [newNode];
2817                                                         }
2818                                                 }
2819
2820                                                 // Trim whitespace before block
2821                                                 if (blockElements[name])
2822                                                         removeWhitespaceBefore(newNode);
2823
2824                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />
2825                                                 if (!empty)
2826                                                         node = newNode;
2827                                         }
2828                                 },
2829
2830                                 end: function(name) {
2831                                         var textNode, elementRule, text, sibling, tempNode;
2832
2833                                         elementRule = validate ? schema.getElementRule(name) : {};
2834                                         if (elementRule) {
2835                                                 if (blockElements[name]) {
2836                                                         if (!whiteSpaceElements[node.name]) {
2837                                                                 // Trim whitespace at beginning of block
2838                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
2839                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');
2840
2841                                                                         if (text.length > 0) {
2842                                                                                 textNode.value = text;
2843                                                                                 textNode = textNode.next;
2844                                                                         } else {
2845                                                                                 sibling = textNode.next;
2846                                                                                 textNode.remove();
2847                                                                                 textNode = sibling;
2848                                                                         }
2849                                                                 }
2850
2851                                                                 // Trim whitespace at end of block
2852                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
2853                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');
2854
2855                                                                         if (text.length > 0) {
2856                                                                                 textNode.value = text;
2857                                                                                 textNode = textNode.prev;
2858                                                                         } else {
2859                                                                                 sibling = textNode.prev;
2860                                                                                 textNode.remove();
2861                                                                                 textNode = sibling;
2862                                                                         }
2863                                                                 }
2864                                                         }
2865
2866                                                         // Trim start white space
2867                                                         textNode = node.prev;
2868                                                         if (textNode && textNode.type === 3) {
2869                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
2870
2871                                                                 if (text.length > 0)
2872                                                                         textNode.value = text;
2873                                                                 else
2874                                                                         textNode.remove();
2875                                                         }
2876                                                 }
2877
2878                                                 // Handle empty nodes
2879                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {
2880                                                         if (node.isEmpty(nonEmptyElements)) {
2881                                                                 if (elementRule.paddEmpty)
2882                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';
2883                                                                 else {
2884                                                                         // Leave nodes that have a name like <a name="name">
2885                                                                         if (!node.attributes.map.name) {
2886                                                                                 tempNode = node.parent;
2887                                                                                 node.empty().remove();
2888                                                                                 node = tempNode;
2889                                                                                 return;
2890                                                                         }
2891                                                                 }
2892                                                         }
2893                                                 }
2894
2895                                                 node = node.parent;
2896                                         }
2897                                 }
2898                         }, schema);
2899
2900                         rootNode = node = new Node(settings.root_name, 11);
2901
2902                         parser.parse(html);
2903
2904                         if (validate)
2905                                 fixInvalidChildren(invalidChildren);
2906
2907                         // Run node filters
2908                         for (name in matchedNodes) {
2909                                 list = nodeFilters[name];
2910                                 nodes = matchedNodes[name];
2911
2912                                 // Remove already removed children
2913                                 fi = nodes.length;
2914                                 while (fi--) {
2915                                         if (!nodes[fi].parent)
2916                                                 nodes.splice(fi, 1);
2917                                 }
2918
2919                                 for (i = 0, l = list.length; i < l; i++)
2920                                         list[i](nodes, name, args);
2921                         }
2922
2923                         // Run attribute filters
2924                         for (i = 0, l = attributeFilters.length; i < l; i++) {
2925                                 list = attributeFilters[i];
2926
2927                                 if (list.name in matchedAttributes) {
2928                                         nodes = matchedAttributes[list.name];
2929
2930                                         // Remove already removed children
2931                                         fi = nodes.length;
2932                                         while (fi--) {
2933                                                 if (!nodes[fi].parent)
2934                                                         nodes.splice(fi, 1);
2935                                         }
2936
2937                                         for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
2938                                                 list.callbacks[fi](nodes, list.name, args);
2939                                 }
2940                         }
2941
2942                         return rootNode;
2943                 };
2944
2945                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
2946                 // make it possible to place the caret inside empty blocks. This logic tries to remove
2947                 // these elements and keep br elements that where intended to be there intact
2948                 if (settings.remove_trailing_brs) {
2949                         self.addNodeFilter('br', function(nodes, name) {
2950                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
2951                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
2952
2953                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
2954                                 for (i = 0; i < l; i++) {
2955                                         node = nodes[i];
2956                                         parent = node.parent;
2957
2958                                         if (blockElements[node.parent.name] && node === parent.lastChild) {
2959                                                 // Loop all nodes to the right of the current node and check for other BR elements
2960                                                 // excluding bookmarks since they are invisible
2961                                                 prev = node.prev;
2962                                                 while (prev) {
2963                                                         prevName = prev.name;
2964
2965                                                         // Ignore bookmarks
2966                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
2967                                                                 // Found a non BR element
2968                                                                 if (prevName !== "br")
2969                                                                         break;
2970         
2971                                                                 // Found another br it's a <br><br> structure then don't remove anything
2972                                                                 if (prevName === 'br') {
2973                                                                         node = null;
2974                                                                         break;
2975                                                                 }
2976                                                         }
2977
2978                                                         prev = prev.prev;
2979                                                 }
2980
2981                                                 if (node) {
2982                                                         node.remove();
2983
2984                                                         // Is the parent to be considered empty after we removed the BR
2985                                                         if (parent.isEmpty(nonEmptyElements)) {
2986                                                                 elementRule = schema.getElementRule(parent.name);
2987
2988                                                                 // Remove or padd the element depending on schema rule
2989                                                                 if (elementRule.removeEmpty)
2990                                                                         parent.remove();
2991                                                                 else if (elementRule.paddEmpty) 
2992                                                                         parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
2993                                                         }
2994                                                 }
2995                                         }
2996                                 }
2997                         });
2998                 }
2999         }
3000 })(tinymce);
3001
3002 tinymce.html.Writer = function(settings) {
3003         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3004
3005         settings = settings || {};
3006         indent = settings.indent;
3007         indentBefore = tinymce.makeMap(settings.indent_before || '');
3008         indentAfter = tinymce.makeMap(settings.indent_after || '');
3009         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3010         htmlOutput = settings.element_format == "html";
3011
3012         return {
3013                 start: function(name, attrs, empty) {
3014                         var i, l, attr, value;
3015
3016                         if (indent && indentBefore[name] && html.length > 0) {
3017                                 value = html[html.length - 1];
3018
3019                                 if (value.length > 0 && value !== '\n')
3020                                         html.push('\n');
3021                         }
3022
3023                         html.push('<', name);
3024
3025                         if (attrs) {
3026                                 for (i = 0, l = attrs.length; i < l; i++) {
3027                                         attr = attrs[i];
3028                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3029                                 }
3030                         }
3031
3032                         if (!empty || htmlOutput)
3033                                 html[html.length] = '>';
3034                         else
3035                                 html[html.length] = ' />';
3036
3037                         if (empty && indent && indentAfter[name] && html.length > 0) {
3038                                 value = html[html.length - 1];
3039
3040                                 if (value.length > 0 && value !== '\n')
3041                                         html.push('\n');
3042                         }
3043                 },
3044
3045                 end: function(name) {
3046                         var value;
3047
3048                         /*if (indent && indentBefore[name] && html.length > 0) {
3049                                 value = html[html.length - 1];
3050
3051                                 if (value.length > 0 && value !== '\n')
3052                                         html.push('\n');
3053                         }*/
3054
3055                         html.push('</', name, '>');
3056
3057                         if (indent && indentAfter[name] && html.length > 0) {
3058                                 value = html[html.length - 1];
3059
3060                                 if (value.length > 0 && value !== '\n')
3061                                         html.push('\n');
3062                         }
3063                 },
3064
3065                 text: function(text, raw) {
3066                         if (text.length > 0)
3067                                 html[html.length] = raw ? text : encode(text);
3068                 },
3069
3070                 cdata: function(text) {
3071                         html.push('<![CDATA[', text, ']]>');
3072                 },
3073
3074                 comment: function(text) {
3075                         html.push('<!--', text, '-->');
3076                 },
3077
3078                 pi: function(name, text) {
3079                         if (text)
3080                                 html.push('<?', name, ' ', text, '?>');
3081                         else
3082                                 html.push('<?', name, '?>');
3083
3084                         if (indent)
3085                                 html.push('\n');
3086                 },
3087
3088                 doctype: function(text) {
3089                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3090                 },
3091
3092                 reset: function() {
3093                         html.length = 0;
3094                 },
3095
3096                 getContent: function() {
3097                         return html.join('').replace(/\n$/, '');
3098                 }
3099         };
3100 };
3101
3102 (function(tinymce) {
3103         tinymce.html.Serializer = function(settings, schema) {
3104                 var self = this, writer = new tinymce.html.Writer(settings);
3105
3106                 settings = settings || {};
3107                 settings.validate = "validate" in settings ? settings.validate : true;
3108
3109                 self.schema = schema = schema || new tinymce.html.Schema();
3110                 self.writer = writer;
3111
3112                 self.serialize = function(node) {
3113                         var handlers, validate;
3114
3115                         validate = settings.validate;
3116
3117                         handlers = {
3118                                 // #text
3119                                 3: function(node, raw) {
3120                                         writer.text(node.value, node.raw);
3121                                 },
3122
3123                                 // #comment
3124                                 8: function(node) {
3125                                         writer.comment(node.value);
3126                                 },
3127
3128                                 // Processing instruction
3129                                 7: function(node) {
3130                                         writer.pi(node.name, node.value);
3131                                 },
3132
3133                                 // Doctype
3134                                 10: function(node) {
3135                                         writer.doctype(node.value);
3136                                 },
3137
3138                                 // CDATA
3139                                 4: function(node) {
3140                                         writer.cdata(node.value);
3141                                 },
3142
3143                                 // Document fragment
3144                                 11: function(node) {
3145                                         if ((node = node.firstChild)) {
3146                                                 do {
3147                                                         walk(node);
3148                                                 } while (node = node.next);
3149                                         }
3150                                 }
3151                         };
3152
3153                         writer.reset();
3154
3155                         function walk(node) {
3156                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3157
3158                                 if (!handler) {
3159                                         name = node.name;
3160                                         isEmpty = node.shortEnded;
3161                                         attrs = node.attributes;
3162
3163                                         // Sort attributes
3164                                         if (validate && attrs && attrs.length > 1) {
3165                                                 sortedAttrs = [];
3166                                                 sortedAttrs.map = {};
3167
3168                                                 elementRule = schema.getElementRule(node.name);
3169                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3170                                                         attrName = elementRule.attributesOrder[i];
3171
3172                                                         if (attrName in attrs.map) {
3173                                                                 attrValue = attrs.map[attrName];
3174                                                                 sortedAttrs.map[attrName] = attrValue;
3175                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3176                                                         }
3177                                                 }
3178
3179                                                 for (i = 0, l = attrs.length; i < l; i++) {
3180                                                         attrName = attrs[i].name;
3181
3182                                                         if (!(attrName in sortedAttrs.map)) {
3183                                                                 attrValue = attrs.map[attrName];
3184                                                                 sortedAttrs.map[attrName] = attrValue;
3185                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3186                                                         }
3187                                                 }
3188
3189                                                 attrs = sortedAttrs;
3190                                         }
3191
3192                                         writer.start(node.name, attrs, isEmpty);
3193
3194                                         if (!isEmpty) {
3195                                                 if ((node = node.firstChild)) {
3196                                                         do {
3197                                                                 walk(node);
3198                                                         } while (node = node.next);
3199                                                 }
3200
3201                                                 writer.end(name);
3202                                         }
3203                                 } else
3204                                         handler(node);
3205                         }
3206
3207                         // Serialize element and treat all non elements as fragments
3208                         if (node.type == 1 && !settings.inner)
3209                                 walk(node);
3210                         else
3211                                 handlers[11](node);
3212
3213                         return writer.getContent();
3214                 };
3215         }
3216 })(tinymce);
3217
3218 (function(tinymce) {
3219         // Shorten names
3220         var each = tinymce.each,
3221                 is = tinymce.is,
3222                 isWebKit = tinymce.isWebKit,
3223                 isIE = tinymce.isIE,
3224                 Entities = tinymce.html.Entities,
3225                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
3226                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
3227                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
3228
3229         tinymce.create('tinymce.dom.DOMUtils', {
3230                 doc : null,
3231                 root : null,
3232                 files : null,
3233                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3234                 props : {
3235                         "for" : "htmlFor",
3236                         "class" : "className",
3237                         className : "className",
3238                         checked : "checked",
3239                         disabled : "disabled",
3240                         maxlength : "maxLength",
3241                         readonly : "readOnly",
3242                         selected : "selected",
3243                         value : "value",
3244                         id : "id",
3245                         name : "name",
3246                         type : "type"
3247                 },
3248
3249                 DOMUtils : function(d, s) {
3250                         var t = this, globalStyle;
3251
3252                         t.doc = d;
3253                         t.win = window;
3254                         t.files = {};
3255                         t.cssFlicker = false;
3256                         t.counter = 0;
3257                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3258                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3259                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
3260
3261                         t.settings = s = tinymce.extend({
3262                                 keep_values : false,
3263                                 hex_colors : 1
3264                         }, s);
3265                         
3266                         t.schema = s.schema;
3267                         t.styles = new tinymce.html.Styles({
3268                                 url_converter : s.url_converter,
3269                                 url_converter_scope : s.url_converter_scope
3270                         }, s.schema);
3271
3272                         // Fix IE6SP2 flicker and check it failed for pre SP2
3273                         if (tinymce.isIE6) {
3274                                 try {
3275                                         d.execCommand('BackgroundImageCache', false, true);
3276                                 } catch (e) {
3277                                         t.cssFlicker = true;
3278                                 }
3279                         }
3280
3281                         if (isIE) {
3282                                 // Add missing HTML 4/5 elements to IE
3283                                 ('abbr article aside audio canvas ' +
3284                                 'details figcaption figure footer ' +
3285                                 'header hgroup mark menu meter nav ' +
3286                                 'output progress section summary ' +
3287                                 'time video').replace(/\w+/g, function(name) {
3288                                         d.createElement(name);
3289                                 });
3290                         }
3291
3292                         tinymce.addUnload(t.destroy, t);
3293                 },
3294
3295                 getRoot : function() {
3296                         var t = this, s = t.settings;
3297
3298                         return (s && t.get(s.root_element)) || t.doc.body;
3299                 },
3300
3301                 getViewPort : function(w) {
3302                         var d, b;
3303
3304                         w = !w ? this.win : w;
3305                         d = w.document;
3306                         b = this.boxModel ? d.documentElement : d.body;
3307
3308                         // Returns viewport size excluding scrollbars
3309                         return {
3310                                 x : w.pageXOffset || b.scrollLeft,
3311                                 y : w.pageYOffset || b.scrollTop,
3312                                 w : w.innerWidth || b.clientWidth,
3313                                 h : w.innerHeight || b.clientHeight
3314                         };
3315                 },
3316
3317                 getRect : function(e) {
3318                         var p, t = this, sr;
3319
3320                         e = t.get(e);
3321                         p = t.getPos(e);
3322                         sr = t.getSize(e);
3323
3324                         return {
3325                                 x : p.x,
3326                                 y : p.y,
3327                                 w : sr.w,
3328                                 h : sr.h
3329                         };
3330                 },
3331
3332                 getSize : function(e) {
3333                         var t = this, w, h;
3334
3335                         e = t.get(e);
3336                         w = t.getStyle(e, 'width');
3337                         h = t.getStyle(e, 'height');
3338
3339                         // Non pixel value, then force offset/clientWidth
3340                         if (w.indexOf('px') === -1)
3341                                 w = 0;
3342
3343                         // Non pixel value, then force offset/clientWidth
3344                         if (h.indexOf('px') === -1)
3345                                 h = 0;
3346
3347                         return {
3348                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3349                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
3350                         };
3351                 },
3352
3353                 getParent : function(n, f, r) {
3354                         return this.getParents(n, f, r, false);
3355                 },
3356
3357                 getParents : function(n, f, r, c) {
3358                         var t = this, na, se = t.settings, o = [];
3359
3360                         n = t.get(n);
3361                         c = c === undefined;
3362
3363                         if (se.strict_root)
3364                                 r = r || t.getRoot();
3365
3366                         // Wrap node name as func
3367                         if (is(f, 'string')) {
3368                                 na = f;
3369
3370                                 if (f === '*') {
3371                                         f = function(n) {return n.nodeType == 1;};
3372                                 } else {
3373                                         f = function(n) {
3374                                                 return t.is(n, na);
3375                                         };
3376                                 }
3377                         }
3378
3379                         while (n) {
3380                                 if (n == r || !n.nodeType || n.nodeType === 9)
3381                                         break;
3382
3383                                 if (!f || f(n)) {
3384                                         if (c)
3385                                                 o.push(n);
3386                                         else
3387                                                 return n;
3388                                 }
3389
3390                                 n = n.parentNode;
3391                         }
3392
3393                         return c ? o : null;
3394                 },
3395
3396                 get : function(e) {
3397                         var n;
3398
3399                         if (e && this.doc && typeof(e) == 'string') {
3400                                 n = e;
3401                                 e = this.doc.getElementById(e);
3402
3403                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3404                                 if (e && e.id !== n)
3405                                         return this.doc.getElementsByName(n)[1];
3406                         }
3407
3408                         return e;
3409                 },
3410
3411                 getNext : function(node, selector) {
3412                         return this._findSib(node, selector, 'nextSibling');
3413                 },
3414
3415                 getPrev : function(node, selector) {
3416                         return this._findSib(node, selector, 'previousSibling');
3417                 },
3418
3419
3420                 select : function(pa, s) {
3421                         var t = this;
3422
3423                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
3424                 },
3425
3426                 is : function(n, selector) {
3427                         var i;
3428
3429                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
3430                         if (n.length === undefined) {
3431                                 // Simple all selector
3432                                 if (selector === '*')
3433                                         return n.nodeType == 1;
3434
3435                                 // Simple selector just elements
3436                                 if (simpleSelectorRe.test(selector)) {
3437                                         selector = selector.toLowerCase().split(/,/);
3438                                         n = n.nodeName.toLowerCase();
3439
3440                                         for (i = selector.length - 1; i >= 0; i--) {
3441                                                 if (selector[i] == n)
3442                                                         return true;
3443                                         }
3444
3445                                         return false;
3446                                 }
3447                         }
3448
3449                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
3450                 },
3451
3452
3453                 add : function(p, n, a, h, c) {
3454                         var t = this;
3455
3456                         return this.run(p, function(p) {
3457                                 var e, k;
3458
3459                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
3460                                 t.setAttribs(e, a);
3461
3462                                 if (h) {
3463                                         if (h.nodeType)
3464                                                 e.appendChild(h);
3465                                         else
3466                                                 t.setHTML(e, h);
3467                                 }
3468
3469                                 return !c ? p.appendChild(e) : e;
3470                         });
3471                 },
3472
3473                 create : function(n, a, h) {
3474                         return this.add(this.doc.createElement(n), n, a, h, 1);
3475                 },
3476
3477                 createHTML : function(n, a, h) {
3478                         var o = '', t = this, k;
3479
3480                         o += '<' + n;
3481
3482                         for (k in a) {
3483                                 if (a.hasOwnProperty(k))
3484                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
3485                         }
3486
3487                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3488                         if (typeof(h) != "undefined")
3489                                 return o + '>' + h + '</' + n + '>';
3490
3491                         return o + ' />';
3492                 },
3493
3494                 remove : function(node, keep_children) {
3495                         return this.run(node, function(node) {
3496                                 var child, parent = node.parentNode;
3497
3498                                 if (!parent)
3499                                         return null;
3500
3501                                 if (keep_children) {
3502                                         while (child = node.firstChild) {
3503                                                 // IE 8 will crash if you don't remove completely empty text nodes
3504                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3505                                                         parent.insertBefore(child, node);
3506                                                 else
3507                                                         node.removeChild(child);
3508                                         }
3509                                 }
3510
3511                                 return parent.removeChild(node);
3512                         });
3513                 },
3514
3515                 setStyle : function(n, na, v) {
3516                         var t = this;
3517
3518                         return t.run(n, function(e) {
3519                                 var s, i;
3520
3521                                 s = e.style;
3522
3523                                 // Camelcase it, if needed
3524                                 na = na.replace(/-(\D)/g, function(a, b){
3525                                         return b.toUpperCase();
3526                                 });
3527
3528                                 // Default px suffix on these
3529                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3530                                         v += 'px';
3531
3532                                 switch (na) {
3533                                         case 'opacity':
3534                                                 // IE specific opacity
3535                                                 if (isIE) {
3536                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3537
3538                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
3539                                                                 s.display = 'inline-block';
3540                                                 }
3541
3542                                                 // Fix for older browsers
3543                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3544                                                 break;
3545
3546                                         case 'float':
3547                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
3548                                                 break;
3549                                         
3550                                         default:
3551                                                 s[na] = v || '';
3552                                 }
3553
3554                                 // Force update of the style data
3555                                 if (t.settings.update_styles)
3556                                         t.setAttrib(e, 'data-mce-style');
3557                         });
3558                 },
3559
3560                 getStyle : function(n, na, c) {
3561                         n = this.get(n);
3562
3563                         if (!n)
3564                                 return;
3565
3566                         // Gecko
3567                         if (this.doc.defaultView && c) {
3568                                 // Remove camelcase
3569                                 na = na.replace(/[A-Z]/g, function(a){
3570                                         return '-' + a;
3571                                 });
3572
3573                                 try {
3574                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3575                                 } catch (ex) {
3576                                         // Old safari might fail
3577                                         return null;
3578                                 }
3579                         }
3580
3581                         // Camelcase it, if needed
3582                         na = na.replace(/-(\D)/g, function(a, b){
3583                                 return b.toUpperCase();
3584                         });
3585
3586                         if (na == 'float')
3587                                 na = isIE ? 'styleFloat' : 'cssFloat';
3588
3589                         // IE & Opera
3590                         if (n.currentStyle && c)
3591                                 return n.currentStyle[na];
3592
3593                         return n.style ? n.style[na] : undefined;
3594                 },
3595
3596                 setStyles : function(e, o) {
3597                         var t = this, s = t.settings, ol;
3598
3599                         ol = s.update_styles;
3600                         s.update_styles = 0;
3601
3602                         each(o, function(v, n) {
3603                                 t.setStyle(e, n, v);
3604                         });
3605
3606                         // Update style info
3607                         s.update_styles = ol;
3608                         if (s.update_styles)
3609                                 t.setAttrib(e, s.cssText);
3610                 },
3611
3612                 removeAllAttribs: function(e) {
3613                         return this.run(e, function(e) {
3614                                 var i, attrs = e.attributes;
3615                                 for (i = attrs.length - 1; i >= 0; i--) {
3616                                         e.removeAttributeNode(attrs.item(i));
3617                                 }
3618                         });
3619                 },
3620
3621                 setAttrib : function(e, n, v) {
3622                         var t = this;
3623
3624                         // Whats the point
3625                         if (!e || !n)
3626                                 return;
3627
3628                         // Strict XML mode
3629                         if (t.settings.strict)
3630                                 n = n.toLowerCase();
3631
3632                         return this.run(e, function(e) {
3633                                 var s = t.settings;
3634
3635                                 switch (n) {
3636                                         case "style":
3637                                                 if (!is(v, 'string')) {
3638                                                         each(v, function(v, n) {
3639                                                                 t.setStyle(e, n, v);
3640                                                         });
3641
3642                                                         return;
3643                                                 }
3644
3645                                                 // No mce_style for elements with these since they might get resized by the user
3646                                                 if (s.keep_values) {
3647                                                         if (v && !t._isRes(v))
3648                                                                 e.setAttribute('data-mce-style', v, 2);
3649                                                         else
3650                                                                 e.removeAttribute('data-mce-style', 2);
3651                                                 }
3652
3653                                                 e.style.cssText = v;
3654                                                 break;
3655
3656                                         case "class":
3657                                                 e.className = v || ''; // Fix IE null bug
3658                                                 break;
3659
3660                                         case "src":
3661                                         case "href":
3662                                                 if (s.keep_values) {
3663                                                         if (s.url_converter)
3664                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3665
3666                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
3667                                                 }
3668
3669                                                 break;
3670
3671                                         case "shape":
3672                                                 e.setAttribute('data-mce-style', v);
3673                                                 break;
3674                                 }
3675
3676                                 if (is(v) && v !== null && v.length !== 0)
3677                                         e.setAttribute(n, '' + v, 2);
3678                                 else
3679                                         e.removeAttribute(n, 2);
3680                         });
3681                 },
3682
3683                 setAttribs : function(e, o) {
3684                         var t = this;
3685
3686                         return this.run(e, function(e) {
3687                                 each(o, function(v, n) {
3688                                         t.setAttrib(e, n, v);
3689                                 });
3690                         });
3691                 },
3692
3693                 getAttrib : function(e, n, dv) {
3694                         var v, t = this;
3695
3696                         e = t.get(e);
3697
3698                         if (!e || e.nodeType !== 1)
3699                                 return false;
3700
3701                         if (!is(dv))
3702                                 dv = '';
3703
3704                         // Try the mce variant for these
3705                         if (/^(src|href|style|coords|shape)$/.test(n)) {
3706                                 v = e.getAttribute("data-mce-" + n);
3707
3708                                 if (v)
3709                                         return v;
3710                         }
3711
3712                         if (isIE && t.props[n]) {
3713                                 v = e[t.props[n]];
3714                                 v = v && v.nodeValue ? v.nodeValue : v;
3715                         }
3716
3717                         if (!v)
3718                                 v = e.getAttribute(n, 2);
3719
3720                         // Check boolean attribs
3721                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
3722                                 if (e[t.props[n]] === true && v === '')
3723                                         return n;
3724
3725                                 return v ? n : '';
3726                         }
3727
3728                         // Inner input elements will override attributes on form elements
3729                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
3730                                 return e.getAttributeNode(n).nodeValue;
3731
3732                         if (n === 'style') {
3733                                 v = v || e.style.cssText;
3734
3735                                 if (v) {
3736                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
3737
3738                                         if (t.settings.keep_values && !t._isRes(v))
3739                                                 e.setAttribute('data-mce-style', v);
3740                                 }
3741                         }
3742
3743                         // Remove Apple and WebKit stuff
3744                         if (isWebKit && n === "class" && v)
3745                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3746
3747                         // Handle IE issues
3748                         if (isIE) {
3749                                 switch (n) {
3750                                         case 'rowspan':
3751                                         case 'colspan':
3752                                                 // IE returns 1 as default value
3753                                                 if (v === 1)
3754                                                         v = '';
3755
3756                                                 break;
3757
3758                                         case 'size':
3759                                                 // IE returns +0 as default value for size
3760                                                 if (v === '+0' || v === 20 || v === 0)
3761                                                         v = '';
3762
3763                                                 break;
3764
3765                                         case 'width':
3766                                         case 'height':
3767                                         case 'vspace':
3768                                         case 'checked':
3769                                         case 'disabled':
3770                                         case 'readonly':
3771                                                 if (v === 0)
3772                                                         v = '';
3773
3774                                                 break;
3775
3776                                         case 'hspace':
3777                                                 // IE returns -1 as default value
3778                                                 if (v === -1)
3779                                                         v = '';
3780
3781                                                 break;
3782
3783                                         case 'maxlength':
3784                                         case 'tabindex':
3785                                                 // IE returns default value
3786                                                 if (v === 32768 || v === 2147483647 || v === '32768')
3787                                                         v = '';
3788
3789                                                 break;
3790
3791                                         case 'multiple':
3792                                         case 'compact':
3793                                         case 'noshade':
3794                                         case 'nowrap':
3795                                                 if (v === 65535)
3796                                                         return n;
3797
3798                                                 return dv;
3799
3800                                         case 'shape':
3801                                                 v = v.toLowerCase();
3802                                                 break;
3803
3804                                         default:
3805                                                 // IE has odd anonymous function for event attributes
3806                                                 if (n.indexOf('on') === 0 && v)
3807                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
3808                                 }
3809                         }
3810
3811                         return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
3812                 },
3813
3814                 getPos : function(n, ro) {
3815                         var t = this, x = 0, y = 0, e, d = t.doc, r;
3816
3817                         n = t.get(n);
3818                         ro = ro || d.body;
3819
3820                         if (n) {
3821                                 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
3822                                 if (isIE && !t.stdMode) {
3823                                         n = n.getBoundingClientRect();
3824                                         e = t.boxModel ? d.documentElement : d.body;
3825                                         x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
3826                                         x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
3827
3828                                         return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
3829                                 }
3830
3831                                 r = n;
3832                                 while (r && r != ro && r.nodeType) {
3833                                         x += r.offsetLeft || 0;
3834                                         y += r.offsetTop || 0;
3835                                         r = r.offsetParent;
3836                                 }
3837
3838                                 r = n.parentNode;
3839                                 while (r && r != ro && r.nodeType) {
3840                                         x -= r.scrollLeft || 0;
3841                                         y -= r.scrollTop || 0;
3842                                         r = r.parentNode;
3843                                 }
3844                         }
3845
3846                         return {x : x, y : y};
3847                 },
3848
3849                 parseStyle : function(st) {
3850                         return this.styles.parse(st);
3851                 },
3852
3853                 serializeStyle : function(o, name) {
3854                         return this.styles.serialize(o, name);
3855                 },
3856
3857                 loadCSS : function(u) {
3858                         var t = this, d = t.doc, head;
3859
3860                         if (!u)
3861                                 u = '';
3862
3863                         head = t.select('head')[0];
3864
3865                         each(u.split(','), function(u) {
3866                                 var link;
3867
3868                                 if (t.files[u])
3869                                         return;
3870
3871                                 t.files[u] = true;
3872                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
3873
3874                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
3875                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
3876                                 // It's ugly but it seems to work fine.
3877                                 if (isIE && d.documentMode && d.recalc) {
3878                                         link.onload = function() {
3879                                                 if (d.recalc)
3880                                                         d.recalc();
3881
3882                                                 link.onload = null;
3883                                         };
3884                                 }
3885
3886                                 head.appendChild(link);
3887                         });
3888                 },
3889
3890                 addClass : function(e, c) {
3891                         return this.run(e, function(e) {
3892                                 var o;
3893
3894                                 if (!c)
3895                                         return 0;
3896
3897                                 if (this.hasClass(e, c))
3898                                         return e.className;
3899
3900                                 o = this.removeClass(e, c);
3901
3902                                 return e.className = (o != '' ? (o + ' ') : '') + c;
3903                         });
3904                 },
3905
3906                 removeClass : function(e, c) {
3907                         var t = this, re;
3908
3909                         return t.run(e, function(e) {
3910                                 var v;
3911
3912                                 if (t.hasClass(e, c)) {
3913                                         if (!re)
3914                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
3915
3916                                         v = e.className.replace(re, ' ');
3917                                         v = tinymce.trim(v != ' ' ? v : '');
3918
3919                                         e.className = v;
3920
3921                                         // Empty class attr
3922                                         if (!v) {
3923                                                 e.removeAttribute('class');
3924                                                 e.removeAttribute('className');
3925                                         }
3926
3927                                         return v;
3928                                 }
3929
3930                                 return e.className;
3931                         });
3932                 },
3933
3934                 hasClass : function(n, c) {
3935                         n = this.get(n);
3936
3937                         if (!n || !c)
3938                                 return false;
3939
3940                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
3941                 },
3942
3943                 show : function(e) {
3944                         return this.setStyle(e, 'display', 'block');
3945                 },
3946
3947                 hide : function(e) {
3948                         return this.setStyle(e, 'display', 'none');
3949                 },
3950
3951                 isHidden : function(e) {
3952                         e = this.get(e);
3953
3954                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
3955                 },
3956
3957                 uniqueId : function(p) {
3958                         return (!p ? 'mce_' : p) + (this.counter++);
3959                 },
3960
3961                 setHTML : function(element, html) {
3962                         var self = this;
3963
3964                         return self.run(element, function(element) {
3965                                 if (isIE) {
3966                                         // Remove all child nodes, IE keeps empty text nodes in DOM
3967                                         while (element.firstChild)
3968                                                 element.removeChild(element.firstChild);
3969
3970                                         try {
3971                                                 // IE will remove comments from the beginning
3972                                                 // unless you padd the contents with something
3973                                                 element.innerHTML = '<br />' + html;
3974                                                 element.removeChild(element.firstChild);
3975                                         } catch (ex) {
3976                                                 // 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
3977                                                 // This seems to fix this problem
3978
3979                                                 // Create new div with HTML contents and a BR infront to keep comments
3980                                                 element = self.create('div');
3981                                                 element.innerHTML = '<br />' + html;
3982
3983                                                 // Add all children from div to target
3984                                                 each (element.childNodes, function(node, i) {
3985                                                         // Skip br element
3986                                                         if (i)
3987                                                                 element.appendChild(node);
3988                                                 });
3989                                         }
3990                                 } else
3991                                         element.innerHTML = html;
3992
3993                                 return html;
3994                         });
3995                 },
3996
3997                 getOuterHTML : function(elm) {
3998                         var doc, self = this;
3999
4000                         elm = self.get(elm);
4001
4002                         if (!elm)
4003                                 return null;
4004
4005                         if (elm.nodeType === 1 && self.hasOuterHTML)
4006                                 return elm.outerHTML;
4007
4008                         doc = (elm.ownerDocument || self.doc).createElement("body");
4009                         doc.appendChild(elm.cloneNode(true));
4010
4011                         return doc.innerHTML;
4012                 },
4013
4014                 setOuterHTML : function(e, h, d) {
4015                         var t = this;
4016
4017                         function setHTML(e, h, d) {
4018                                 var n, tp;
4019
4020                                 tp = d.createElement("body");
4021                                 tp.innerHTML = h;
4022
4023                                 n = tp.lastChild;
4024                                 while (n) {
4025                                         t.insertAfter(n.cloneNode(true), e);
4026                                         n = n.previousSibling;
4027                                 }
4028
4029                                 t.remove(e);
4030                         };
4031
4032                         return this.run(e, function(e) {
4033                                 e = t.get(e);
4034
4035                                 // Only set HTML on elements
4036                                 if (e.nodeType == 1) {
4037                                         d = d || e.ownerDocument || t.doc;
4038
4039                                         if (isIE) {
4040                                                 try {
4041                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
4042                                                         if (isIE && e.nodeType == 1)
4043                                                                 e.outerHTML = h;
4044                                                         else
4045                                                                 setHTML(e, h, d);
4046                                                 } catch (ex) {
4047                                                         // Fix for unknown runtime error
4048                                                         setHTML(e, h, d);
4049                                                 }
4050                                         } else
4051                                                 setHTML(e, h, d);
4052                                 }
4053                         });
4054                 },
4055
4056                 decode : Entities.decode,
4057
4058                 encode : Entities.encodeAllRaw,
4059
4060                 insertAfter : function(node, reference_node) {
4061                         reference_node = this.get(reference_node);
4062
4063                         return this.run(node, function(node) {
4064                                 var parent, nextSibling;
4065
4066                                 parent = reference_node.parentNode;
4067                                 nextSibling = reference_node.nextSibling;
4068
4069                                 if (nextSibling)
4070                                         parent.insertBefore(node, nextSibling);
4071                                 else
4072                                         parent.appendChild(node);
4073
4074                                 return node;
4075                         });
4076                 },
4077
4078                 isBlock : function(node) {
4079                         var type = node.nodeType;
4080
4081                         // If it's a node then check the type and use the nodeName
4082                         if (type)
4083                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
4084
4085                         return !!blockElementsMap[node];
4086                 },
4087
4088                 replace : function(n, o, k) {
4089                         var t = this;
4090
4091                         if (is(o, 'array'))
4092                                 n = n.cloneNode(true);
4093
4094                         return t.run(o, function(o) {
4095                                 if (k) {
4096                                         each(tinymce.grep(o.childNodes), function(c) {
4097                                                 n.appendChild(c);
4098                                         });
4099                                 }
4100
4101                                 return o.parentNode.replaceChild(n, o);
4102                         });
4103                 },
4104
4105                 rename : function(elm, name) {
4106                         var t = this, newElm;
4107
4108                         if (elm.nodeName != name.toUpperCase()) {
4109                                 // Rename block element
4110                                 newElm = t.create(name);
4111
4112                                 // Copy attribs to new block
4113                                 each(t.getAttribs(elm), function(attr_node) {
4114                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4115                                 });
4116
4117                                 // Replace block
4118                                 t.replace(newElm, elm, 1);
4119                         }
4120
4121                         return newElm || elm;
4122                 },
4123
4124                 findCommonAncestor : function(a, b) {
4125                         var ps = a, pe;
4126
4127                         while (ps) {
4128                                 pe = b;
4129
4130                                 while (pe && ps != pe)
4131                                         pe = pe.parentNode;
4132
4133                                 if (ps == pe)
4134                                         break;
4135
4136                                 ps = ps.parentNode;
4137                         }
4138
4139                         if (!ps && a.ownerDocument)
4140                                 return a.ownerDocument.documentElement;
4141
4142                         return ps;
4143                 },
4144
4145                 toHex : function(s) {
4146                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4147
4148                         function hex(s) {
4149                                 s = parseInt(s).toString(16);
4150
4151                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
4152                         };
4153
4154                         if (c) {
4155                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4156
4157                                 return s;
4158                         }
4159
4160                         return s;
4161                 },
4162
4163                 getClasses : function() {
4164                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4165
4166                         if (t.classes)
4167                                 return t.classes;
4168
4169                         function addClasses(s) {
4170                                 // IE style imports
4171                                 each(s.imports, function(r) {
4172                                         addClasses(r);
4173                                 });
4174
4175                                 each(s.cssRules || s.rules, function(r) {
4176                                         // Real type or fake it on IE
4177                                         switch (r.type || 1) {
4178                                                 // Rule
4179                                                 case 1:
4180                                                         if (r.selectorText) {
4181                                                                 each(r.selectorText.split(','), function(v) {
4182                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
4183
4184                                                                         // Is internal or it doesn't contain a class
4185                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4186                                                                                 return;
4187
4188                                                                         // Remove everything but class name
4189                                                                         ov = v;
4190                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
4191
4192                                                                         // Filter classes
4193                                                                         if (f && !(v = f(v, ov)))
4194                                                                                 return;
4195
4196                                                                         if (!lo[v]) {
4197                                                                                 cl.push({'class' : v});
4198                                                                                 lo[v] = 1;
4199                                                                         }
4200                                                                 });
4201                                                         }
4202                                                         break;
4203
4204                                                 // Import
4205                                                 case 3:
4206                                                         addClasses(r.styleSheet);
4207                                                         break;
4208                                         }
4209                                 });
4210                         };
4211
4212                         try {
4213                                 each(t.doc.styleSheets, addClasses);
4214                         } catch (ex) {
4215                                 // Ignore
4216                         }
4217
4218                         if (cl.length > 0)
4219                                 t.classes = cl;
4220
4221                         return cl;
4222                 },
4223
4224                 run : function(e, f, s) {
4225                         var t = this, o;
4226
4227                         if (t.doc && typeof(e) === 'string')
4228                                 e = t.get(e);
4229
4230                         if (!e)
4231                                 return false;
4232
4233                         s = s || this;
4234                         if (!e.nodeType && (e.length || e.length === 0)) {
4235                                 o = [];
4236
4237                                 each(e, function(e, i) {
4238                                         if (e) {
4239                                                 if (typeof(e) == 'string')
4240                                                         e = t.doc.getElementById(e);
4241
4242                                                 o.push(f.call(s, e, i));
4243                                         }
4244                                 });
4245
4246                                 return o;
4247                         }
4248
4249                         return f.call(s, e);
4250                 },
4251
4252                 getAttribs : function(n) {
4253                         var o;
4254
4255                         n = this.get(n);
4256
4257                         if (!n)
4258                                 return [];
4259
4260                         if (isIE) {
4261                                 o = [];
4262
4263                                 // Object will throw exception in IE
4264                                 if (n.nodeName == 'OBJECT')
4265                                         return n.attributes;
4266
4267                                 // IE doesn't keep the selected attribute if you clone option elements
4268                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4269                                         o.push({specified : 1, nodeName : 'selected'});
4270
4271                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
4272                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
4273                                         o.push({specified : 1, nodeName : a});
4274                                 });
4275
4276                                 return o;
4277                         }
4278
4279                         return n.attributes;
4280                 },
4281
4282                 isEmpty : function(node, elements) {
4283                         var self = this, i, attributes, type, walker, name;
4284
4285                         node = node.firstChild;
4286                         if (node) {
4287                                 walker = new tinymce.dom.TreeWalker(node);
4288                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4289
4290                                 do {
4291                                         type = node.nodeType;
4292
4293                                         if (type === 1) {
4294                                                 // Ignore bogus elements
4295                                                 if (node.getAttribute('data-mce-bogus'))
4296                                                         continue;
4297
4298                                                 // Keep empty elements like <img />
4299                                                 if (elements && elements[node.nodeName.toLowerCase()])
4300                                                         return false;
4301
4302                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
4303                                                 attributes = self.getAttribs(node);
4304                                                 i = node.attributes.length;
4305                                                 while (i--) {
4306                                                         name = node.attributes[i].nodeName;
4307                                                         if (name === "name" || name.indexOf('data-') === 0)
4308                                                                 return false;
4309                                                 }
4310                                         }
4311
4312                                         // Keep non whitespace text nodes
4313                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4314                                                 return false;
4315                                 } while (node = walker.next());
4316                         }
4317
4318                         return true;
4319                 },
4320
4321                 destroy : function(s) {
4322                         var t = this;
4323
4324                         if (t.events)
4325                                 t.events.destroy();
4326
4327                         t.win = t.doc = t.root = t.events = null;
4328
4329                         // Manual destroy then remove unload handler
4330                         if (!s)
4331                                 tinymce.removeUnload(t.destroy);
4332                 },
4333
4334                 createRng : function() {
4335                         var d = this.doc;
4336
4337                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4338                 },
4339
4340                 nodeIndex : function(node, normalized) {
4341                         var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
4342
4343                         if (node) {
4344                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4345                                         nodeType = node.nodeType;
4346
4347                                         // Normalize text nodes
4348                                         if (normalized && nodeType == 3) {
4349                                                 // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
4350                                                 // (the nodeValue attribute will not exist, and will error here).
4351                                                 nodeValueExists = false;
4352                                                 try {nodeValueExists = node.nodeValue.length} catch (c) {}
4353                                                 if (nodeType == lastNodeType || !nodeValueExists)
4354                                                         continue;
4355                                         }
4356                                         idx++;
4357                                         lastNodeType = nodeType;
4358                                 }
4359                         }
4360
4361                         return idx;
4362                 },
4363
4364                 split : function(pe, e, re) {
4365                         var t = this, r = t.createRng(), bef, aft, pa;
4366
4367                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
4368                         // but we don't want that in our code since it serves no purpose for the end user
4369                         // For example if this is chopped:
4370                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4371                         // would produce:
4372                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4373                         // this function will then trim of empty edges and produce:
4374                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4375                         function trim(node) {
4376                                 var i, children = node.childNodes, type = node.nodeType;
4377
4378                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4379                                         return;
4380
4381                                 for (i = children.length - 1; i >= 0; i--)
4382                                         trim(children[i]);
4383
4384                                 if (type != 9) {
4385                                         // Keep non whitespace text nodes
4386                                         if (type == 3 && node.nodeValue.length > 0) {
4387                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4388                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4389                                                         return;
4390                                         } else if (type == 1) {
4391                                                 // If the only child is a bookmark then move it up
4392                                                 children = node.childNodes;
4393                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
4394                                                         node.parentNode.insertBefore(children[0], node);
4395
4396                                                 // Keep non empty elements or img, hr etc
4397                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4398                                                         return;
4399                                         }
4400
4401                                         t.remove(node);
4402                                 }
4403
4404                                 return node;
4405                         };
4406
4407                         if (pe && e) {
4408                                 // Get before chunk
4409                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
4410                                 r.setEnd(e.parentNode, t.nodeIndex(e));
4411                                 bef = r.extractContents();
4412
4413                                 // Get after chunk
4414                                 r = t.createRng();
4415                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
4416                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
4417                                 aft = r.extractContents();
4418
4419                                 // Insert before chunk
4420                                 pa = pe.parentNode;
4421                                 pa.insertBefore(trim(bef), pe);
4422
4423                                 // Insert middle chunk
4424                                 if (re)
4425                                         pa.replaceChild(re, e);
4426                                 else
4427                                         pa.insertBefore(e, pe);
4428
4429                                 // Insert after chunk
4430                                 pa.insertBefore(trim(aft), pe);
4431                                 t.remove(pe);
4432
4433                                 return re || e;
4434                         }
4435                 },
4436
4437                 bind : function(target, name, func, scope) {
4438                         var t = this;
4439
4440                         if (!t.events)
4441                                 t.events = new tinymce.dom.EventUtils();
4442
4443                         return t.events.add(target, name, func, scope || this);
4444                 },
4445
4446                 unbind : function(target, name, func) {
4447                         var t = this;
4448
4449                         if (!t.events)
4450                                 t.events = new tinymce.dom.EventUtils();
4451
4452                         return t.events.remove(target, name, func);
4453                 },
4454
4455
4456                 _findSib : function(node, selector, name) {
4457                         var t = this, f = selector;
4458
4459                         if (node) {
4460                                 // If expression make a function of it using is
4461                                 if (is(f, 'string')) {
4462                                         f = function(node) {
4463                                                 return t.is(node, selector);
4464                                         };
4465                                 }
4466
4467                                 // Loop all siblings
4468                                 for (node = node[name]; node; node = node[name]) {
4469                                         if (f(node))
4470                                                 return node;
4471                                 }
4472                         }
4473
4474                         return null;
4475                 },
4476
4477                 _isRes : function(c) {
4478                         // Is live resizble element
4479                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
4480                 }
4481
4482                 /*
4483                 walk : function(n, f, s) {
4484                         var d = this.doc, w;
4485
4486                         if (d.createTreeWalker) {
4487                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4488
4489                                 while ((n = w.nextNode()) != null)
4490                                         f.call(s || this, n);
4491                         } else
4492                                 tinymce.walk(n, f, 'childNodes', s);
4493                 }
4494                 */
4495
4496                 /*
4497                 toRGB : function(s) {
4498                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4499
4500                         if (c) {
4501                                 // #FFF -> #FFFFFF
4502                                 if (!is(c[3]))
4503                                         c[3] = c[2] = c[1];
4504
4505                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4506                         }
4507
4508                         return s;
4509                 }
4510                 */
4511         });
4512
4513         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
4514 })(tinymce);
4515
4516 (function(ns) {
4517         // Range constructor
4518         function Range(dom) {
4519                 var t = this,
4520                         doc = dom.doc,
4521                         EXTRACT = 0,
4522                         CLONE = 1,
4523                         DELETE = 2,
4524                         TRUE = true,
4525                         FALSE = false,
4526                         START_OFFSET = 'startOffset',
4527                         START_CONTAINER = 'startContainer',
4528                         END_CONTAINER = 'endContainer',
4529                         END_OFFSET = 'endOffset',
4530                         extend = tinymce.extend,
4531                         nodeIndex = dom.nodeIndex;
4532
4533                 extend(t, {
4534                         // Inital states
4535                         startContainer : doc,
4536                         startOffset : 0,
4537                         endContainer : doc,
4538                         endOffset : 0,
4539                         collapsed : TRUE,
4540                         commonAncestorContainer : doc,
4541
4542                         // Range constants
4543                         START_TO_START : 0,
4544                         START_TO_END : 1,
4545                         END_TO_END : 2,
4546                         END_TO_START : 3,
4547
4548                         // Public methods
4549                         setStart : setStart,
4550                         setEnd : setEnd,
4551                         setStartBefore : setStartBefore,
4552                         setStartAfter : setStartAfter,
4553                         setEndBefore : setEndBefore,
4554                         setEndAfter : setEndAfter,
4555                         collapse : collapse,
4556                         selectNode : selectNode,
4557                         selectNodeContents : selectNodeContents,
4558                         compareBoundaryPoints : compareBoundaryPoints,
4559                         deleteContents : deleteContents,
4560                         extractContents : extractContents,
4561                         cloneContents : cloneContents,
4562                         insertNode : insertNode,
4563                         surroundContents : surroundContents,
4564                         cloneRange : cloneRange
4565                 });
4566
4567                 function setStart(n, o) {
4568                         _setEndPoint(TRUE, n, o);
4569                 };
4570
4571                 function setEnd(n, o) {
4572                         _setEndPoint(FALSE, n, o);
4573                 };
4574
4575                 function setStartBefore(n) {
4576                         setStart(n.parentNode, nodeIndex(n));
4577                 };
4578
4579                 function setStartAfter(n) {
4580                         setStart(n.parentNode, nodeIndex(n) + 1);
4581                 };
4582
4583                 function setEndBefore(n) {
4584                         setEnd(n.parentNode, nodeIndex(n));
4585                 };
4586
4587                 function setEndAfter(n) {
4588                         setEnd(n.parentNode, nodeIndex(n) + 1);
4589                 };
4590
4591                 function collapse(ts) {
4592                         if (ts) {
4593                                 t[END_CONTAINER] = t[START_CONTAINER];
4594                                 t[END_OFFSET] = t[START_OFFSET];
4595                         } else {
4596                                 t[START_CONTAINER] = t[END_CONTAINER];
4597                                 t[START_OFFSET] = t[END_OFFSET];
4598                         }
4599
4600                         t.collapsed = TRUE;
4601                 };
4602
4603                 function selectNode(n) {
4604                         setStartBefore(n);
4605                         setEndAfter(n);
4606                 };
4607
4608                 function selectNodeContents(n) {
4609                         setStart(n, 0);
4610                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
4611                 };
4612
4613                 function compareBoundaryPoints(h, r) {
4614                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
4615                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
4616
4617                         // Check START_TO_START
4618                         if (h === 0)
4619                                 return _compareBoundaryPoints(sc, so, rsc, rso);
4620         
4621                         // Check START_TO_END
4622                         if (h === 1)
4623                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
4624         
4625                         // Check END_TO_END
4626                         if (h === 2)
4627                                 return _compareBoundaryPoints(ec, eo, rec, reo);
4628         
4629                         // Check END_TO_START
4630                         if (h === 3) 
4631                                 return _compareBoundaryPoints(sc, so, rec, reo);
4632                 };
4633
4634                 function deleteContents() {
4635                         _traverse(DELETE);
4636                 };
4637
4638                 function extractContents() {
4639                         return _traverse(EXTRACT);
4640                 };
4641
4642                 function cloneContents() {
4643                         return _traverse(CLONE);
4644                 };
4645
4646                 function insertNode(n) {
4647                         var startContainer = this[START_CONTAINER],
4648                                 startOffset = this[START_OFFSET], nn, o;
4649
4650                         // Node is TEXT_NODE or CDATA
4651                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
4652                                 if (!startOffset) {
4653                                         // At the start of text
4654                                         startContainer.parentNode.insertBefore(n, startContainer);
4655                                 } else if (startOffset >= startContainer.nodeValue.length) {
4656                                         // At the end of text
4657                                         dom.insertAfter(n, startContainer);
4658                                 } else {
4659                                         // Middle, need to split
4660                                         nn = startContainer.splitText(startOffset);
4661                                         startContainer.parentNode.insertBefore(n, nn);
4662                                 }
4663                         } else {
4664                                 // Insert element node
4665                                 if (startContainer.childNodes.length > 0)
4666                                         o = startContainer.childNodes[startOffset];
4667
4668                                 if (o)
4669                                         startContainer.insertBefore(n, o);
4670                                 else
4671                                         startContainer.appendChild(n);
4672                         }
4673                 };
4674
4675                 function surroundContents(n) {
4676                         var f = t.extractContents();
4677
4678                         t.insertNode(n);
4679                         n.appendChild(f);
4680                         t.selectNode(n);
4681                 };
4682
4683                 function cloneRange() {
4684                         return extend(new Range(dom), {
4685                                 startContainer : t[START_CONTAINER],
4686                                 startOffset : t[START_OFFSET],
4687                                 endContainer : t[END_CONTAINER],
4688                                 endOffset : t[END_OFFSET],
4689                                 collapsed : t.collapsed,
4690                                 commonAncestorContainer : t.commonAncestorContainer
4691                         });
4692                 };
4693
4694                 // Private methods
4695
4696                 function _getSelectedNode(container, offset) {
4697                         var child;
4698
4699                         if (container.nodeType == 3 /* TEXT_NODE */)
4700                                 return container;
4701
4702                         if (offset < 0)
4703                                 return container;
4704
4705                         child = container.firstChild;
4706                         while (child && offset > 0) {
4707                                 --offset;
4708                                 child = child.nextSibling;
4709                         }
4710
4711                         if (child)
4712                                 return child;
4713
4714                         return container;
4715                 };
4716
4717                 function _isCollapsed() {
4718                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
4719                 };
4720
4721                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
4722                         var c, offsetC, n, cmnRoot, childA, childB;
4723                         
4724                         // In the first case the boundary-points have the same container. A is before B
4725                         // if its offset is less than the offset of B, A is equal to B if its offset is
4726                         // equal to the offset of B, and A is after B if its offset is greater than the
4727                         // offset of B.
4728                         if (containerA == containerB) {
4729                                 if (offsetA == offsetB)
4730                                         return 0; // equal
4731
4732                                 if (offsetA < offsetB)
4733                                         return -1; // before
4734
4735                                 return 1; // after
4736                         }
4737
4738                         // In the second case a child node C of the container of A is an ancestor
4739                         // container of B. In this case, A is before B if the offset of A is less than or
4740                         // equal to the index of the child node C and A is after B otherwise.
4741                         c = containerB;
4742                         while (c && c.parentNode != containerA)
4743                                 c = c.parentNode;
4744
4745                         if (c) {
4746                                 offsetC = 0;
4747                                 n = containerA.firstChild;
4748
4749                                 while (n != c && offsetC < offsetA) {
4750                                         offsetC++;
4751                                         n = n.nextSibling;
4752                                 }
4753
4754                                 if (offsetA <= offsetC)
4755                                         return -1; // before
4756
4757                                 return 1; // after
4758                         }
4759
4760                         // In the third case a child node C of the container of B is an ancestor container
4761                         // of A. In this case, A is before B if the index of the child node C is less than
4762                         // the offset of B and A is after B otherwise.
4763                         c = containerA;
4764                         while (c && c.parentNode != containerB) {
4765                                 c = c.parentNode;
4766                         }
4767
4768                         if (c) {
4769                                 offsetC = 0;
4770                                 n = containerB.firstChild;
4771
4772                                 while (n != c && offsetC < offsetB) {
4773                                         offsetC++;
4774                                         n = n.nextSibling;
4775                                 }
4776
4777                                 if (offsetC < offsetB)
4778                                         return -1; // before
4779
4780                                 return 1; // after
4781                         }
4782
4783                         // In the fourth case, none of three other cases hold: the containers of A and B
4784                         // are siblings or descendants of sibling nodes. In this case, A is before B if
4785                         // the container of A is before the container of B in a pre-order traversal of the
4786                         // Ranges' context tree and A is after B otherwise.
4787                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
4788                         childA = containerA;
4789
4790                         while (childA && childA.parentNode != cmnRoot)
4791                                 childA = childA.parentNode;
4792
4793                         if (!childA)
4794                                 childA = cmnRoot;
4795
4796                         childB = containerB;
4797                         while (childB && childB.parentNode != cmnRoot)
4798                                 childB = childB.parentNode;
4799
4800                         if (!childB)
4801                                 childB = cmnRoot;
4802
4803                         if (childA == childB)
4804                                 return 0; // equal
4805
4806                         n = cmnRoot.firstChild;
4807                         while (n) {
4808                                 if (n == childA)
4809                                         return -1; // before
4810
4811                                 if (n == childB)
4812                                         return 1; // after
4813
4814                                 n = n.nextSibling;
4815                         }
4816                 };
4817
4818                 function _setEndPoint(st, n, o) {
4819                         var ec, sc;
4820
4821                         if (st) {
4822                                 t[START_CONTAINER] = n;
4823                                 t[START_OFFSET] = o;
4824                         } else {
4825                                 t[END_CONTAINER] = n;
4826                                 t[END_OFFSET] = o;
4827                         }
4828
4829                         // If one boundary-point of a Range is set to have a root container
4830                         // other than the current one for the Range, the Range is collapsed to
4831                         // the new position. This enforces the restriction that both boundary-
4832                         // points of a Range must have the same root container.
4833                         ec = t[END_CONTAINER];
4834                         while (ec.parentNode)
4835                                 ec = ec.parentNode;
4836
4837                         sc = t[START_CONTAINER];
4838                         while (sc.parentNode)
4839                                 sc = sc.parentNode;
4840
4841                         if (sc == ec) {
4842                                 // The start position of a Range is guaranteed to never be after the
4843                                 // end position. To enforce this restriction, if the start is set to
4844                                 // be at a position after the end, the Range is collapsed to that
4845                                 // position.
4846                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
4847                                         t.collapse(st);
4848                         } else
4849                                 t.collapse(st);
4850
4851                         t.collapsed = _isCollapsed();
4852                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
4853                 };
4854
4855                 function _traverse(how) {
4856                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
4857
4858                         if (t[START_CONTAINER] == t[END_CONTAINER])
4859                                 return _traverseSameContainer(how);
4860
4861                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
4862                                 if (p == t[START_CONTAINER])
4863                                         return _traverseCommonStartContainer(c, how);
4864
4865                                 ++endContainerDepth;
4866                         }
4867
4868                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
4869                                 if (p == t[END_CONTAINER])
4870                                         return _traverseCommonEndContainer(c, how);
4871
4872                                 ++startContainerDepth;
4873                         }
4874
4875                         depthDiff = startContainerDepth - endContainerDepth;
4876
4877                         startNode = t[START_CONTAINER];
4878                         while (depthDiff > 0) {
4879                                 startNode = startNode.parentNode;
4880                                 depthDiff--;
4881                         }
4882
4883                         endNode = t[END_CONTAINER];
4884                         while (depthDiff < 0) {
4885                                 endNode = endNode.parentNode;
4886                                 depthDiff++;
4887                         }
4888
4889                         // ascend the ancestor hierarchy until we have a common parent.
4890                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
4891                                 startNode = sp;
4892                                 endNode = ep;
4893                         }
4894
4895                         return _traverseCommonAncestors(startNode, endNode, how);
4896                 };
4897
4898                  function _traverseSameContainer(how) {
4899                         var frag, s, sub, n, cnt, sibling, xferNode;
4900
4901                         if (how != DELETE)
4902                                 frag = doc.createDocumentFragment();
4903
4904                         // If selection is empty, just return the fragment
4905                         if (t[START_OFFSET] == t[END_OFFSET])
4906                                 return frag;
4907
4908                         // Text node needs special case handling
4909                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
4910                                 // get the substring
4911                                 s = t[START_CONTAINER].nodeValue;
4912                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
4913
4914                                 // set the original text node to its new value
4915                                 if (how != CLONE) {
4916                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
4917
4918                                         // Nothing is partially selected, so collapse to start point
4919                                         t.collapse(TRUE);
4920                                 }
4921
4922                                 if (how == DELETE)
4923                                         return;
4924
4925                                 frag.appendChild(doc.createTextNode(sub));
4926                                 return frag;
4927                         }
4928
4929                         // Copy nodes between the start/end offsets.
4930                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
4931                         cnt = t[END_OFFSET] - t[START_OFFSET];
4932
4933                         while (cnt > 0) {
4934                                 sibling = n.nextSibling;
4935                                 xferNode = _traverseFullySelected(n, how);
4936
4937                                 if (frag)
4938                                         frag.appendChild( xferNode );
4939
4940                                 --cnt;
4941                                 n = sibling;
4942                         }
4943
4944                         // Nothing is partially selected, so collapse to start point
4945                         if (how != CLONE)
4946                                 t.collapse(TRUE);
4947
4948                         return frag;
4949                 };
4950
4951                 function _traverseCommonStartContainer(endAncestor, how) {
4952                         var frag, n, endIdx, cnt, sibling, xferNode;
4953
4954                         if (how != DELETE)
4955                                 frag = doc.createDocumentFragment();
4956
4957                         n = _traverseRightBoundary(endAncestor, how);
4958
4959                         if (frag)
4960                                 frag.appendChild(n);
4961
4962                         endIdx = nodeIndex(endAncestor);
4963                         cnt = endIdx - t[START_OFFSET];
4964
4965                         if (cnt <= 0) {
4966                                 // Collapse to just before the endAncestor, which
4967                                 // is partially selected.
4968                                 if (how != CLONE) {
4969                                         t.setEndBefore(endAncestor);
4970                                         t.collapse(FALSE);
4971                                 }
4972
4973                                 return frag;
4974                         }
4975
4976                         n = endAncestor.previousSibling;
4977                         while (cnt > 0) {
4978                                 sibling = n.previousSibling;
4979                                 xferNode = _traverseFullySelected(n, how);
4980
4981                                 if (frag)
4982                                         frag.insertBefore(xferNode, frag.firstChild);
4983
4984                                 --cnt;
4985                                 n = sibling;
4986                         }
4987
4988                         // Collapse to just before the endAncestor, which
4989                         // is partially selected.
4990                         if (how != CLONE) {
4991                                 t.setEndBefore(endAncestor);
4992                                 t.collapse(FALSE);
4993                         }
4994
4995                         return frag;
4996                 };
4997
4998                 function _traverseCommonEndContainer(startAncestor, how) {
4999                         var frag, startIdx, n, cnt, sibling, xferNode;
5000
5001                         if (how != DELETE)
5002                                 frag = doc.createDocumentFragment();
5003
5004                         n = _traverseLeftBoundary(startAncestor, how);
5005                         if (frag)
5006                                 frag.appendChild(n);
5007
5008                         startIdx = nodeIndex(startAncestor);
5009                         ++startIdx; // Because we already traversed it
5010
5011                         cnt = t[END_OFFSET] - startIdx;
5012                         n = startAncestor.nextSibling;
5013                         while (cnt > 0) {
5014                                 sibling = n.nextSibling;
5015                                 xferNode = _traverseFullySelected(n, how);
5016
5017                                 if (frag)
5018                                         frag.appendChild(xferNode);
5019
5020                                 --cnt;
5021                                 n = sibling;
5022                         }
5023
5024                         if (how != CLONE) {
5025                                 t.setStartAfter(startAncestor);
5026                                 t.collapse(TRUE);
5027                         }
5028
5029                         return frag;
5030                 };
5031
5032                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
5033                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
5034
5035                         if (how != DELETE)
5036                                 frag = doc.createDocumentFragment();
5037
5038                         n = _traverseLeftBoundary(startAncestor, how);
5039                         if (frag)
5040                                 frag.appendChild(n);
5041
5042                         commonParent = startAncestor.parentNode;
5043                         startOffset = nodeIndex(startAncestor);
5044                         endOffset = nodeIndex(endAncestor);
5045                         ++startOffset;
5046
5047                         cnt = endOffset - startOffset;
5048                         sibling = startAncestor.nextSibling;
5049
5050                         while (cnt > 0) {
5051                                 nextSibling = sibling.nextSibling;
5052                                 n = _traverseFullySelected(sibling, how);
5053
5054                                 if (frag)
5055                                         frag.appendChild(n);
5056
5057                                 sibling = nextSibling;
5058                                 --cnt;
5059                         }
5060
5061                         n = _traverseRightBoundary(endAncestor, how);
5062
5063                         if (frag)
5064                                 frag.appendChild(n);
5065
5066                         if (how != CLONE) {
5067                                 t.setStartAfter(startAncestor);
5068                                 t.collapse(TRUE);
5069                         }
5070
5071                         return frag;
5072                 };
5073
5074                 function _traverseRightBoundary(root, how) {
5075                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
5076
5077                         if (next == root)
5078                                 return _traverseNode(next, isFullySelected, FALSE, how);
5079
5080                         parent = next.parentNode;
5081                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
5082
5083                         while (parent) {
5084                                 while (next) {
5085                                         prevSibling = next.previousSibling;
5086                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
5087
5088                                         if (how != DELETE)
5089                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5090
5091                                         isFullySelected = TRUE;
5092                                         next = prevSibling;
5093                                 }
5094
5095                                 if (parent == root)
5096                                         return clonedParent;
5097
5098                                 next = parent.previousSibling;
5099                                 parent = parent.parentNode;
5100
5101                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
5102
5103                                 if (how != DELETE)
5104                                         clonedGrandParent.appendChild(clonedParent);
5105
5106                                 clonedParent = clonedGrandParent;
5107                         }
5108                 };
5109
5110                 function _traverseLeftBoundary(root, how) {
5111                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
5112
5113                         if (next == root)
5114                                 return _traverseNode(next, isFullySelected, TRUE, how);
5115
5116                         parent = next.parentNode;
5117                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
5118
5119                         while (parent) {
5120                                 while (next) {
5121                                         nextSibling = next.nextSibling;
5122                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
5123
5124                                         if (how != DELETE)
5125                                                 clonedParent.appendChild(clonedChild);
5126
5127                                         isFullySelected = TRUE;
5128                                         next = nextSibling;
5129                                 }
5130
5131                                 if (parent == root)
5132                                         return clonedParent;
5133
5134                                 next = parent.nextSibling;
5135                                 parent = parent.parentNode;
5136
5137                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
5138
5139                                 if (how != DELETE)
5140                                         clonedGrandParent.appendChild(clonedParent);
5141
5142                                 clonedParent = clonedGrandParent;
5143                         }
5144                 };
5145
5146                 function _traverseNode(n, isFullySelected, isLeft, how) {
5147                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
5148
5149                         if (isFullySelected)
5150                                 return _traverseFullySelected(n, how);
5151
5152                         if (n.nodeType == 3 /* TEXT_NODE */) {
5153                                 txtValue = n.nodeValue;
5154
5155                                 if (isLeft) {
5156                                         offset = t[START_OFFSET];
5157                                         newNodeValue = txtValue.substring(offset);
5158                                         oldNodeValue = txtValue.substring(0, offset);
5159                                 } else {
5160                                         offset = t[END_OFFSET];
5161                                         newNodeValue = txtValue.substring(0, offset);
5162                                         oldNodeValue = txtValue.substring(offset);
5163                                 }
5164
5165                                 if (how != CLONE)
5166                                         n.nodeValue = oldNodeValue;
5167
5168                                 if (how == DELETE)
5169                                         return;
5170
5171                                 newNode = n.cloneNode(FALSE);
5172                                 newNode.nodeValue = newNodeValue;
5173
5174                                 return newNode;
5175                         }
5176
5177                         if (how == DELETE)
5178                                 return;
5179
5180                         return n.cloneNode(FALSE);
5181                 };
5182
5183                 function _traverseFullySelected(n, how) {
5184                         if (how != DELETE)
5185                                 return how == CLONE ? n.cloneNode(TRUE) : n;
5186
5187                         n.parentNode.removeChild(n);
5188                 };
5189         };
5190
5191         ns.Range = Range;
5192 })(tinymce.dom);
5193
5194 (function() {
5195         function Selection(selection) {
5196                 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
5197
5198                 // Returns a W3C DOM compatible range object by using the IE Range API
5199                 function getRange() {
5200                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
5201
5202                         // If selection is outside the current document just return an empty range
5203                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5204                         if (element.ownerDocument != dom.doc)
5205                                 return domRange;
5206
5207                         collapsed = selection.isCollapsed();
5208
5209                         // Handle control selection or text selection of a image
5210                         if (ieRange.item || !element.hasChildNodes()) {
5211                                 if (collapsed) {
5212                                         domRange.setStart(element, 0);
5213                                         domRange.setEnd(element, 0);
5214                                 } else {
5215                                         domRange.setStart(element.parentNode, dom.nodeIndex(element));
5216                                         domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5217                                 }
5218
5219                                 return domRange;
5220                         }
5221
5222                         function findEndPoint(start) {
5223                                 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
5224
5225                                 // Setup temp range and collapse it
5226                                 checkRng = ieRange.duplicate();
5227                                 checkRng.collapse(start);
5228
5229                                 // Create marker and insert it at the end of the endpoints parent
5230                                 marker = dom.create('a');
5231                                 parent = checkRng.parentElement();
5232
5233                                 // If parent doesn't have any children then set the container to that parent and the index to 0
5234                                 if (!parent.hasChildNodes()) {
5235                                         domRange[start ? 'setStart' : 'setEnd'](parent, 0);
5236                                         return;
5237                                 }
5238
5239                                 parent.appendChild(marker);
5240                                 checkRng.moveToElementText(marker);
5241                                 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5242                                 if (position > 0) {
5243                                         // The position is after the end of the parent element.
5244                                         // This is the case where IE puts the caret to the left edge of a table.
5245                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
5246                                         dom.remove(marker);
5247                                         return;
5248                                 }
5249
5250                                 // Setup node list and endIndex
5251                                 nodes = tinymce.grep(parent.childNodes);
5252                                 endIndex = nodes.length - 1;
5253                                 // Perform a binary search for the position
5254                                 while (startIndex <= endIndex) {
5255                                         index = Math.floor((startIndex + endIndex) / 2);
5256
5257                                         // Insert marker and check it's position relative to the selection
5258                                         parent.insertBefore(marker, nodes[index]);
5259                                         checkRng.moveToElementText(marker);
5260                                         position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5261                                         if (position > 0) {
5262                                                 // Marker is to the right
5263                                                 startIndex = index + 1;
5264                                         } else if (position < 0) {
5265                                                 // Marker is to the left
5266                                                 endIndex = index - 1;
5267                                         } else {
5268                                                 // Maker is where we are
5269                                                 found = true;
5270                                                 break;
5271                                         }
5272                                 }
5273
5274                                 // Setup container
5275                                 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
5276
5277                                 // Handle element selection
5278                                 if (container.nodeType == 1) {
5279                                         dom.remove(marker);
5280
5281                                         // Find offset and container
5282                                         offset = dom.nodeIndex(container);
5283                                         container = container.parentNode;
5284
5285                                         // Move the offset if we are setting the end or the position is after an element
5286                                         if (!start || index > 0)
5287                                                 offset++;
5288                                 } else {
5289                                         // Calculate offset within text node
5290                                         if (position > 0 || index == 0) {
5291                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
5292                                                 offset = checkRng.text.length;
5293                                         } else {
5294                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
5295                                                 offset = container.nodeValue.length - checkRng.text.length;
5296                                         }
5297
5298                                         dom.remove(marker);
5299                                 }
5300
5301                                 domRange[start ? 'setStart' : 'setEnd'](container, offset);
5302                         };
5303
5304                         // Find start point
5305                         findEndPoint(true);
5306
5307                         // Find end point if needed
5308                         if (!collapsed)
5309                                 findEndPoint();
5310
5311                         return domRange;
5312                 };
5313
5314                 this.addRange = function(rng) {
5315                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
5316
5317                         function setEndPoint(start) {
5318                                 var container, offset, marker, tmpRng, nodes;
5319
5320                                 marker = dom.create('a');
5321                                 container = start ? startContainer : endContainer;
5322                                 offset = start ? startOffset : endOffset;
5323                                 tmpRng = ieRng.duplicate();
5324
5325                                 if (container == doc || container == doc.documentElement) {
5326                                         container = body;
5327                                         offset = 0;
5328                                 }
5329
5330                                 if (container.nodeType == 3) {
5331                                         container.parentNode.insertBefore(marker, container);
5332                                         tmpRng.moveToElementText(marker);
5333                                         tmpRng.moveStart('character', offset);
5334                                         dom.remove(marker);
5335                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5336                                 } else {
5337                                         nodes = container.childNodes;
5338
5339                                         if (nodes.length) {
5340                                                 if (offset >= nodes.length) {
5341                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
5342                                                 } else {
5343                                                         container.insertBefore(marker, nodes[offset]);
5344                                                 }
5345
5346                                                 tmpRng.moveToElementText(marker);
5347                                         } else {
5348                                                 // Empty node selection for example <div>|</div>
5349                                                 marker = doc.createTextNode(invisibleChar);
5350                                                 container.appendChild(marker);
5351                                                 tmpRng.moveToElementText(marker.parentNode);
5352                                                 tmpRng.collapse(TRUE);
5353                                         }
5354
5355                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5356                                         dom.remove(marker);
5357                                 }
5358                         }
5359
5360                         // Destroy cached range
5361                         this.destroy();
5362
5363                         // Setup some shorter versions
5364                         startContainer = rng.startContainer;
5365                         startOffset = rng.startOffset;
5366                         endContainer = rng.endContainer;
5367                         endOffset = rng.endOffset;
5368                         ieRng = body.createTextRange();
5369
5370                         // If single element selection then try making a control selection out of it
5371                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
5372                                 if (startOffset == endOffset - 1) {
5373                                         try {
5374                                                 ctrlRng = body.createControlRange();
5375                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);
5376                                                 ctrlRng.select();
5377                                                 return;
5378                                         } catch (ex) {
5379                                                 // Ignore
5380                                         }
5381                                 }
5382                         }
5383
5384                         // Set start/end point of selection
5385                         setEndPoint(true);
5386                         setEndPoint();
5387
5388                         // Select the new range and scroll it into view
5389                         ieRng.select();
5390                 };
5391
5392                 this.getRangeAt = function() {
5393                         // Setup new range if the cache is empty
5394                         if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
5395                                 range = getRange();
5396
5397                                 // Store away text range for next call
5398                                 lastIERng = selection.getRng();
5399                         }
5400
5401                         // IE will say that the range is equal then produce an invalid argument exception
5402                         // if you perform specific operations in a keyup event. For example Ctrl+Del.
5403                         // This hack will invalidate the range cache if the exception occurs
5404                         try {
5405                                 range.startContainer.nextSibling;
5406                         } catch (ex) {
5407                                 range = getRange();
5408                                 lastIERng = null;
5409                         }
5410
5411                         // Return cached range
5412                         return range;
5413                 };
5414
5415                 this.destroy = function() {
5416                         // Destroy cached range and last IE range to avoid memory leaks
5417                         lastIERng = range = null;
5418                 };
5419         };
5420
5421         // Expose the selection object
5422         tinymce.dom.TridentSelection = Selection;
5423 })();
5424
5425
5426 /*
5427  * Sizzle CSS Selector Engine - v1.0
5428  *  Copyright 2009, The Dojo Foundation
5429  *  Released under the MIT, BSD, and GPL Licenses.
5430  *  More information: http://sizzlejs.com/
5431  */
5432 (function(){
5433
5434 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
5435         done = 0,
5436         toString = Object.prototype.toString,
5437         hasDuplicate = false,
5438         baseHasDuplicate = true;
5439
5440 // Here we check if the JavaScript engine is using some sort of
5441 // optimization where it does not always call our comparision
5442 // function. If that is the case, discard the hasDuplicate value.
5443 //   Thus far that includes Google Chrome.
5444 [0, 0].sort(function(){
5445         baseHasDuplicate = false;
5446         return 0;
5447 });
5448
5449 var Sizzle = function(selector, context, results, seed) {
5450         results = results || [];
5451         context = context || document;
5452
5453         var origContext = context;
5454
5455         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
5456                 return [];
5457         }
5458         
5459         if ( !selector || typeof selector !== "string" ) {
5460                 return results;
5461         }
5462
5463         var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
5464                 soFar = selector, ret, cur, pop, i;
5465         
5466         // Reset the position of the chunker regexp (start from head)
5467         do {
5468                 chunker.exec("");
5469                 m = chunker.exec(soFar);
5470
5471                 if ( m ) {
5472                         soFar = m[3];
5473                 
5474                         parts.push( m[1] );
5475                 
5476                         if ( m[2] ) {
5477                                 extra = m[3];
5478                                 break;
5479                         }
5480                 }
5481         } while ( m );
5482
5483         if ( parts.length > 1 && origPOS.exec( selector ) ) {
5484                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
5485                         set = posProcess( parts[0] + parts[1], context );
5486                 } else {
5487                         set = Expr.relative[ parts[0] ] ?
5488                                 [ context ] :
5489                                 Sizzle( parts.shift(), context );
5490
5491                         while ( parts.length ) {
5492                                 selector = parts.shift();
5493
5494                                 if ( Expr.relative[ selector ] ) {
5495                                         selector += parts.shift();
5496                                 }
5497                                 
5498                                 set = posProcess( selector, set );
5499                         }
5500                 }
5501         } else {
5502                 // Take a shortcut and set the context if the root selector is an ID
5503                 // (but not if it'll be faster if the inner selector is an ID)
5504                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
5505                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
5506                         ret = Sizzle.find( parts.shift(), context, contextXML );
5507                         context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
5508                 }
5509
5510                 if ( context ) {
5511                         ret = seed ?
5512                                 { expr: parts.pop(), set: makeArray(seed) } :
5513                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
5514                         set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
5515
5516                         if ( parts.length > 0 ) {
5517                                 checkSet = makeArray(set);
5518                         } else {
5519                                 prune = false;
5520                         }
5521
5522                         while ( parts.length ) {
5523                                 cur = parts.pop();
5524                                 pop = cur;
5525
5526                                 if ( !Expr.relative[ cur ] ) {
5527                                         cur = "";
5528                                 } else {
5529                                         pop = parts.pop();
5530                                 }
5531
5532                                 if ( pop == null ) {
5533                                         pop = context;
5534                                 }
5535
5536                                 Expr.relative[ cur ]( checkSet, pop, contextXML );
5537                         }
5538                 } else {
5539                         checkSet = parts = [];
5540                 }
5541         }
5542
5543         if ( !checkSet ) {
5544                 checkSet = set;
5545         }
5546
5547         if ( !checkSet ) {
5548                 Sizzle.error( cur || selector );
5549         }
5550
5551         if ( toString.call(checkSet) === "[object Array]" ) {
5552                 if ( !prune ) {
5553                         results.push.apply( results, checkSet );
5554                 } else if ( context && context.nodeType === 1 ) {
5555                         for ( i = 0; checkSet[i] != null; i++ ) {
5556                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
5557                                         results.push( set[i] );
5558                                 }
5559                         }
5560                 } else {
5561                         for ( i = 0; checkSet[i] != null; i++ ) {
5562                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
5563                                         results.push( set[i] );
5564                                 }
5565                         }
5566                 }
5567         } else {
5568                 makeArray( checkSet, results );
5569         }
5570
5571         if ( extra ) {
5572                 Sizzle( extra, origContext, results, seed );
5573                 Sizzle.uniqueSort( results );
5574         }
5575
5576         return results;
5577 };
5578
5579 Sizzle.uniqueSort = function(results){
5580         if ( sortOrder ) {
5581                 hasDuplicate = baseHasDuplicate;
5582                 results.sort(sortOrder);
5583
5584                 if ( hasDuplicate ) {
5585                         for ( var i = 1; i < results.length; i++ ) {
5586                                 if ( results[i] === results[i-1] ) {
5587                                         results.splice(i--, 1);
5588                                 }
5589                         }
5590                 }
5591         }
5592
5593         return results;
5594 };
5595
5596 Sizzle.matches = function(expr, set){
5597         return Sizzle(expr, null, null, set);
5598 };
5599
5600 Sizzle.find = function(expr, context, isXML){
5601         var set;
5602
5603         if ( !expr ) {
5604                 return [];
5605         }
5606
5607         for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
5608                 var type = Expr.order[i], match;
5609                 
5610                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
5611                         var left = match[1];
5612                         match.splice(1,1);
5613
5614                         if ( left.substr( left.length - 1 ) !== "\\" ) {
5615                                 match[1] = (match[1] || "").replace(/\\/g, "");
5616                                 set = Expr.find[ type ]( match, context, isXML );
5617                                 if ( set != null ) {
5618                                         expr = expr.replace( Expr.match[ type ], "" );
5619                                         break;
5620                                 }
5621                         }
5622                 }
5623         }
5624
5625         if ( !set ) {
5626                 set = context.getElementsByTagName("*");
5627         }
5628
5629         return {set: set, expr: expr};
5630 };
5631
5632 Sizzle.filter = function(expr, set, inplace, not){
5633         var old = expr, result = [], curLoop = set, match, anyFound,
5634                 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
5635
5636         while ( expr && set.length ) {
5637                 for ( var type in Expr.filter ) {
5638                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
5639                                 var filter = Expr.filter[ type ], found, item, left = match[1];
5640                                 anyFound = false;
5641
5642                                 match.splice(1,1);
5643
5644                                 if ( left.substr( left.length - 1 ) === "\\" ) {
5645                                         continue;
5646                                 }
5647
5648                                 if ( curLoop === result ) {
5649                                         result = [];
5650                                 }
5651
5652                                 if ( Expr.preFilter[ type ] ) {
5653                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
5654
5655                                         if ( !match ) {
5656                                                 anyFound = found = true;
5657                                         } else if ( match === true ) {
5658                                                 continue;
5659                                         }
5660                                 }
5661
5662                                 if ( match ) {
5663                                         for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
5664                                                 if ( item ) {
5665                                                         found = filter( item, match, i, curLoop );
5666                                                         var pass = not ^ !!found;
5667
5668                                                         if ( inplace && found != null ) {
5669                                                                 if ( pass ) {
5670                                                                         anyFound = true;
5671                                                                 } else {
5672                                                                         curLoop[i] = false;
5673                                                                 }
5674                                                         } else if ( pass ) {
5675                                                                 result.push( item );
5676                                                                 anyFound = true;
5677                                                         }
5678                                                 }
5679                                         }
5680                                 }
5681
5682                                 if ( found !== undefined ) {
5683                                         if ( !inplace ) {
5684                                                 curLoop = result;
5685                                         }
5686
5687                                         expr = expr.replace( Expr.match[ type ], "" );
5688
5689                                         if ( !anyFound ) {
5690                                                 return [];
5691                                         }
5692
5693                                         break;
5694                                 }
5695                         }
5696                 }
5697
5698                 // Improper expression
5699                 if ( expr === old ) {
5700                         if ( anyFound == null ) {
5701                                 Sizzle.error( expr );
5702                         } else {
5703                                 break;
5704                         }
5705                 }
5706
5707                 old = expr;
5708         }
5709
5710         return curLoop;
5711 };
5712
5713 Sizzle.error = function( msg ) {
5714         throw "Syntax error, unrecognized expression: " + msg;
5715 };
5716
5717 var Expr = Sizzle.selectors = {
5718         order: [ "ID", "NAME", "TAG" ],
5719         match: {
5720                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
5721                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
5722                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
5723                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
5724                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
5725                 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
5726                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
5727                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
5728         },
5729         leftMatch: {},
5730         attrMap: {
5731                 "class": "className",
5732                 "for": "htmlFor"
5733         },
5734         attrHandle: {
5735                 href: function(elem){
5736                         return elem.getAttribute("href");
5737                 }
5738         },
5739         relative: {
5740                 "+": function(checkSet, part){
5741                         var isPartStr = typeof part === "string",
5742                                 isTag = isPartStr && !/\W/.test(part),
5743                                 isPartStrNotTag = isPartStr && !isTag;
5744
5745                         if ( isTag ) {
5746                                 part = part.toLowerCase();
5747                         }
5748
5749                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
5750                                 if ( (elem = checkSet[i]) ) {
5751                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
5752
5753                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
5754                                                 elem || false :
5755                                                 elem === part;
5756                                 }
5757                         }
5758
5759                         if ( isPartStrNotTag ) {
5760                                 Sizzle.filter( part, checkSet, true );
5761                         }
5762                 },
5763                 ">": function(checkSet, part){
5764                         var isPartStr = typeof part === "string",
5765                                 elem, i = 0, l = checkSet.length;
5766
5767                         if ( isPartStr && !/\W/.test(part) ) {
5768                                 part = part.toLowerCase();
5769
5770                                 for ( ; i < l; i++ ) {
5771                                         elem = checkSet[i];
5772                                         if ( elem ) {
5773                                                 var parent = elem.parentNode;
5774                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
5775                                         }
5776                                 }
5777                         } else {
5778                                 for ( ; i < l; i++ ) {
5779                                         elem = checkSet[i];
5780                                         if ( elem ) {
5781                                                 checkSet[i] = isPartStr ?
5782                                                         elem.parentNode :
5783                                                         elem.parentNode === part;
5784                                         }
5785                                 }
5786
5787                                 if ( isPartStr ) {
5788                                         Sizzle.filter( part, checkSet, true );
5789                                 }
5790                         }
5791                 },
5792                 "": function(checkSet, part, isXML){
5793                         var doneName = done++, checkFn = dirCheck, nodeCheck;
5794
5795                         if ( typeof part === "string" && !/\W/.test(part) ) {
5796                                 part = part.toLowerCase();
5797                                 nodeCheck = part;
5798                                 checkFn = dirNodeCheck;
5799                         }
5800
5801                         checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
5802                 },
5803                 "~": function(checkSet, part, isXML){
5804                         var doneName = done++, checkFn = dirCheck, nodeCheck;
5805
5806                         if ( typeof part === "string" && !/\W/.test(part) ) {
5807                                 part = part.toLowerCase();
5808                                 nodeCheck = part;
5809                                 checkFn = dirNodeCheck;
5810                         }
5811
5812                         checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
5813                 }
5814         },
5815         find: {
5816                 ID: function(match, context, isXML){
5817                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
5818                                 var m = context.getElementById(match[1]);
5819                                 return m ? [m] : [];
5820                         }
5821                 },
5822                 NAME: function(match, context){
5823                         if ( typeof context.getElementsByName !== "undefined" ) {
5824                                 var ret = [], results = context.getElementsByName(match[1]);
5825
5826                                 for ( var i = 0, l = results.length; i < l; i++ ) {
5827                                         if ( results[i].getAttribute("name") === match[1] ) {
5828                                                 ret.push( results[i] );
5829                                         }
5830                                 }
5831
5832                                 return ret.length === 0 ? null : ret;
5833                         }
5834                 },
5835                 TAG: function(match, context){
5836                         return context.getElementsByTagName(match[1]);
5837                 }
5838         },
5839         preFilter: {
5840                 CLASS: function(match, curLoop, inplace, result, not, isXML){
5841                         match = " " + match[1].replace(/\\/g, "") + " ";
5842
5843                         if ( isXML ) {
5844                                 return match;
5845                         }
5846
5847                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
5848                                 if ( elem ) {
5849                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
5850                                                 if ( !inplace ) {
5851                                                         result.push( elem );
5852                                                 }
5853                                         } else if ( inplace ) {
5854                                                 curLoop[i] = false;
5855                                         }
5856                                 }
5857                         }
5858
5859                         return false;
5860                 },
5861                 ID: function(match){
5862                         return match[1].replace(/\\/g, "");
5863                 },
5864                 TAG: function(match, curLoop){
5865                         return match[1].toLowerCase();
5866                 },
5867                 CHILD: function(match){
5868                         if ( match[1] === "nth" ) {
5869                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
5870                                 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
5871                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
5872                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
5873
5874                                 // calculate the numbers (first)n+(last) including if they are negative
5875                                 match[2] = (test[1] + (test[2] || 1)) - 0;
5876                                 match[3] = test[3] - 0;
5877                         }
5878
5879                         // TODO: Move to normal caching system
5880                         match[0] = done++;
5881
5882                         return match;
5883                 },
5884                 ATTR: function(match, curLoop, inplace, result, not, isXML){
5885                         var name = match[1].replace(/\\/g, "");
5886                         
5887                         if ( !isXML && Expr.attrMap[name] ) {
5888                                 match[1] = Expr.attrMap[name];
5889                         }
5890
5891                         if ( match[2] === "~=" ) {
5892                                 match[4] = " " + match[4] + " ";
5893                         }
5894
5895                         return match;
5896                 },
5897                 PSEUDO: function(match, curLoop, inplace, result, not){
5898                         if ( match[1] === "not" ) {
5899                                 // If we're dealing with a complex expression, or a simple one
5900                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
5901                                         match[3] = Sizzle(match[3], null, null, curLoop);
5902                                 } else {
5903                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
5904                                         if ( !inplace ) {
5905                                                 result.push.apply( result, ret );
5906                                         }
5907                                         return false;
5908                                 }
5909                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
5910                                 return true;
5911                         }
5912                         
5913                         return match;
5914                 },
5915                 POS: function(match){
5916                         match.unshift( true );
5917                         return match;
5918                 }
5919         },
5920         filters: {
5921                 enabled: function(elem){
5922                         return elem.disabled === false && elem.type !== "hidden";
5923                 },
5924                 disabled: function(elem){
5925                         return elem.disabled === true;
5926                 },
5927                 checked: function(elem){
5928                         return elem.checked === true;
5929                 },
5930                 selected: function(elem){
5931                         // Accessing this property makes selected-by-default
5932                         // options in Safari work properly
5933                         elem.parentNode.selectedIndex;
5934                         return elem.selected === true;
5935                 },
5936                 parent: function(elem){
5937                         return !!elem.firstChild;
5938                 },
5939                 empty: function(elem){
5940                         return !elem.firstChild;
5941                 },
5942                 has: function(elem, i, match){
5943                         return !!Sizzle( match[3], elem ).length;
5944                 },
5945                 header: function(elem){
5946                         return (/h\d/i).test( elem.nodeName );
5947                 },
5948                 text: function(elem){
5949                         return "text" === elem.type;
5950                 },
5951                 radio: function(elem){
5952                         return "radio" === elem.type;
5953                 },
5954                 checkbox: function(elem){
5955                         return "checkbox" === elem.type;
5956                 },
5957                 file: function(elem){
5958                         return "file" === elem.type;
5959                 },
5960                 password: function(elem){
5961                         return "password" === elem.type;
5962                 },
5963                 submit: function(elem){
5964                         return "submit" === elem.type;
5965                 },
5966                 image: function(elem){
5967                         return "image" === elem.type;
5968                 },
5969                 reset: function(elem){
5970                         return "reset" === elem.type;
5971                 },
5972                 button: function(elem){
5973                         return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
5974                 },
5975                 input: function(elem){
5976                         return (/input|select|textarea|button/i).test(elem.nodeName);
5977                 }
5978         },
5979         setFilters: {
5980                 first: function(elem, i){
5981                         return i === 0;
5982                 },
5983                 last: function(elem, i, match, array){
5984                         return i === array.length - 1;
5985                 },
5986                 even: function(elem, i){
5987                         return i % 2 === 0;
5988                 },
5989                 odd: function(elem, i){
5990                         return i % 2 === 1;
5991                 },
5992                 lt: function(elem, i, match){
5993                         return i < match[3] - 0;
5994                 },
5995                 gt: function(elem, i, match){
5996                         return i > match[3] - 0;
5997                 },
5998                 nth: function(elem, i, match){
5999                         return match[3] - 0 === i;
6000                 },
6001                 eq: function(elem, i, match){
6002                         return match[3] - 0 === i;
6003                 }
6004         },
6005         filter: {
6006                 PSEUDO: function(elem, match, i, array){
6007                         var name = match[1], filter = Expr.filters[ name ];
6008
6009                         if ( filter ) {
6010                                 return filter( elem, i, match, array );
6011                         } else if ( name === "contains" ) {
6012                                 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
6013                         } else if ( name === "not" ) {
6014                                 var not = match[3];
6015
6016                                 for ( var j = 0, l = not.length; j < l; j++ ) {
6017                                         if ( not[j] === elem ) {
6018                                                 return false;
6019                                         }
6020                                 }
6021
6022                                 return true;
6023                         } else {
6024                                 Sizzle.error( "Syntax error, unrecognized expression: " + name );
6025                         }
6026                 },
6027                 CHILD: function(elem, match){
6028                         var type = match[1], node = elem;
6029                         switch (type) {
6030                                 case 'only':
6031                                 case 'first':
6032                                         while ( (node = node.previousSibling) )  {
6033                                                 if ( node.nodeType === 1 ) { 
6034                                                         return false; 
6035                                                 }
6036                                         }
6037                                         if ( type === "first" ) { 
6038                                                 return true; 
6039                                         }
6040                                         node = elem;
6041                                 case 'last':
6042                                         while ( (node = node.nextSibling) )      {
6043                                                 if ( node.nodeType === 1 ) { 
6044                                                         return false; 
6045                                                 }
6046                                         }
6047                                         return true;
6048                                 case 'nth':
6049                                         var first = match[2], last = match[3];
6050
6051                                         if ( first === 1 && last === 0 ) {
6052                                                 return true;
6053                                         }
6054                                         
6055                                         var doneName = match[0],
6056                                                 parent = elem.parentNode;
6057         
6058                                         if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
6059                                                 var count = 0;
6060                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {
6061                                                         if ( node.nodeType === 1 ) {
6062                                                                 node.nodeIndex = ++count;
6063                                                         }
6064                                                 } 
6065                                                 parent.sizcache = doneName;
6066                                         }
6067                                         
6068                                         var diff = elem.nodeIndex - last;
6069                                         if ( first === 0 ) {
6070                                                 return diff === 0;
6071                                         } else {
6072                                                 return ( diff % first === 0 && diff / first >= 0 );
6073                                         }
6074                         }
6075                 },
6076                 ID: function(elem, match){
6077                         return elem.nodeType === 1 && elem.getAttribute("id") === match;
6078                 },
6079                 TAG: function(elem, match){
6080                         return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
6081                 },
6082                 CLASS: function(elem, match){
6083                         return (" " + (elem.className || elem.getAttribute("class")) + " ")
6084                                 .indexOf( match ) > -1;
6085                 },
6086                 ATTR: function(elem, match){
6087                         var name = match[1],
6088                                 result = Expr.attrHandle[ name ] ?
6089                                         Expr.attrHandle[ name ]( elem ) :
6090                                         elem[ name ] != null ?
6091                                                 elem[ name ] :
6092                                                 elem.getAttribute( name ),
6093                                 value = result + "",
6094                                 type = match[2],
6095                                 check = match[4];
6096
6097                         return result == null ?
6098                                 type === "!=" :
6099                                 type === "=" ?
6100                                 value === check :
6101                                 type === "*=" ?
6102                                 value.indexOf(check) >= 0 :
6103                                 type === "~=" ?
6104                                 (" " + value + " ").indexOf(check) >= 0 :
6105                                 !check ?
6106                                 value && result !== false :
6107                                 type === "!=" ?
6108                                 value !== check :
6109                                 type === "^=" ?
6110                                 value.indexOf(check) === 0 :
6111                                 type === "$=" ?
6112                                 value.substr(value.length - check.length) === check :
6113                                 type === "|=" ?
6114                                 value === check || value.substr(0, check.length + 1) === check + "-" :
6115                                 false;
6116                 },
6117                 POS: function(elem, match, i, array){
6118                         var name = match[2], filter = Expr.setFilters[ name ];
6119
6120                         if ( filter ) {
6121                                 return filter( elem, i, match, array );
6122                         }
6123                 }
6124         }
6125 };
6126
6127 var origPOS = Expr.match.POS,
6128         fescape = function(all, num){
6129                 return "\\" + (num - 0 + 1);
6130         };
6131
6132 for ( var type in Expr.match ) {
6133         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
6134         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
6135 }
6136
6137 var makeArray = function(array, results) {
6138         array = Array.prototype.slice.call( array, 0 );
6139
6140         if ( results ) {
6141                 results.push.apply( results, array );
6142                 return results;
6143         }
6144         
6145         return array;
6146 };
6147
6148 // Perform a simple check to determine if the browser is capable of
6149 // converting a NodeList to an array using builtin methods.
6150 // Also verifies that the returned array holds DOM nodes
6151 // (which is not the case in the Blackberry browser)
6152 try {
6153         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
6154
6155 // Provide a fallback method if it does not work
6156 } catch(e){
6157         makeArray = function(array, results) {
6158                 var ret = results || [], i = 0;
6159
6160                 if ( toString.call(array) === "[object Array]" ) {
6161                         Array.prototype.push.apply( ret, array );
6162                 } else {
6163                         if ( typeof array.length === "number" ) {
6164                                 for ( var l = array.length; i < l; i++ ) {
6165                                         ret.push( array[i] );
6166                                 }
6167                         } else {
6168                                 for ( ; array[i]; i++ ) {
6169                                         ret.push( array[i] );
6170                                 }
6171                         }
6172                 }
6173
6174                 return ret;
6175         };
6176 }
6177
6178 var sortOrder;
6179
6180 if ( document.documentElement.compareDocumentPosition ) {
6181         sortOrder = function( a, b ) {
6182                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
6183                         if ( a == b ) {
6184                                 hasDuplicate = true;
6185                         }
6186                         return a.compareDocumentPosition ? -1 : 1;
6187                 }
6188
6189                 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
6190                 if ( ret === 0 ) {
6191                         hasDuplicate = true;
6192                 }
6193                 return ret;
6194         };
6195 } else if ( "sourceIndex" in document.documentElement ) {
6196         sortOrder = function( a, b ) {
6197                 if ( !a.sourceIndex || !b.sourceIndex ) {
6198                         if ( a == b ) {
6199                                 hasDuplicate = true;
6200                         }
6201                         return a.sourceIndex ? -1 : 1;
6202                 }
6203
6204                 var ret = a.sourceIndex - b.sourceIndex;
6205                 if ( ret === 0 ) {
6206                         hasDuplicate = true;
6207                 }
6208                 return ret;
6209         };
6210 } else if ( document.createRange ) {
6211         sortOrder = function( a, b ) {
6212                 if ( !a.ownerDocument || !b.ownerDocument ) {
6213                         if ( a == b ) {
6214                                 hasDuplicate = true;
6215                         }
6216                         return a.ownerDocument ? -1 : 1;
6217                 }
6218
6219                 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
6220                 aRange.setStart(a, 0);
6221                 aRange.setEnd(a, 0);
6222                 bRange.setStart(b, 0);
6223                 bRange.setEnd(b, 0);
6224                 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
6225                 if ( ret === 0 ) {
6226                         hasDuplicate = true;
6227                 }
6228                 return ret;
6229         };
6230 }
6231
6232 // Utility function for retreiving the text value of an array of DOM nodes
6233 Sizzle.getText = function( elems ) {
6234         var ret = "", elem;
6235
6236         for ( var i = 0; elems[i]; i++ ) {
6237                 elem = elems[i];
6238
6239                 // Get the text from text nodes and CDATA nodes
6240                 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
6241                         ret += elem.nodeValue;
6242
6243                 // Traverse everything else, except comment nodes
6244                 } else if ( elem.nodeType !== 8 ) {
6245                         ret += Sizzle.getText( elem.childNodes );
6246                 }
6247         }
6248
6249         return ret;
6250 };
6251
6252 // Check to see if the browser returns elements by name when
6253 // querying by getElementById (and provide a workaround)
6254 (function(){
6255         // We're going to inject a fake input element with a specified name
6256         var form = document.createElement("div"),
6257                 id = "script" + (new Date()).getTime();
6258         form.innerHTML = "<a name='" + id + "'/>";
6259
6260         // Inject it into the root element, check its status, and remove it quickly
6261         var root = document.documentElement;
6262         root.insertBefore( form, root.firstChild );
6263
6264         // The workaround has to do additional checks after a getElementById
6265         // Which slows things down for other browsers (hence the branching)
6266         if ( document.getElementById( id ) ) {
6267                 Expr.find.ID = function(match, context, isXML){
6268                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
6269                                 var m = context.getElementById(match[1]);
6270                                 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
6271                         }
6272                 };
6273
6274                 Expr.filter.ID = function(elem, match){
6275                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
6276                         return elem.nodeType === 1 && node && node.nodeValue === match;
6277                 };
6278         }
6279
6280         root.removeChild( form );
6281         root = form = null; // release memory in IE
6282 })();
6283
6284 (function(){
6285         // Check to see if the browser returns only elements
6286         // when doing getElementsByTagName("*")
6287
6288         // Create a fake element
6289         var div = document.createElement("div");
6290         div.appendChild( document.createComment("") );
6291
6292         // Make sure no comments are found
6293         if ( div.getElementsByTagName("*").length > 0 ) {
6294                 Expr.find.TAG = function(match, context){
6295                         var results = context.getElementsByTagName(match[1]);
6296
6297                         // Filter out possible comments
6298                         if ( match[1] === "*" ) {
6299                                 var tmp = [];
6300
6301                                 for ( var i = 0; results[i]; i++ ) {
6302                                         if ( results[i].nodeType === 1 ) {
6303                                                 tmp.push( results[i] );
6304                                         }
6305                                 }
6306
6307                                 results = tmp;
6308                         }
6309
6310                         return results;
6311                 };
6312         }
6313
6314         // Check to see if an attribute returns normalized href attributes
6315         div.innerHTML = "<a href='#'></a>";
6316         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
6317                         div.firstChild.getAttribute("href") !== "#" ) {
6318                 Expr.attrHandle.href = function(elem){
6319                         return elem.getAttribute("href", 2);
6320                 };
6321         }
6322
6323         div = null; // release memory in IE
6324 })();
6325
6326 if ( document.querySelectorAll ) {
6327         (function(){
6328                 var oldSizzle = Sizzle, div = document.createElement("div");
6329                 div.innerHTML = "<p class='TEST'></p>";
6330
6331                 // Safari can't handle uppercase or unicode characters when
6332                 // in quirks mode.
6333                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
6334                         return;
6335                 }
6336         
6337                 Sizzle = function(query, context, extra, seed){
6338                         context = context || document;
6339
6340                         // Only use querySelectorAll on non-XML documents
6341                         // (ID selectors don't work in non-HTML documents)
6342                         if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
6343                                 try {
6344                                         return makeArray( context.querySelectorAll(query), extra );
6345                                 } catch(e){}
6346                         }
6347                 
6348                         return oldSizzle(query, context, extra, seed);
6349                 };
6350
6351                 for ( var prop in oldSizzle ) {
6352                         Sizzle[ prop ] = oldSizzle[ prop ];
6353                 }
6354
6355                 div = null; // release memory in IE
6356         })();
6357 }
6358
6359 (function(){
6360         var div = document.createElement("div");
6361
6362         div.innerHTML = "<div class='test e'></div><div class='test'></div>";
6363
6364         // Opera can't find a second classname (in 9.6)
6365         // Also, make sure that getElementsByClassName actually exists
6366         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
6367                 return;
6368         }
6369
6370         // Safari caches class attributes, doesn't catch changes (in 3.2)
6371         div.lastChild.className = "e";
6372
6373         if ( div.getElementsByClassName("e").length === 1 ) {
6374                 return;
6375         }
6376         
6377         Expr.order.splice(1, 0, "CLASS");
6378         Expr.find.CLASS = function(match, context, isXML) {
6379                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
6380                         return context.getElementsByClassName(match[1]);
6381                 }
6382         };
6383
6384         div = null; // release memory in IE
6385 })();
6386
6387 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6388         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6389                 var elem = checkSet[i];
6390                 if ( elem ) {
6391                         elem = elem[dir];
6392                         var match = false;
6393
6394                         while ( elem ) {
6395                                 if ( elem.sizcache === doneName ) {
6396                                         match = checkSet[elem.sizset];
6397                                         break;
6398                                 }
6399
6400                                 if ( elem.nodeType === 1 && !isXML ){
6401                                         elem.sizcache = doneName;
6402                                         elem.sizset = i;
6403                                 }
6404
6405                                 if ( elem.nodeName.toLowerCase() === cur ) {
6406                                         match = elem;
6407                                         break;
6408                                 }
6409
6410                                 elem = elem[dir];
6411                         }
6412
6413                         checkSet[i] = match;
6414                 }
6415         }
6416 }
6417
6418 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6419         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6420                 var elem = checkSet[i];
6421                 if ( elem ) {
6422                         elem = elem[dir];
6423                         var match = false;
6424
6425                         while ( elem ) {
6426                                 if ( elem.sizcache === doneName ) {
6427                                         match = checkSet[elem.sizset];
6428                                         break;
6429                                 }
6430
6431                                 if ( elem.nodeType === 1 ) {
6432                                         if ( !isXML ) {
6433                                                 elem.sizcache = doneName;
6434                                                 elem.sizset = i;
6435                                         }
6436                                         if ( typeof cur !== "string" ) {
6437                                                 if ( elem === cur ) {
6438                                                         match = true;
6439                                                         break;
6440                                                 }
6441
6442                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
6443                                                 match = elem;
6444                                                 break;
6445                                         }
6446                                 }
6447
6448                                 elem = elem[dir];
6449                         }
6450
6451                         checkSet[i] = match;
6452                 }
6453         }
6454 }
6455
6456 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
6457         return !!(a.compareDocumentPosition(b) & 16);
6458 } : function(a, b){
6459         return a !== b && (a.contains ? a.contains(b) : true);
6460 };
6461
6462 Sizzle.isXML = function(elem){
6463         // documentElement is verified for cases where it doesn't yet exist
6464         // (such as loading iframes in IE - #4833) 
6465         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
6466         return documentElement ? documentElement.nodeName !== "HTML" : false;
6467 };
6468
6469 var posProcess = function(selector, context){
6470         var tmpSet = [], later = "", match,
6471                 root = context.nodeType ? [context] : context;
6472
6473         // Position selectors must be done after the filter
6474         // And so must :not(positional) so we move all PSEUDOs to the end
6475         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
6476                 later += match[0];
6477                 selector = selector.replace( Expr.match.PSEUDO, "" );
6478         }
6479
6480         selector = Expr.relative[selector] ? selector + "*" : selector;
6481
6482         for ( var i = 0, l = root.length; i < l; i++ ) {
6483                 Sizzle( selector, root[i], tmpSet );
6484         }
6485
6486         return Sizzle.filter( later, tmpSet );
6487 };
6488
6489 // EXPOSE
6490
6491 window.tinymce.dom.Sizzle = Sizzle;
6492
6493 })();
6494
6495
6496 (function(tinymce) {
6497         // Shorten names
6498         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
6499
6500         tinymce.create('tinymce.dom.EventUtils', {
6501                 EventUtils : function() {
6502                         this.inits = [];
6503                         this.events = [];
6504                 },
6505
6506                 add : function(o, n, f, s) {
6507                         var cb, t = this, el = t.events, r;
6508
6509                         if (n instanceof Array) {
6510                                 r = [];
6511
6512                                 each(n, function(n) {
6513                                         r.push(t.add(o, n, f, s));
6514                                 });
6515
6516                                 return r;
6517                         }
6518
6519                         // Handle array
6520                         if (o && o.hasOwnProperty && o instanceof Array) {
6521                                 r = [];
6522
6523                                 each(o, function(o) {
6524                                         o = DOM.get(o);
6525                                         r.push(t.add(o, n, f, s));
6526                                 });
6527
6528                                 return r;
6529                         }
6530
6531                         o = DOM.get(o);
6532
6533                         if (!o)
6534                                 return;
6535
6536                         // Setup event callback
6537                         cb = function(e) {
6538                                 // Is all events disabled
6539                                 if (t.disabled)
6540                                         return;
6541
6542                                 e = e || window.event;
6543
6544                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
6545                                 if (e && isIE) {
6546                                         if (!e.target)
6547                                                 e.target = e.srcElement;
6548
6549                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility
6550                                         tinymce.extend(e, t._stoppers);
6551                                 }
6552
6553                                 if (!s)
6554                                         return f(e);
6555
6556                                 return f.call(s, e);
6557                         };
6558
6559                         if (n == 'unload') {
6560                                 tinymce.unloads.unshift({func : cb});
6561                                 return cb;
6562                         }
6563
6564                         if (n == 'init') {
6565                                 if (t.domLoaded)
6566                                         cb();
6567                                 else
6568                                         t.inits.push(cb);
6569
6570                                 return cb;
6571                         }
6572
6573                         // Store away listener reference
6574                         el.push({
6575                                 obj : o,
6576                                 name : n,
6577                                 func : f,
6578                                 cfunc : cb,
6579                                 scope : s
6580                         });
6581
6582                         t._add(o, n, cb);
6583
6584                         return f;
6585                 },
6586
6587                 remove : function(o, n, f) {
6588                         var t = this, a = t.events, s = false, r;
6589
6590                         // Handle array
6591                         if (o && o.hasOwnProperty && o instanceof Array) {
6592                                 r = [];
6593
6594                                 each(o, function(o) {
6595                                         o = DOM.get(o);
6596                                         r.push(t.remove(o, n, f));
6597                                 });
6598
6599                                 return r;
6600                         }
6601
6602                         o = DOM.get(o);
6603
6604                         each(a, function(e, i) {
6605                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
6606                                         a.splice(i, 1);
6607                                         t._remove(o, n, e.cfunc);
6608                                         s = true;
6609                                         return false;
6610                                 }
6611                         });
6612
6613                         return s;
6614                 },
6615
6616                 clear : function(o) {
6617                         var t = this, a = t.events, i, e;
6618
6619                         if (o) {
6620                                 o = DOM.get(o);
6621
6622                                 for (i = a.length - 1; i >= 0; i--) {
6623                                         e = a[i];
6624
6625                                         if (e.obj === o) {
6626                                                 t._remove(e.obj, e.name, e.cfunc);
6627                                                 e.obj = e.cfunc = null;
6628                                                 a.splice(i, 1);
6629                                         }
6630                                 }
6631                         }
6632                 },
6633
6634                 cancel : function(e) {
6635                         if (!e)
6636                                 return false;
6637
6638                         this.stop(e);
6639
6640                         return this.prevent(e);
6641                 },
6642
6643                 stop : function(e) {
6644                         if (e.stopPropagation)
6645                                 e.stopPropagation();
6646                         else
6647                                 e.cancelBubble = true;
6648
6649                         return false;
6650                 },
6651
6652                 prevent : function(e) {
6653                         if (e.preventDefault)
6654                                 e.preventDefault();
6655                         else
6656                                 e.returnValue = false;
6657
6658                         return false;
6659                 },
6660
6661                 destroy : function() {
6662                         var t = this;
6663
6664                         each(t.events, function(e, i) {
6665                                 t._remove(e.obj, e.name, e.cfunc);
6666                                 e.obj = e.cfunc = null;
6667                         });
6668
6669                         t.events = [];
6670                         t = null;
6671                 },
6672
6673                 _add : function(o, n, f) {
6674                         if (o.attachEvent)
6675                                 o.attachEvent('on' + n, f);
6676                         else if (o.addEventListener)
6677                                 o.addEventListener(n, f, false);
6678                         else
6679                                 o['on' + n] = f;
6680                 },
6681
6682                 _remove : function(o, n, f) {
6683                         if (o) {
6684                                 try {
6685                                         if (o.detachEvent)
6686                                                 o.detachEvent('on' + n, f);
6687                                         else if (o.removeEventListener)
6688                                                 o.removeEventListener(n, f, false);
6689                                         else
6690                                                 o['on' + n] = null;
6691                                 } catch (ex) {
6692                                         // Might fail with permission denined on IE so we just ignore that
6693                                 }
6694                         }
6695                 },
6696
6697                 _pageInit : function(win) {
6698                         var t = this;
6699
6700                         // Keep it from running more than once
6701                         if (t.domLoaded)
6702                                 return;
6703
6704                         t.domLoaded = true;
6705
6706                         each(t.inits, function(c) {
6707                                 c();
6708                         });
6709
6710                         t.inits = [];
6711                 },
6712
6713                 _wait : function(win) {
6714                         var t = this, doc = win.document;
6715
6716                         // No need since the document is already loaded
6717                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
6718                                 t.domLoaded = 1;
6719                                 return;
6720                         }
6721
6722                         // Use IE method
6723                         if (doc.attachEvent) {
6724                                 doc.attachEvent("onreadystatechange", function() {
6725                                         if (doc.readyState === "complete") {
6726                                                 doc.detachEvent("onreadystatechange", arguments.callee);
6727                                                 t._pageInit(win);
6728                                         }
6729                                 });
6730
6731                                 if (doc.documentElement.doScroll && win == win.top) {
6732                                         (function() {
6733                                                 if (t.domLoaded)
6734                                                         return;
6735
6736                                                 try {
6737                                                         // If IE is used, use the trick by Diego Perini
6738                                                         // http://javascript.nwbox.com/IEContentLoaded/
6739                                                         doc.documentElement.doScroll("left");
6740                                                 } catch (ex) {
6741                                                         setTimeout(arguments.callee, 0);
6742                                                         return;
6743                                                 }
6744
6745                                                 t._pageInit(win);
6746                                         })();
6747                                 }
6748                         } else if (doc.addEventListener) {
6749                                 t._add(win, 'DOMContentLoaded', function() {
6750                                         t._pageInit(win);
6751                                 });
6752                         }
6753
6754                         t._add(win, 'load', function() {
6755                                 t._pageInit(win);
6756                         });
6757                 },
6758
6759                 _stoppers : {
6760                         preventDefault : function() {
6761                                 this.returnValue = false;
6762                         },
6763
6764                         stopPropagation : function() {
6765                                 this.cancelBubble = true;
6766                         }
6767                 }
6768         });
6769
6770         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
6771
6772         // Dispatch DOM content loaded event for IE and Safari
6773         Event._wait(window);
6774
6775         tinymce.addUnload(function() {
6776                 Event.destroy();
6777         });
6778 })(tinymce);
6779
6780 (function(tinymce) {
6781         tinymce.dom.Element = function(id, settings) {
6782                 var t = this, dom, el;
6783
6784                 t.settings = settings = settings || {};
6785                 t.id = id;
6786                 t.dom = dom = settings.dom || tinymce.DOM;
6787
6788                 // Only IE leaks DOM references, this is a lot faster
6789                 if (!tinymce.isIE)
6790                         el = dom.get(t.id);
6791
6792                 tinymce.each(
6793                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
6794                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
6795                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
6796                                 'isHidden,setHTML,get').split(/,/)
6797                         , function(k) {
6798                                 t[k] = function() {
6799                                         var a = [id], i;
6800
6801                                         for (i = 0; i < arguments.length; i++)
6802                                                 a.push(arguments[i]);
6803
6804                                         a = dom[k].apply(dom, a);
6805                                         t.update(k);
6806
6807                                         return a;
6808                                 };
6809                 });
6810
6811                 tinymce.extend(t, {
6812                         on : function(n, f, s) {
6813                                 return tinymce.dom.Event.add(t.id, n, f, s);
6814                         },
6815
6816                         getXY : function() {
6817                                 return {
6818                                         x : parseInt(t.getStyle('left')),
6819                                         y : parseInt(t.getStyle('top'))
6820                                 };
6821                         },
6822
6823                         getSize : function() {
6824                                 var n = dom.get(t.id);
6825
6826                                 return {
6827                                         w : parseInt(t.getStyle('width') || n.clientWidth),
6828                                         h : parseInt(t.getStyle('height') || n.clientHeight)
6829                                 };
6830                         },
6831
6832                         moveTo : function(x, y) {
6833                                 t.setStyles({left : x, top : y});
6834                         },
6835
6836                         moveBy : function(x, y) {
6837                                 var p = t.getXY();
6838
6839                                 t.moveTo(p.x + x, p.y + y);
6840                         },
6841
6842                         resizeTo : function(w, h) {
6843                                 t.setStyles({width : w, height : h});
6844                         },
6845
6846                         resizeBy : function(w, h) {
6847                                 var s = t.getSize();
6848
6849                                 t.resizeTo(s.w + w, s.h + h);
6850                         },
6851
6852                         update : function(k) {
6853                                 var b;
6854
6855                                 if (tinymce.isIE6 && settings.blocker) {
6856                                         k = k || '';
6857
6858                                         // Ignore getters
6859                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
6860                                                 return;
6861
6862                                         // Remove blocker on remove
6863                                         if (k == 'remove') {
6864                                                 dom.remove(t.blocker);
6865                                                 return;
6866                                         }
6867
6868                                         if (!t.blocker) {
6869                                                 t.blocker = dom.uniqueId();
6870                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
6871                                                 dom.setStyle(b, 'opacity', 0);
6872                                         } else
6873                                                 b = dom.get(t.blocker);
6874
6875                                         dom.setStyles(b, {
6876                                                 left : t.getStyle('left', 1),
6877                                                 top : t.getStyle('top', 1),
6878                                                 width : t.getStyle('width', 1),
6879                                                 height : t.getStyle('height', 1),
6880                                                 display : t.getStyle('display', 1),
6881                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
6882                                         });
6883                                 }
6884                         }
6885                 });
6886         };
6887 })(tinymce);
6888
6889 (function(tinymce) {
6890         function trimNl(s) {
6891                 return s.replace(/[\n\r]+/g, '');
6892         };
6893
6894         // Shorten names
6895         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
6896
6897         tinymce.create('tinymce.dom.Selection', {
6898                 Selection : function(dom, win, serializer) {
6899                         var t = this;
6900
6901                         t.dom = dom;
6902                         t.win = win;
6903                         t.serializer = serializer;
6904
6905                         // Add events
6906                         each([
6907                                 'onBeforeSetContent',
6908
6909                                 'onBeforeGetContent',
6910
6911                                 'onSetContent',
6912
6913                                 'onGetContent'
6914                         ], function(e) {
6915                                 t[e] = new tinymce.util.Dispatcher(t);
6916                         });
6917
6918                         // No W3C Range support
6919                         if (!t.win.getSelection)
6920                                 t.tridentSel = new tinymce.dom.TridentSelection(t);
6921
6922                         if (tinymce.isIE && dom.boxModel)
6923                                 this._fixIESelection();
6924
6925                         // Prevent leaks
6926                         tinymce.addUnload(t.destroy, t);
6927                 },
6928
6929                 getContent : function(s) {
6930                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
6931
6932                         s = s || {};
6933                         wb = wa = '';
6934                         s.get = true;
6935                         s.format = s.format || 'html';
6936                         t.onBeforeGetContent.dispatch(t, s);
6937
6938                         if (s.format == 'text')
6939                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
6940
6941                         if (r.cloneContents) {
6942                                 n = r.cloneContents();
6943
6944                                 if (n)
6945                                         e.appendChild(n);
6946                         } else if (is(r.item) || is(r.htmlText))
6947                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
6948                         else
6949                                 e.innerHTML = r.toString();
6950
6951                         // Keep whitespace before and after
6952                         if (/^\s/.test(e.innerHTML))
6953                                 wb = ' ';
6954
6955                         if (/\s+$/.test(e.innerHTML))
6956                                 wa = ' ';
6957
6958                         s.getInner = true;
6959
6960                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
6961                         t.onGetContent.dispatch(t, s);
6962
6963                         return s.content;
6964                 },
6965
6966                 setContent : function(content, args) {
6967                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
6968
6969                         args = args || {format : 'html'};
6970                         args.set = true;
6971                         content = args.content = content;
6972
6973                         // Dispatch before set content event
6974                         if (!args.no_events)
6975                                 self.onBeforeSetContent.dispatch(self, args);
6976
6977                         content = args.content;
6978
6979                         if (rng.insertNode) {
6980                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
6981                                 content += '<span id="__caret">_</span>';
6982
6983                                 // Delete and insert new node
6984                                 if (rng.startContainer == doc && rng.endContainer == doc) {
6985                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
6986                                         doc.body.innerHTML = content;
6987                                 } else {
6988                                         rng.deleteContents();
6989
6990                                         if (doc.body.childNodes.length == 0) {
6991                                                 doc.body.innerHTML = content;
6992                                         } else {
6993                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
6994                                                 if (rng.createContextualFragment) {
6995                                                         rng.insertNode(rng.createContextualFragment(content));
6996                                                 } else {
6997                                                         // Fake createContextualFragment call in IE 9
6998                                                         frag = doc.createDocumentFragment();
6999                                                         temp = doc.createElement('div');
7000
7001                                                         frag.appendChild(temp);
7002                                                         temp.outerHTML = content;
7003
7004                                                         rng.insertNode(frag);
7005                                                 }
7006                                         }
7007                                 }
7008
7009                                 // Move to caret marker
7010                                 caretNode = self.dom.get('__caret');
7011
7012                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
7013                                 rng = doc.createRange();
7014                                 rng.setStartBefore(caretNode);
7015                                 rng.setEndBefore(caretNode);
7016                                 self.setRng(rng);
7017
7018                                 // Remove the caret position
7019                                 self.dom.remove('__caret');
7020                                 self.setRng(rng);
7021                         } else {
7022                                 if (rng.item) {
7023                                         // Delete content and get caret text selection
7024                                         doc.execCommand('Delete', false, null);
7025                                         rng = self.getRng();
7026                                 }
7027
7028                                 rng.pasteHTML(content);
7029                         }
7030
7031                         // Dispatch set content event
7032                         if (!args.no_events)
7033                                 self.onSetContent.dispatch(self, args);
7034                 },
7035
7036                 getStart : function() {
7037                         var rng = this.getRng(), startElement, parentElement, checkRng, node;
7038
7039                         if (rng.duplicate || rng.item) {
7040                                 // Control selection, return first item
7041                                 if (rng.item)
7042                                         return rng.item(0);
7043
7044                                 // Get start element
7045                                 checkRng = rng.duplicate();
7046                                 checkRng.collapse(1);
7047                                 startElement = checkRng.parentElement();
7048
7049                                 // Check if range parent is inside the start element, then return the inner parent element
7050                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7051                                 parentElement = node = rng.parentElement();
7052                                 while (node = node.parentNode) {
7053                                         if (node == startElement) {
7054                                                 startElement = parentElement;
7055                                                 break;
7056                                         }
7057                                 }
7058
7059                                 return startElement;
7060                         } else {
7061                                 startElement = rng.startContainer;
7062
7063                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
7064                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
7065
7066                                 if (startElement && startElement.nodeType == 3)
7067                                         return startElement.parentNode;
7068
7069                                 return startElement;
7070                         }
7071                 },
7072
7073                 getEnd : function() {
7074                         var t = this, r = t.getRng(), e, eo;
7075
7076                         if (r.duplicate || r.item) {
7077                                 if (r.item)
7078                                         return r.item(0);
7079
7080                                 r = r.duplicate();
7081                                 r.collapse(0);
7082                                 e = r.parentElement();
7083
7084                                 if (e && e.nodeName == 'BODY')
7085                                         return e.lastChild || e;
7086
7087                                 return e;
7088                         } else {
7089                                 e = r.endContainer;
7090                                 eo = r.endOffset;
7091
7092                                 if (e.nodeType == 1 && e.hasChildNodes())
7093                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];
7094
7095                                 if (e && e.nodeType == 3)
7096                                         return e.parentNode;
7097
7098                                 return e;
7099                         }
7100                 },
7101
7102                 getBookmark : function(type, normalized) {
7103                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
7104
7105                         function findIndex(name, element) {
7106                                 var index = 0;
7107
7108                                 each(dom.select(name), function(node, i) {
7109                                         if (node == element)
7110                                                 index = i;
7111                                 });
7112
7113                                 return index;
7114                         };
7115
7116                         if (type == 2) {
7117                                 function getLocation() {
7118                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
7119
7120                                         function getPoint(rng, start) {
7121                                                 var container = rng[start ? 'startContainer' : 'endContainer'],
7122                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
7123
7124                                                 if (container.nodeType == 3) {
7125                                                         if (normalized) {
7126                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
7127                                                                         offset += node.nodeValue.length;
7128                                                         }
7129
7130                                                         point.push(offset);
7131                                                 } else {
7132                                                         childNodes = container.childNodes;
7133
7134                                                         if (offset >= childNodes.length && childNodes.length) {
7135                                                                 after = 1;
7136                                                                 offset = Math.max(0, childNodes.length - 1);
7137                                                         }
7138
7139                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
7140                                                 }
7141
7142                                                 for (; container && container != root; container = container.parentNode)
7143                                                         point.push(t.dom.nodeIndex(container, normalized));
7144
7145                                                 return point;
7146                                         };
7147
7148                                         bookmark.start = getPoint(rng, true);
7149
7150                                         if (!t.isCollapsed())
7151                                                 bookmark.end = getPoint(rng);
7152
7153                                         return bookmark;
7154                                 };
7155
7156                                 return getLocation();
7157                         }
7158
7159                         // Handle simple range
7160                         if (type)
7161                                 return {rng : t.getRng()};
7162
7163                         rng = t.getRng();
7164                         id = dom.uniqueId();
7165                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();
7166                         styles = 'overflow:hidden;line-height:0px';
7167
7168                         // Explorer method
7169                         if (rng.duplicate || rng.item) {
7170                                 // Text selection
7171                                 if (!rng.item) {
7172                                         rng2 = rng.duplicate();
7173
7174                                         try {
7175                                                 // Insert start marker
7176                                                 rng.collapse();
7177                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
7178
7179                                                 // Insert end marker
7180                                                 if (!collapsed) {
7181                                                         rng2.collapse(false);
7182
7183                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
7184                                                         rng.moveToElementText(rng2.parentElement());
7185                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)
7186                                                                 rng2.move('character', -1);
7187
7188                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
7189                                                 }
7190                                         } catch (ex) {
7191                                                 // IE might throw unspecified error so lets ignore it
7192                                                 return null;
7193                                         }
7194                                 } else {
7195                                         // Control selection
7196                                         element = rng.item(0);
7197                                         name = element.nodeName;
7198
7199                                         return {name : name, index : findIndex(name, element)};
7200                                 }
7201                         } else {
7202                                 element = t.getNode();
7203                                 name = element.nodeName;
7204                                 if (name == 'IMG')
7205                                         return {name : name, index : findIndex(name, element)};
7206
7207                                 // W3C method
7208                                 rng2 = rng.cloneRange();
7209
7210                                 // Insert end marker
7211                                 if (!collapsed) {
7212                                         rng2.collapse(false);
7213                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
7214                                 }
7215
7216                                 rng.collapse(true);
7217                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
7218                         }
7219
7220                         t.moveToBookmark({id : id, keep : 1});
7221
7222                         return {id : id};
7223                 },
7224
7225                 moveToBookmark : function(bookmark) {
7226                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
7227
7228                         // Clear selection cache
7229                         if (t.tridentSel)
7230                                 t.tridentSel.destroy();
7231
7232                         if (bookmark) {
7233                                 if (bookmark.start) {
7234                                         rng = dom.createRng();
7235                                         root = dom.getRoot();
7236
7237                                         function setEndPoint(start) {
7238                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
7239
7240                                                 if (point) {
7241                                                         offset = point[0];
7242
7243                                                         // Find container node
7244                                                         for (node = root, i = point.length - 1; i >= 1; i--) {
7245                                                                 children = node.childNodes;
7246
7247                                                                 if (point[i] > children.length - 1)
7248                                                                         return;
7249
7250                                                                 node = children[point[i]];
7251                                                         }
7252
7253                                                         // Move text offset to best suitable location
7254                                                         if (node.nodeType === 3)
7255                                                                 offset = Math.min(point[0], node.nodeValue.length);
7256
7257                                                         // Move element offset to best suitable location
7258                                                         if (node.nodeType === 1)
7259                                                                 offset = Math.min(point[0], node.childNodes.length);
7260
7261                                                         // Set offset within container node
7262                                                         if (start)
7263                                                                 rng.setStart(node, offset);
7264                                                         else
7265                                                                 rng.setEnd(node, offset);
7266                                                 }
7267
7268                                                 return true;
7269                                         };
7270
7271                                         if (setEndPoint(true) && setEndPoint()) {
7272                                                 t.setRng(rng);
7273                                         }
7274                                 } else if (bookmark.id) {
7275                                         function restoreEndPoint(suffix) {
7276                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
7277
7278                                                 if (marker) {
7279                                                         node = marker.parentNode;
7280
7281                                                         if (suffix == 'start') {
7282                                                                 if (!keep) {
7283                                                                         idx = dom.nodeIndex(marker);
7284                                                                 } else {
7285                                                                         node = marker.firstChild;
7286                                                                         idx = 1;
7287                                                                 }
7288
7289                                                                 startContainer = endContainer = node;
7290                                                                 startOffset = endOffset = idx;
7291                                                         } else {
7292                                                                 if (!keep) {
7293                                                                         idx = dom.nodeIndex(marker);
7294                                                                 } else {
7295                                                                         node = marker.firstChild;
7296                                                                         idx = 1;
7297                                                                 }
7298
7299                                                                 endContainer = node;
7300                                                                 endOffset = idx;
7301                                                         }
7302
7303                                                         if (!keep) {
7304                                                                 prev = marker.previousSibling;
7305                                                                 next = marker.nextSibling;
7306
7307                                                                 // Remove all marker text nodes
7308                                                                 each(tinymce.grep(marker.childNodes), function(node) {
7309                                                                         if (node.nodeType == 3)
7310                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
7311                                                                 });
7312
7313                                                                 // Remove marker but keep children if for example contents where inserted into the marker
7314                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
7315                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))
7316                                                                         dom.remove(marker, 1);
7317
7318                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
7319                                                                 // 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
7320                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
7321                                                                         idx = prev.nodeValue.length;
7322                                                                         prev.appendData(next.nodeValue);
7323                                                                         dom.remove(next);
7324
7325                                                                         if (suffix == 'start') {
7326                                                                                 startContainer = endContainer = prev;
7327                                                                                 startOffset = endOffset = idx;
7328                                                                         } else {
7329                                                                                 endContainer = prev;
7330                                                                                 endOffset = idx;
7331                                                                         }
7332                                                                 }
7333                                                         }
7334                                                 }
7335                                         };
7336
7337                                         function addBogus(node) {
7338                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
7339                                                 if (dom.isBlock(node) && !node.innerHTML)
7340                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
7341
7342                                                 return node;
7343                                         };
7344
7345                                         // Restore start/end points
7346                                         restoreEndPoint('start');
7347                                         restoreEndPoint('end');
7348
7349                                         if (startContainer) {
7350                                                 rng = dom.createRng();
7351                                                 rng.setStart(addBogus(startContainer), startOffset);
7352                                                 rng.setEnd(addBogus(endContainer), endOffset);
7353                                                 t.setRng(rng);
7354                                         }
7355                                 } else if (bookmark.name) {
7356                                         t.select(dom.select(bookmark.name)[bookmark.index]);
7357                                 } else if (bookmark.rng)
7358                                         t.setRng(bookmark.rng);
7359                         }
7360                 },
7361
7362                 select : function(node, content) {
7363                         var t = this, dom = t.dom, rng = dom.createRng(), idx;
7364
7365                         if (node) {
7366                                 idx = dom.nodeIndex(node);
7367                                 rng.setStart(node.parentNode, idx);
7368                                 rng.setEnd(node.parentNode, idx + 1);
7369
7370                                 // Find first/last text node or BR element
7371                                 if (content) {
7372                                         function setPoint(node, start) {
7373                                                 var walker = new tinymce.dom.TreeWalker(node, node);
7374
7375                                                 do {
7376                                                         // Text node
7377                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
7378                                                                 if (start)
7379                                                                         rng.setStart(node, 0);
7380                                                                 else
7381                                                                         rng.setEnd(node, node.nodeValue.length);
7382
7383                                                                 return;
7384                                                         }
7385
7386                                                         // BR element
7387                                                         if (node.nodeName == 'BR') {
7388                                                                 if (start)
7389                                                                         rng.setStartBefore(node);
7390                                                                 else
7391                                                                         rng.setEndBefore(node);
7392
7393                                                                 return;
7394                                                         }
7395                                                 } while (node = (start ? walker.next() : walker.prev()));
7396                                         };
7397
7398                                         setPoint(node, 1);
7399                                         setPoint(node);
7400                                 }
7401
7402                                 t.setRng(rng);
7403                         }
7404
7405                         return node;
7406                 },
7407
7408                 isCollapsed : function() {
7409                         var t = this, r = t.getRng(), s = t.getSel();
7410
7411                         if (!r || r.item)
7412                                 return false;
7413
7414                         if (r.compareEndPoints)
7415                                 return r.compareEndPoints('StartToEnd', r) === 0;
7416
7417                         return !s || r.collapsed;
7418                 },
7419
7420                 collapse : function(to_start) {
7421                         var self = this, rng = self.getRng(), node;
7422
7423                         // Control range on IE
7424                         if (rng.item) {
7425                                 node = rng.item(0);
7426                                 rng = self.win.document.body.createTextRange();
7427                                 rng.moveToElementText(node);
7428                         }
7429
7430                         rng.collapse(!!to_start);
7431                         self.setRng(rng);
7432                 },
7433
7434                 getSel : function() {
7435                         var t = this, w = this.win;
7436
7437                         return w.getSelection ? w.getSelection() : w.document.selection;
7438                 },
7439
7440                 getRng : function(w3c) {
7441                         var t = this, s, r, elm, doc = t.win.document;
7442
7443                         // Found tridentSel object then we need to use that one
7444                         if (w3c && t.tridentSel)
7445                                 return t.tridentSel.getRangeAt(0);
7446
7447                         try {
7448                                 if (s = t.getSel())
7449                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
7450                         } catch (ex) {
7451                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
7452                         }
7453
7454                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
7455                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
7456                                 elm = doc.selection.createRange().item(0);
7457                                 r = doc.createRange();
7458                                 r.setStartBefore(elm);
7459                                 r.setEndAfter(elm);
7460                         }
7461
7462                         // No range found then create an empty one
7463                         // This can occur when the editor is placed in a hidden container element on Gecko
7464                         // Or on IE when there was an exception
7465                         if (!r)
7466                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
7467
7468                         if (t.selectedRange && t.explicitRange) {
7469                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
7470                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
7471                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
7472                                         r = t.explicitRange;
7473                                 } else {
7474                                         t.selectedRange = null;
7475                                         t.explicitRange = null;
7476                                 }
7477                         }
7478
7479                         return r;
7480                 },
7481
7482                 setRng : function(r) {
7483                         var s, t = this;
7484                         
7485                         if (!t.tridentSel) {
7486                                 s = t.getSel();
7487
7488                                 if (s) {
7489                                         t.explicitRange = r;
7490
7491                                         try {
7492                                                 s.removeAllRanges();
7493                                         } catch (ex) {
7494                                                 // IE9 might throw errors here don't know why
7495                                         }
7496
7497                                         s.addRange(r);
7498                                         t.selectedRange = s.getRangeAt(0);
7499                                 }
7500                         } else {
7501                                 // Is W3C Range
7502                                 if (r.cloneRange) {
7503                                         t.tridentSel.addRange(r);
7504                                         return;
7505                                 }
7506
7507                                 // Is IE specific range
7508                                 try {
7509                                         r.select();
7510                                 } catch (ex) {
7511                                         // Needed for some odd IE bug #1843306
7512                                 }
7513                         }
7514                 },
7515
7516                 setNode : function(n) {
7517                         var t = this;
7518
7519                         t.setContent(t.dom.getOuterHTML(n));
7520
7521                         return n;
7522                 },
7523
7524                 getNode : function() {
7525                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
7526
7527                         // Range maybe lost after the editor is made visible again
7528                         if (!rng)
7529                                 return t.dom.getRoot();
7530
7531                         if (rng.setStart) {
7532                                 elm = rng.commonAncestorContainer;
7533
7534                                 // Handle selection a image or other control like element such as anchors
7535                                 if (!rng.collapsed) {
7536                                         if (rng.startContainer == rng.endContainer) {
7537                                                 if (rng.endOffset - rng.startOffset < 2) {
7538                                                         if (rng.startContainer.hasChildNodes())
7539                                                                 elm = rng.startContainer.childNodes[rng.startOffset];
7540                                                 }
7541                                         }
7542
7543                                         // If the anchor node is a element instead of a text node then return this element
7544                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
7545                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
7546
7547                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
7548                                         // This happens when you double click an underlined word in FireFox.
7549                                         if (start.nodeType === 3 && end.nodeType === 3) {
7550                                                 function skipEmptyTextNodes(n, forwards) {
7551                                                         var orig = n;
7552                                                         while (n && n.nodeType === 3 && n.length === 0) {
7553                                                                 n = forwards ? n.nextSibling : n.previousSibling;
7554                                                         }
7555                                                         return n || orig;
7556                                                 }
7557                                                 if (start.length === rng.startOffset) {
7558                                                         start = skipEmptyTextNodes(start.nextSibling, true);
7559                                                 } else {
7560                                                         start = start.parentNode;
7561                                                 }
7562                                                 if (rng.endOffset === 0) {
7563                                                         end = skipEmptyTextNodes(end.previousSibling, false);
7564                                                 } else {
7565                                                         end = end.parentNode;
7566                                                 }
7567
7568                                                 if (start && start === end)
7569                                                         return start;
7570                                         }
7571                                 }
7572
7573                                 if (elm && elm.nodeType == 3)
7574                                         return elm.parentNode;
7575
7576                                 return elm;
7577                         }
7578
7579                         return rng.item ? rng.item(0) : rng.parentElement();
7580                 },
7581
7582                 getSelectedBlocks : function(st, en) {
7583                         var t = this, dom = t.dom, sb, eb, n, bl = [];
7584
7585                         sb = dom.getParent(st || t.getStart(), dom.isBlock);
7586                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);
7587
7588                         if (sb)
7589                                 bl.push(sb);
7590
7591                         if (sb && eb && sb != eb) {
7592                                 n = sb;
7593
7594                                 while ((n = n.nextSibling) && n != eb) {
7595                                         if (dom.isBlock(n))
7596                                                 bl.push(n);
7597                                 }
7598                         }
7599
7600                         if (eb && sb != eb)
7601                                 bl.push(eb);
7602
7603                         return bl;
7604                 },
7605
7606                 destroy : function(s) {
7607                         var t = this;
7608
7609                         t.win = null;
7610
7611                         if (t.tridentSel)
7612                                 t.tridentSel.destroy();
7613
7614                         // Manual destroy then remove unload handler
7615                         if (!s)
7616                                 tinymce.removeUnload(t.destroy);
7617                 },
7618
7619                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
7620                 _fixIESelection : function() {
7621                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
7622
7623                         // Make HTML element unselectable since we are going to handle selection by hand
7624                         doc.documentElement.unselectable = true;
7625
7626                         // Return range from point or null if it failed
7627                         function rngFromPoint(x, y) {
7628                                 var rng = body.createTextRange();
7629
7630                                 try {
7631                                         rng.moveToPoint(x, y);
7632                                 } catch (ex) {
7633                                         // IE sometimes throws and exception, so lets just ignore it
7634                                         rng = null;
7635                                 }
7636
7637                                 return rng;
7638                         };
7639
7640                         // Fires while the selection is changing
7641                         function selectionChange(e) {
7642                                 var pointRng;
7643
7644                                 // Check if the button is down or not
7645                                 if (e.button) {
7646                                         // Create range from mouse position
7647                                         pointRng = rngFromPoint(e.x, e.y);
7648
7649                                         if (pointRng) {
7650                                                 // Check if pointRange is before/after selection then change the endPoint
7651                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
7652                                                         pointRng.setEndPoint('StartToStart', startRng);
7653                                                 else
7654                                                         pointRng.setEndPoint('EndToEnd', startRng);
7655
7656                                                 pointRng.select();
7657                                         }
7658                                 } else
7659                                         endSelection();
7660                         }
7661
7662                         // Removes listeners
7663                         function endSelection() {
7664                                 var rng = doc.selection.createRange();
7665
7666                                 // If the range is collapsed then use the last start range
7667                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
7668                                         startRng.select();
7669
7670                                 dom.unbind(doc, 'mouseup', endSelection);
7671                                 dom.unbind(doc, 'mousemove', selectionChange);
7672                                 startRng = started = 0;
7673                         };
7674
7675                         // Detect when user selects outside BODY
7676                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
7677                                 if (e.target.nodeName === 'HTML') {
7678                                         if (started)
7679                                                 endSelection();
7680
7681                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
7682                                         htmlElm = doc.documentElement;
7683                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)
7684                                                 return;
7685
7686                                         started = 1;
7687                                         // Setup start position
7688                                         startRng = rngFromPoint(e.x, e.y);
7689                                         if (startRng) {
7690                                                 // Listen for selection change events
7691                                                 dom.bind(doc, 'mouseup', endSelection);
7692                                                 dom.bind(doc, 'mousemove', selectionChange);
7693
7694                                                 dom.win.focus();
7695                                                 startRng.select();
7696                                         }
7697                                 }
7698                         });
7699                 }
7700         });
7701 })(tinymce);
7702
7703 (function(tinymce) {
7704         tinymce.dom.Serializer = function(settings, dom, schema) {
7705                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
7706
7707                 // Support the old apply_source_formatting option
7708                 if (!settings.apply_source_formatting)
7709                         settings.indent = false;
7710
7711                 settings.remove_trailing_brs = true;
7712
7713                 // Default DOM and Schema if they are undefined
7714                 dom = dom || tinymce.DOM;
7715                 schema = schema || new tinymce.html.Schema(settings);
7716                 settings.entity_encoding = settings.entity_encoding || 'named';
7717
7718                 onPreProcess = new tinymce.util.Dispatcher(self);
7719
7720                 onPostProcess = new tinymce.util.Dispatcher(self);
7721
7722                 htmlParser = new tinymce.html.DomParser(settings, schema);
7723
7724                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
7725                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
7726                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
7727
7728                         while (i--) {
7729                                 node = nodes[i];
7730
7731                                 value = node.attributes.map[internalName];
7732                                 if (value !== undef) {
7733                                         // Set external name to internal value and remove internal
7734                                         node.attr(name, value.length > 0 ? value : null);
7735                                         node.attr(internalName, null);
7736                                 } else {
7737                                         // No internal attribute found then convert the value we have in the DOM
7738                                         value = node.attributes.map[name];
7739
7740                                         if (name === "style")
7741                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
7742                                         else if (urlConverter)
7743                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
7744
7745                                         node.attr(name, value.length > 0 ? value : null);
7746                                 }
7747                         }
7748                 });
7749
7750                 // Remove internal classes mceItem<..>
7751                 htmlParser.addAttributeFilter('class', function(nodes, name) {
7752                         var i = nodes.length, node, value;
7753
7754                         while (i--) {
7755                                 node = nodes[i];
7756                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
7757                                 node.attr('class', value.length > 0 ? value : null);
7758                         }
7759                 });
7760
7761                 // Remove bookmark elements
7762                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
7763                         var i = nodes.length, node;
7764
7765                         while (i--) {
7766                                 node = nodes[i];
7767
7768                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
7769                                         node.remove();
7770                         }
7771                 });
7772
7773                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
7774                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
7775                         var i = nodes.length, node, value;
7776
7777                         function trim(value) {
7778                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
7779                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
7780                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
7781                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
7782                         };
7783
7784                         while (i--) {
7785                                 node = nodes[i];
7786                                 value = node.firstChild ? node.firstChild.value : '';
7787
7788                                 if (name === "script") {
7789                                         // Remove mce- prefix from script elements
7790                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
7791
7792                                         if (value.length > 0)
7793                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
7794                                 } else {
7795                                         if (value.length > 0)
7796                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
7797                                 }
7798                         }
7799                 });
7800
7801                 // Convert comments to cdata and handle protected comments
7802                 htmlParser.addNodeFilter('#comment', function(nodes, name) {
7803                         var i = nodes.length, node;
7804
7805                         while (i--) {
7806                                 node = nodes[i];
7807
7808                                 if (node.value.indexOf('[CDATA[') === 0) {
7809                                         node.name = '#cdata';
7810                                         node.type = 4;
7811                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
7812                                 } else if (node.value.indexOf('mce:protected ') === 0) {
7813                                         node.name = "#text";
7814                                         node.type = 3;
7815                                         node.raw = true;
7816                                         node.value = unescape(node.value).substr(14);
7817                                 }
7818                         }
7819                 });
7820
7821                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
7822                         var i = nodes.length, node;
7823
7824                         while (i--) {
7825                                 node = nodes[i];
7826                                 if (node.type === 7)
7827                                         node.remove();
7828                                 else if (node.type === 1) {
7829                                         if (name === "input" && !("type" in node.attributes.map))
7830                                                 node.attr('type', 'text');
7831                                 }
7832                         }
7833                 });
7834
7835                 // Fix list elements, TODO: Replace this later
7836                 if (settings.fix_list_elements) {
7837                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
7838                                 var i = nodes.length, node, parentNode;
7839
7840                                 while (i--) {
7841                                         node = nodes[i];
7842                                         parentNode = node.parent;
7843
7844                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
7845                                                 if (node.prev && node.prev.name === 'li') {
7846                                                         node.prev.append(node);
7847                                                 }
7848                                         }
7849                                 }
7850                         });
7851                 }
7852
7853                 // Remove internal data attributes
7854                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
7855                         var i = nodes.length;
7856
7857                         while (i--) {
7858                                 nodes[i].attr(name, null);
7859                         }
7860                 });
7861
7862                 // Return public methods
7863                 return {
7864                         schema : schema,
7865
7866                         addNodeFilter : htmlParser.addNodeFilter,
7867
7868                         addAttributeFilter : htmlParser.addAttributeFilter,
7869
7870                         onPreProcess : onPreProcess,
7871
7872                         onPostProcess : onPostProcess,
7873
7874                         serialize : function(node, args) {
7875                                 var impl, doc, oldDoc, htmlSerializer, content;
7876
7877                                 // Explorer won't clone contents of script and style and the
7878                                 // selected index of select elements are cleared on a clone operation.
7879                                 if (isIE && dom.select('script,style,select').length > 0) {
7880                                         content = node.innerHTML;
7881                                         node = node.cloneNode(false);
7882                                         dom.setHTML(node, content);
7883                                 } else
7884                                         node = node.cloneNode(true);
7885
7886                                 // Nodes needs to be attached to something in WebKit/Opera
7887                                 // Older builds of Opera crashes if you attach the node to an document created dynamically
7888                                 // and since we can't feature detect a crash we need to sniff the acutal build number
7889                                 // This fix will make DOM ranges and make Sizzle happy!
7890                                 impl = node.ownerDocument.implementation;
7891                                 if (impl.createHTMLDocument) {
7892                                         // Create an empty HTML document
7893                                         doc = impl.createHTMLDocument("");
7894
7895                                         // Add the element or it's children if it's a body element to the new document
7896                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
7897                                                 doc.body.appendChild(doc.importNode(node, true));
7898                                         });
7899
7900                                         // Grab first child or body element for serialization
7901                                         if (node.nodeName != 'BODY')
7902                                                 node = doc.body.firstChild;
7903                                         else
7904                                                 node = doc.body;
7905
7906                                         // set the new document in DOMUtils so createElement etc works
7907                                         oldDoc = dom.doc;
7908                                         dom.doc = doc;
7909                                 }
7910
7911                                 args = args || {};
7912                                 args.format = args.format || 'html';
7913
7914                                 // Pre process
7915                                 if (!args.no_events) {
7916                                         args.node = node;
7917                                         onPreProcess.dispatch(self, args);
7918                                 }
7919
7920                                 // Setup serializer
7921                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
7922
7923                                 // Parse and serialize HTML
7924                                 args.content = htmlSerializer.serialize(
7925                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
7926                                 );
7927
7928                                 // Replace all BOM characters for now until we can find a better solution
7929                                 if (!args.cleanup)
7930                                         args.content = args.content.replace(/\uFEFF/g, '');
7931
7932                                 // Post process
7933                                 if (!args.no_events)
7934                                         onPostProcess.dispatch(self, args);
7935
7936                                 // Restore the old document if it was changed
7937                                 if (oldDoc)
7938                                         dom.doc = oldDoc;
7939
7940                                 args.node = null;
7941
7942                                 return args.content;
7943                         },
7944
7945                         addRules : function(rules) {
7946                                 schema.addValidElements(rules);
7947                         },
7948
7949                         setRules : function(rules) {
7950                                 schema.setValidElements(rules);
7951                         }
7952                 };
7953         };
7954 })(tinymce);
7955 (function(tinymce) {
7956         tinymce.dom.ScriptLoader = function(settings) {
7957                 var QUEUED = 0,
7958                         LOADING = 1,
7959                         LOADED = 2,
7960                         states = {},
7961                         queue = [],
7962                         scriptLoadedCallbacks = {},
7963                         queueLoadedCallbacks = [],
7964                         loading = 0,
7965                         undefined;
7966
7967                 function loadScript(url, callback) {
7968                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;
7969
7970                         // Execute callback when script is loaded
7971                         function done() {
7972                                 dom.remove(id);
7973
7974                                 if (elm)
7975                                         elm.onreadystatechange = elm.onload = elm = null;
7976
7977                                 callback();
7978                         };
7979                         
7980                         function error() {
7981                                 // Report the error so it's easier for people to spot loading errors
7982                                 if (typeof(console) !== "undefined" && console.log)
7983                                         console.log("Failed to load: " + url);
7984
7985                                 // We can't mark it as done if there is a load error since
7986                                 // A) We don't want to produce 404 errors on the server and
7987                                 // B) the onerror event won't fire on all browsers.
7988                                 // done();
7989                         };
7990
7991                         id = dom.uniqueId();
7992
7993                         if (tinymce.isIE6) {
7994                                 uri = new tinymce.util.URI(url);
7995                                 loc = location;
7996
7997                                 // If script is from same domain and we
7998                                 // use IE 6 then use XHR since it's more reliable
7999                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
8000                                         tinymce.util.XHR.send({
8001                                                 url : tinymce._addVer(uri.getURI()),
8002                                                 success : function(content) {
8003                                                         // Create new temp script element
8004                                                         var script = dom.create('script', {
8005                                                                 type : 'text/javascript'
8006                                                         });
8007
8008                                                         // Evaluate script in global scope
8009                                                         script.text = content;
8010                                                         document.getElementsByTagName('head')[0].appendChild(script);
8011                                                         dom.remove(script);
8012
8013                                                         done();
8014                                                 },
8015                                                 
8016                                                 error : error
8017                                         });
8018
8019                                         return;
8020                                 }
8021                         }
8022
8023                         // Create new script element
8024                         elm = dom.create('script', {
8025                                 id : id,
8026                                 type : 'text/javascript',
8027                                 src : tinymce._addVer(url)
8028                         });
8029
8030                         // Add onload listener for non IE browsers since IE9
8031                         // fires onload event before the script is parsed and executed
8032                         if (!tinymce.isIE)
8033                                 elm.onload = done;
8034
8035                         // Add onerror event will get fired on some browsers but not all of them
8036                         elm.onerror = error;
8037
8038                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
8039                         if (!tinymce.isOpera) {
8040                                 elm.onreadystatechange = function() {
8041                                         var state = elm.readyState;
8042
8043                                         // Loaded state is passed on IE 6 however there
8044                                         // are known issues with this method but we can't use
8045                                         // XHR in a cross domain loading
8046                                         if (state == 'complete' || state == 'loaded')
8047                                                 done();
8048                                 };
8049                         }
8050
8051                         // Most browsers support this feature so we report errors
8052                         // for those at least to help users track their missing plugins etc
8053                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
8054                         /*elm.onerror = function() {
8055                                 alert('Failed to load: ' + url);
8056                         };*/
8057
8058                         // Add script to document
8059                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
8060                 };
8061
8062                 this.isDone = function(url) {
8063                         return states[url] == LOADED;
8064                 };
8065
8066                 this.markDone = function(url) {
8067                         states[url] = LOADED;
8068                 };
8069
8070                 this.add = this.load = function(url, callback, scope) {
8071                         var item, state = states[url];
8072
8073                         // Add url to load queue
8074                         if (state == undefined) {
8075                                 queue.push(url);
8076                                 states[url] = QUEUED;
8077                         }
8078
8079                         if (callback) {
8080                                 // Store away callback for later execution
8081                                 if (!scriptLoadedCallbacks[url])
8082                                         scriptLoadedCallbacks[url] = [];
8083
8084                                 scriptLoadedCallbacks[url].push({
8085                                         func : callback,
8086                                         scope : scope || this
8087                                 });
8088                         }
8089                 };
8090
8091                 this.loadQueue = function(callback, scope) {
8092                         this.loadScripts(queue, callback, scope);
8093                 };
8094
8095                 this.loadScripts = function(scripts, callback, scope) {
8096                         var loadScripts;
8097
8098                         function execScriptLoadedCallbacks(url) {
8099                                 // Execute URL callback functions
8100                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
8101                                         callback.func.call(callback.scope);
8102                                 });
8103
8104                                 scriptLoadedCallbacks[url] = undefined;
8105                         };
8106
8107                         queueLoadedCallbacks.push({
8108                                 func : callback,
8109                                 scope : scope || this
8110                         });
8111
8112                         loadScripts = function() {
8113                                 var loadingScripts = tinymce.grep(scripts);
8114
8115                                 // Current scripts has been handled
8116                                 scripts.length = 0;
8117
8118                                 // Load scripts that needs to be loaded
8119                                 tinymce.each(loadingScripts, function(url) {
8120                                         // Script is already loaded then execute script callbacks directly
8121                                         if (states[url] == LOADED) {
8122                                                 execScriptLoadedCallbacks(url);
8123                                                 return;
8124                                         }
8125
8126                                         // Is script not loading then start loading it
8127                                         if (states[url] != LOADING) {
8128                                                 states[url] = LOADING;
8129                                                 loading++;
8130
8131                                                 loadScript(url, function() {
8132                                                         states[url] = LOADED;
8133                                                         loading--;
8134
8135                                                         execScriptLoadedCallbacks(url);
8136
8137                                                         // Load more scripts if they where added by the recently loaded script
8138                                                         loadScripts();
8139                                                 });
8140                                         }
8141                                 });
8142
8143                                 // No scripts are currently loading then execute all pending queue loaded callbacks
8144                                 if (!loading) {
8145                                         tinymce.each(queueLoadedCallbacks, function(callback) {
8146                                                 callback.func.call(callback.scope);
8147                                         });
8148
8149                                         queueLoadedCallbacks.length = 0;
8150                                 }
8151                         };
8152
8153                         loadScripts();
8154                 };
8155         };
8156
8157         // Global script loader
8158         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
8159 })(tinymce);
8160
8161 tinymce.dom.TreeWalker = function(start_node, root_node) {
8162         var node = start_node;
8163
8164         function findSibling(node, start_name, sibling_name, shallow) {
8165                 var sibling, parent;
8166
8167                 if (node) {
8168                         // Walk into nodes if it has a start
8169                         if (!shallow && node[start_name])
8170                                 return node[start_name];
8171
8172                         // Return the sibling if it has one
8173                         if (node != root_node) {
8174                                 sibling = node[sibling_name];
8175                                 if (sibling)
8176                                         return sibling;
8177
8178                                 // Walk up the parents to look for siblings
8179                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
8180                                         sibling = parent[sibling_name];
8181                                         if (sibling)
8182                                                 return sibling;
8183                                 }
8184                         }
8185                 }
8186         };
8187
8188         this.current = function() {
8189                 return node;
8190         };
8191
8192         this.next = function(shallow) {
8193                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
8194         };
8195
8196         this.prev = function(shallow) {
8197                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
8198         };
8199 };
8200
8201 (function(tinymce) {
8202         tinymce.dom.RangeUtils = function(dom) {
8203                 var INVISIBLE_CHAR = '\uFEFF';
8204
8205                 this.walk = function(rng, callback) {
8206                         var startContainer = rng.startContainer,
8207                                 startOffset = rng.startOffset,
8208                                 endContainer = rng.endContainer,
8209                                 endOffset = rng.endOffset,
8210                                 ancestor, startPoint,
8211                                 endPoint, node, parent, siblings, nodes;
8212
8213                         // Handle table cell selection the table plugin enables
8214                         // you to fake select table cells and perform formatting actions on them
8215                         nodes = dom.select('td.mceSelected,th.mceSelected');
8216                         if (nodes.length > 0) {
8217                                 tinymce.each(nodes, function(node) {
8218                                         callback([node]);
8219                                 });
8220
8221                                 return;
8222                         }
8223
8224                         function collectSiblings(node, name, end_node) {
8225                                 var siblings = [];
8226
8227                                 for (; node && node != end_node; node = node[name])
8228                                         siblings.push(node);
8229
8230                                 return siblings;
8231                         };
8232
8233                         function findEndPoint(node, root) {
8234                                 do {
8235                                         if (node.parentNode == root)
8236                                                 return node;
8237
8238                                         node = node.parentNode;
8239                                 } while(node);
8240                         };
8241
8242                         function walkBoundary(start_node, end_node, next) {
8243                                 var siblingName = next ? 'nextSibling' : 'previousSibling';
8244
8245                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
8246                                         parent = node.parentNode;
8247                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
8248
8249                                         if (siblings.length) {
8250                                                 if (!next)
8251                                                         siblings.reverse();
8252
8253                                                 callback(siblings);
8254                                         }
8255                                 }
8256                         };
8257
8258                         // If index based start position then resolve it
8259                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
8260                                 startContainer = startContainer.childNodes[startOffset];
8261
8262                         // If index based end position then resolve it
8263                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
8264                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
8265
8266                         // Find common ancestor and end points
8267                         ancestor = dom.findCommonAncestor(startContainer, endContainer);
8268
8269                         // Same container
8270                         if (startContainer == endContainer)
8271                                 return callback([startContainer]);
8272
8273                         // Process left side
8274                         for (node = startContainer; node; node = node.parentNode) {
8275                                 if (node == endContainer)
8276                                         return walkBoundary(startContainer, ancestor, true);
8277
8278                                 if (node == ancestor)
8279                                         break;
8280                         }
8281
8282                         // Process right side
8283                         for (node = endContainer; node; node = node.parentNode) {
8284                                 if (node == startContainer)
8285                                         return walkBoundary(endContainer, ancestor);
8286
8287                                 if (node == ancestor)
8288                                         break;
8289                         }
8290
8291                         // Find start/end point
8292                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;
8293                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;
8294
8295                         // Walk left leaf
8296                         walkBoundary(startContainer, startPoint, true);
8297
8298                         // Walk the middle from start to end point
8299                         siblings = collectSiblings(
8300                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
8301                                 'nextSibling',
8302                                 endPoint == endContainer ? endPoint.nextSibling : endPoint
8303                         );
8304
8305                         if (siblings.length)
8306                                 callback(siblings);
8307
8308                         // Walk right leaf
8309                         walkBoundary(endContainer, endPoint);
8310                 };
8311
8312                 /*              this.split = function(rng) {
8313                         var startContainer = rng.startContainer,
8314                                 startOffset = rng.startOffset,
8315                                 endContainer = rng.endContainer,
8316                                 endOffset = rng.endOffset;
8317
8318                         function splitText(node, offset) {
8319                                 if (offset == node.nodeValue.length)
8320                                         node.appendData(INVISIBLE_CHAR);
8321
8322                                 node = node.splitText(offset);
8323
8324                                 if (node.nodeValue === INVISIBLE_CHAR)
8325                                         node.nodeValue = '';
8326
8327                                 return node;
8328                         };
8329
8330                         // Handle single text node
8331                         if (startContainer == endContainer) {
8332                                 if (startContainer.nodeType == 3) {
8333                                         if (startOffset != 0)
8334                                                 startContainer = endContainer = splitText(startContainer, startOffset);
8335
8336                                         if (endOffset - startOffset != startContainer.nodeValue.length)
8337                                                 splitText(startContainer, endOffset - startOffset);
8338                                 }
8339                         } else {
8340                                 // Split startContainer text node if needed
8341                                 if (startContainer.nodeType == 3 && startOffset != 0) {
8342                                         startContainer = splitText(startContainer, startOffset);
8343                                         startOffset = 0;
8344                                 }
8345
8346                                 // Split endContainer text node if needed
8347                                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
8348                                         endContainer = splitText(endContainer, endOffset).previousSibling;
8349                                         endOffset = endContainer.nodeValue.length;
8350                                 }
8351                         }
8352
8353                         return {
8354                                 startContainer : startContainer,
8355                                 startOffset : startOffset,
8356                                 endContainer : endContainer,
8357                                 endOffset : endOffset
8358                         };
8359                 };
8360 */
8361         };
8362
8363         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
8364                 if (rng1 && rng2) {
8365                         // Compare native IE ranges
8366                         if (rng1.item || rng1.duplicate) {
8367                                 // Both are control ranges and the selected element matches
8368                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
8369                                         return true;
8370
8371                                 // Both are text ranges and the range matches
8372                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
8373                                         return true;
8374                         } else {
8375                                 // Compare w3c ranges
8376                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
8377                         }
8378                 }
8379
8380                 return false;
8381         };
8382 })(tinymce);
8383
8384 (function(tinymce) {
8385         var Event = tinymce.dom.Event, each = tinymce.each;
8386
8387         tinymce.create('tinymce.ui.KeyboardNavigation', {
8388                 KeyboardNavigation: function(settings, dom) {
8389                         var t = this, root = settings.root, items = settings.items,
8390                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
8391                                         excludeFromTabOrder = settings.excludeFromTabOrder,
8392                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
8393
8394                         dom = dom || tinymce.DOM;
8395
8396                         itemFocussed = function(evt) {
8397                                 focussedId = evt.target.id;
8398                         };
8399                         
8400                         itemBlurred = function(evt) {
8401                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');
8402                         };
8403                         
8404                         rootFocussed = function(evt) {
8405                                 var item = dom.get(focussedId);
8406                                 dom.setAttrib(item, 'tabindex', '0');
8407                                 item.focus();
8408                         };
8409                         
8410                         t.focus = function() {
8411                                 dom.get(focussedId).focus();
8412                         };
8413
8414                         t.destroy = function() {
8415                                 each(items, function(item) {
8416                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);
8417                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);
8418                                 });
8419
8420                                 dom.unbind(dom.get(root), 'focus', rootFocussed);
8421                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);
8422
8423                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
8424                                 t.destroy = function() {};
8425                         };
8426                         
8427                         t.moveFocus = function(dir, evt) {
8428                                 var idx = -1, controls = t.controls, newFocus;
8429
8430                                 if (!focussedId)
8431                                         return;
8432
8433                                 each(items, function(item, index) {
8434                                         if (item.id === focussedId) {
8435                                                 idx = index;
8436                                                 return false;
8437                                         }
8438                                 });
8439
8440                                 idx += dir;
8441                                 if (idx < 0) {
8442                                         idx = items.length - 1;
8443                                 } else if (idx >= items.length) {
8444                                         idx = 0;
8445                                 }
8446                                 
8447                                 newFocus = items[idx];
8448                                 dom.setAttrib(focussedId, 'tabindex', '-1');
8449                                 dom.setAttrib(newFocus.id, 'tabindex', '0');
8450                                 dom.get(newFocus.id).focus();
8451
8452                                 if (settings.actOnFocus) {
8453                                         settings.onAction(newFocus.id);
8454                                 }
8455
8456                                 if (evt)
8457                                         Event.cancel(evt);
8458                         };
8459                         
8460                         rootKeydown = function(evt) {
8461                                 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;
8462                                 
8463                                 switch (evt.keyCode) {
8464                                         case DOM_VK_LEFT:
8465                                                 if (enableLeftRight) t.moveFocus(-1);
8466                                                 break;
8467         
8468                                         case DOM_VK_RIGHT:
8469                                                 if (enableLeftRight) t.moveFocus(1);
8470                                                 break;
8471         
8472                                         case DOM_VK_UP:
8473                                                 if (enableUpDown) t.moveFocus(-1);
8474                                                 break;
8475
8476                                         case DOM_VK_DOWN:
8477                                                 if (enableUpDown) t.moveFocus(1);
8478                                                 break;
8479
8480                                         case DOM_VK_ESCAPE:
8481                                                 if (settings.onCancel) {
8482                                                         settings.onCancel();
8483                                                         Event.cancel(evt);
8484                                                 }
8485                                                 break;
8486
8487                                         case DOM_VK_ENTER:
8488                                         case DOM_VK_RETURN:
8489                                         case DOM_VK_SPACE:
8490                                                 if (settings.onAction) {
8491                                                         settings.onAction(focussedId);
8492                                                         Event.cancel(evt);
8493                                                 }
8494                                                 break;
8495                                 }
8496                         };
8497
8498                         // Set up state and listeners for each item.
8499                         each(items, function(item, idx) {
8500                                 var tabindex;
8501
8502                                 if (!item.id) {
8503                                         item.id = dom.uniqueId('_mce_item_');
8504                                 }
8505
8506                                 if (excludeFromTabOrder) {
8507                                         dom.bind(item.id, 'blur', itemBlurred);
8508                                         tabindex = '-1';
8509                                 } else {
8510                                         tabindex = (idx === 0 ? '0' : '-1');
8511                                 }
8512
8513                                 dom.setAttrib(item.id, 'tabindex', tabindex);
8514                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);
8515                         });
8516                         
8517                         // Setup initial state for root element.
8518                         if (items[0]){
8519                                 focussedId = items[0].id;
8520                         }
8521
8522                         dom.setAttrib(root, 'tabindex', '-1');
8523                         
8524                         // Setup listeners for root element.
8525                         dom.bind(dom.get(root), 'focus', rootFocussed);
8526                         dom.bind(dom.get(root), 'keydown', rootKeydown);
8527                 }
8528         });
8529 })(tinymce);
8530 (function(tinymce) {
8531         // Shorten class names
8532         var DOM = tinymce.DOM, is = tinymce.is;
8533
8534         tinymce.create('tinymce.ui.Control', {
8535                 Control : function(id, s, editor) {
8536                         this.id = id;
8537                         this.settings = s = s || {};
8538                         this.rendered = false;
8539                         this.onRender = new tinymce.util.Dispatcher(this);
8540                         this.classPrefix = '';
8541                         this.scope = s.scope || this;
8542                         this.disabled = 0;
8543                         this.active = 0;
8544                         this.editor = editor;
8545                 },
8546                 
8547                 setAriaProperty : function(property, value) {
8548                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
8549                         if (element) {
8550                                 DOM.setAttrib(element, 'aria-' + property, !!value);
8551                         }
8552                 },
8553                 
8554                 focus : function() {
8555                         DOM.get(this.id).focus();
8556                 },
8557
8558                 setDisabled : function(s) {
8559                         if (s != this.disabled) {
8560                                 this.setAriaProperty('disabled', s);
8561
8562                                 this.setState('Disabled', s);
8563                                 this.setState('Enabled', !s);
8564                                 this.disabled = s;
8565                         }
8566                 },
8567
8568                 isDisabled : function() {
8569                         return this.disabled;
8570                 },
8571
8572                 setActive : function(s) {
8573                         if (s != this.active) {
8574                                 this.setState('Active', s);
8575                                 this.active = s;
8576                                 this.setAriaProperty('pressed', s);
8577                         }
8578                 },
8579
8580                 isActive : function() {
8581                         return this.active;
8582                 },
8583
8584                 setState : function(c, s) {
8585                         var n = DOM.get(this.id);
8586
8587                         c = this.classPrefix + c;
8588
8589                         if (s)
8590                                 DOM.addClass(n, c);
8591                         else
8592                                 DOM.removeClass(n, c);
8593                 },
8594
8595                 isRendered : function() {
8596                         return this.rendered;
8597                 },
8598
8599                 renderHTML : function() {
8600                 },
8601
8602                 renderTo : function(n) {
8603                         DOM.setHTML(n, this.renderHTML());
8604                 },
8605
8606                 postRender : function() {
8607                         var t = this, b;
8608
8609                         // Set pending states
8610                         if (is(t.disabled)) {
8611                                 b = t.disabled;
8612                                 t.disabled = -1;
8613                                 t.setDisabled(b);
8614                         }
8615
8616                         if (is(t.active)) {
8617                                 b = t.active;
8618                                 t.active = -1;
8619                                 t.setActive(b);
8620                         }
8621                 },
8622
8623                 remove : function() {
8624                         DOM.remove(this.id);
8625                         this.destroy();
8626                 },
8627
8628                 destroy : function() {
8629                         tinymce.dom.Event.clear(this.id);
8630                 }
8631         });
8632 })(tinymce);
8633 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
8634         Container : function(id, s, editor) {
8635                 this.parent(id, s, editor);
8636
8637                 this.controls = [];
8638
8639                 this.lookup = {};
8640         },
8641
8642         add : function(c) {
8643                 this.lookup[c.id] = c;
8644                 this.controls.push(c);
8645
8646                 return c;
8647         },
8648
8649         get : function(n) {
8650                 return this.lookup[n];
8651         }
8652 });
8653
8654
8655 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
8656         Separator : function(id, s) {
8657                 this.parent(id, s);
8658                 this.classPrefix = 'mceSeparator';
8659                 this.setDisabled(true);
8660         },
8661
8662         renderHTML : function() {
8663                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
8664         }
8665 });
8666
8667 (function(tinymce) {
8668         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8669
8670         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
8671                 MenuItem : function(id, s) {
8672                         this.parent(id, s);
8673                         this.classPrefix = 'mceMenuItem';
8674                 },
8675
8676                 setSelected : function(s) {
8677                         this.setState('Selected', s);
8678                         this.setAriaProperty('checked', !!s);
8679                         this.selected = s;
8680                 },
8681
8682                 isSelected : function() {
8683                         return this.selected;
8684                 },
8685
8686                 postRender : function() {
8687                         var t = this;
8688                         
8689                         t.parent();
8690
8691                         // Set pending state
8692                         if (is(t.selected))
8693                                 t.setSelected(t.selected);
8694                 }
8695         });
8696 })(tinymce);
8697
8698 (function(tinymce) {
8699         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8700
8701         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
8702                 Menu : function(id, s) {
8703                         var t = this;
8704
8705                         t.parent(id, s);
8706                         t.items = {};
8707                         t.collapsed = false;
8708                         t.menuCount = 0;
8709                         t.onAddItem = new tinymce.util.Dispatcher(this);
8710                 },
8711
8712                 expand : function(d) {
8713                         var t = this;
8714
8715                         if (d) {
8716                                 walk(t, function(o) {
8717                                         if (o.expand)
8718                                                 o.expand();
8719                                 }, 'items', t);
8720                         }
8721
8722                         t.collapsed = false;
8723                 },
8724
8725                 collapse : function(d) {
8726                         var t = this;
8727
8728                         if (d) {
8729                                 walk(t, function(o) {
8730                                         if (o.collapse)
8731                                                 o.collapse();
8732                                 }, 'items', t);
8733                         }
8734
8735                         t.collapsed = true;
8736                 },
8737
8738                 isCollapsed : function() {
8739                         return this.collapsed;
8740                 },
8741
8742                 add : function(o) {
8743                         if (!o.settings)
8744                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
8745
8746                         this.onAddItem.dispatch(this, o);
8747
8748                         return this.items[o.id] = o;
8749                 },
8750
8751                 addSeparator : function() {
8752                         return this.add({separator : true});
8753                 },
8754
8755                 addMenu : function(o) {
8756                         if (!o.collapse)
8757                                 o = this.createMenu(o);
8758
8759                         this.menuCount++;
8760
8761                         return this.add(o);
8762                 },
8763
8764                 hasMenus : function() {
8765                         return this.menuCount !== 0;
8766                 },
8767
8768                 remove : function(o) {
8769                         delete this.items[o.id];
8770                 },
8771
8772                 removeAll : function() {
8773                         var t = this;
8774
8775                         walk(t, function(o) {
8776                                 if (o.removeAll)
8777                                         o.removeAll();
8778                                 else
8779                                         o.remove();
8780
8781                                 o.destroy();
8782                         }, 'items', t);
8783
8784                         t.items = {};
8785                 },
8786
8787                 createMenu : function(o) {
8788                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
8789
8790                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
8791
8792                         return m;
8793                 }
8794         });
8795 })(tinymce);
8796 (function(tinymce) {
8797         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
8798
8799         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
8800                 DropMenu : function(id, s) {
8801                         s = s || {};
8802                         s.container = s.container || DOM.doc.body;
8803                         s.offset_x = s.offset_x || 0;
8804                         s.offset_y = s.offset_y || 0;
8805                         s.vp_offset_x = s.vp_offset_x || 0;
8806                         s.vp_offset_y = s.vp_offset_y || 0;
8807
8808                         if (is(s.icons) && !s.icons)
8809                                 s['class'] += ' mceNoIcons';
8810
8811                         this.parent(id, s);
8812                         this.onShowMenu = new tinymce.util.Dispatcher(this);
8813                         this.onHideMenu = new tinymce.util.Dispatcher(this);
8814                         this.classPrefix = 'mceMenu';
8815                 },
8816
8817                 createMenu : function(s) {
8818                         var t = this, cs = t.settings, m;
8819
8820                         s.container = s.container || cs.container;
8821                         s.parent = t;
8822                         s.constrain = s.constrain || cs.constrain;
8823                         s['class'] = s['class'] || cs['class'];
8824                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
8825                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
8826                         s.keyboard_focus = cs.keyboard_focus;
8827                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
8828
8829                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
8830
8831                         return m;
8832                 },
8833                 
8834                 focus : function() {
8835                         var t = this;
8836                         if (t.keyboardNav) {
8837                                 t.keyboardNav.focus();
8838                         }
8839                 },
8840
8841                 update : function() {
8842                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
8843
8844                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
8845                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
8846
8847                         if (!DOM.boxModel)
8848                                 t.element.setStyles({width : tw + 2, height : th + 2});
8849                         else
8850                                 t.element.setStyles({width : tw, height : th});
8851
8852                         if (s.max_width)
8853                                 DOM.setStyle(co, 'width', tw);
8854
8855                         if (s.max_height) {
8856                                 DOM.setStyle(co, 'height', th);
8857
8858                                 if (tb.clientHeight < s.max_height)
8859                                         DOM.setStyle(co, 'overflow', 'hidden');
8860                         }
8861                 },
8862
8863                 showMenu : function(x, y, px) {
8864                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
8865
8866                         t.collapse(1);
8867
8868                         if (t.isMenuVisible)
8869                                 return;
8870
8871                         if (!t.rendered) {
8872                                 co = DOM.add(t.settings.container, t.renderNode());
8873
8874                                 each(t.items, function(o) {
8875                                         o.postRender();
8876                                 });
8877
8878                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8879                         } else
8880                                 co = DOM.get('menu_' + t.id);
8881
8882                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
8883                         if (!tinymce.isOpera)
8884                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
8885
8886                         DOM.show(co);
8887                         t.update();
8888
8889                         x += s.offset_x || 0;
8890                         y += s.offset_y || 0;
8891                         vp.w -= 4;
8892                         vp.h -= 4;
8893
8894                         // Move inside viewport if not submenu
8895                         if (s.constrain) {
8896                                 w = co.clientWidth - ot;
8897                                 h = co.clientHeight - ot;
8898                                 mx = vp.x + vp.w;
8899                                 my = vp.y + vp.h;
8900
8901                                 if ((x + s.vp_offset_x + w) > mx)
8902                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
8903
8904                                 if ((y + s.vp_offset_y + h) > my)
8905                                         y = Math.max(0, (my - s.vp_offset_y) - h);
8906                         }
8907
8908                         DOM.setStyles(co, {left : x , top : y});
8909                         t.element.update();
8910
8911                         t.isMenuVisible = 1;
8912                         t.mouseClickFunc = Event.add(co, 'click', function(e) {
8913                                 var m;
8914
8915                                 e = e.target;
8916
8917                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
8918                                         m = t.items[e.id];
8919
8920                                         if (m.isDisabled())
8921                                                 return;
8922
8923                                         dm = t;
8924
8925                                         while (dm) {
8926                                                 if (dm.hideMenu)
8927                                                         dm.hideMenu();
8928
8929                                                 dm = dm.settings.parent;
8930                                         }
8931
8932                                         if (m.settings.onclick)
8933                                                 m.settings.onclick(e);
8934
8935                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
8936                                 }
8937                         });
8938
8939                         if (t.hasMenus()) {
8940                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
8941                                         var m, r, mi;
8942
8943                                         e = e.target;
8944                                         if (e && (e = DOM.getParent(e, 'tr'))) {
8945                                                 m = t.items[e.id];
8946
8947                                                 if (t.lastMenu)
8948                                                         t.lastMenu.collapse(1);
8949
8950                                                 if (m.isDisabled())
8951                                                         return;
8952
8953                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
8954                                                         //p = DOM.getPos(s.container);
8955                                                         r = DOM.getRect(e);
8956                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
8957                                                         t.lastMenu = m;
8958                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
8959                                                 }
8960                                         }
8961                                 });
8962                         }
8963                         
8964                         Event.add(co, 'keydown', t._keyHandler, t);
8965
8966                         t.onShowMenu.dispatch(t);
8967
8968                         if (s.keyboard_focus) { 
8969                                 t._setupKeyboardNav(); 
8970                         }
8971                 },
8972
8973                 hideMenu : function(c) {
8974                         var t = this, co = DOM.get('menu_' + t.id), e;
8975
8976                         if (!t.isMenuVisible)
8977                                 return;
8978
8979                         if (t.keyboardNav) t.keyboardNav.destroy();
8980                         Event.remove(co, 'mouseover', t.mouseOverFunc);
8981                         Event.remove(co, 'click', t.mouseClickFunc);
8982                         Event.remove(co, 'keydown', t._keyHandler);
8983                         DOM.hide(co);
8984                         t.isMenuVisible = 0;
8985
8986                         if (!c)
8987                                 t.collapse(1);
8988
8989                         if (t.element)
8990                                 t.element.hide();
8991
8992                         if (e = DOM.get(t.id))
8993                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
8994
8995                         t.onHideMenu.dispatch(t);
8996                 },
8997
8998                 add : function(o) {
8999                         var t = this, co;
9000
9001                         o = t.parent(o);
9002
9003                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))
9004                                 t._add(DOM.select('tbody', co)[0], o);
9005
9006                         return o;
9007                 },
9008
9009                 collapse : function(d) {
9010                         this.parent(d);
9011                         this.hideMenu(1);
9012                 },
9013
9014                 remove : function(o) {
9015                         DOM.remove(o.id);
9016                         this.destroy();
9017
9018                         return this.parent(o);
9019                 },
9020
9021                 destroy : function() {
9022                         var t = this, co = DOM.get('menu_' + t.id);
9023
9024                         if (t.keyboardNav) t.keyboardNav.destroy();
9025                         Event.remove(co, 'mouseover', t.mouseOverFunc);
9026                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
9027                         Event.remove(co, 'click', t.mouseClickFunc);
9028                         Event.remove(co, 'keydown', t._keyHandler);
9029
9030                         if (t.element)
9031                                 t.element.remove();
9032
9033                         DOM.remove(co);
9034                 },
9035
9036                 renderNode : function() {
9037                         var t = this, s = t.settings, n, tb, co, w;
9038
9039                         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'});
9040                         if (t.settings.parent) {
9041                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
9042                         }
9043                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
9044                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
9045
9046                         if (s.menu_line)
9047                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
9048
9049 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
9050                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
9051                         tb = DOM.add(n, 'tbody');
9052
9053                         each(t.items, function(o) {
9054                                 t._add(tb, o);
9055                         });
9056
9057                         t.rendered = true;
9058
9059                         return w;
9060                 },
9061
9062                 // Internal functions
9063                 _setupKeyboardNav : function(){
9064                         var contextMenu, menuItems, t=this; 
9065                         contextMenu = DOM.select('#menu_' + t.id)[0];
9066                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
9067                         menuItems.splice(0,0,contextMenu);
9068                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({
9069                                 root: 'menu_' + t.id,
9070                                 items: menuItems,
9071                                 onCancel: function() {
9072                                         t.hideMenu();
9073                                 },
9074                                 enableUpDown: true
9075                         });
9076                         contextMenu.focus();
9077                 },
9078
9079                 _keyHandler : function(evt) {
9080                         var t = this, e;
9081                         switch (evt.keyCode) {
9082                                 case 37: // Left
9083                                         if (t.settings.parent) {
9084                                                 t.hideMenu();
9085                                                 t.settings.parent.focus();
9086                                                 Event.cancel(evt);
9087                                         }
9088                                         break;
9089                                 case 39: // Right
9090                                         if (t.mouseOverFunc)
9091                                                 t.mouseOverFunc(evt);
9092                                         break;
9093                         }
9094                 },
9095
9096                 _add : function(tb, o) {
9097                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
9098
9099                         if (s.separator) {
9100                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
9101                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
9102
9103                                 if (n = ro.previousSibling)
9104                                         DOM.addClass(n, 'mceLast');
9105
9106                                 return;
9107                         }
9108
9109                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
9110                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
9111                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
9112
9113                         if (s.parent) {
9114                                 DOM.setAttrib(a, 'aria-haspopup', 'true');
9115                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
9116                         }
9117
9118                         DOM.addClass(it, s['class']);
9119 //                      n = DOM.add(n, 'span', {'class' : 'item'});
9120
9121                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
9122
9123                         if (s.icon_src)
9124                                 DOM.add(ic, 'img', {src : s.icon_src});
9125
9126                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
9127
9128                         if (o.settings.style)
9129                                 DOM.setAttrib(n, 'style', o.settings.style);
9130
9131                         if (tb.childNodes.length == 1)
9132                                 DOM.addClass(ro, 'mceFirst');
9133
9134                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
9135                                 DOM.addClass(ro, 'mceFirst');
9136
9137                         if (o.collapse)
9138                                 DOM.addClass(ro, cp + 'ItemSub');
9139
9140                         if (n = ro.previousSibling)
9141                                 DOM.removeClass(n, 'mceLast');
9142
9143                         DOM.addClass(ro, 'mceLast');
9144                 }
9145         });
9146 })(tinymce);
9147 (function(tinymce) {
9148         var DOM = tinymce.DOM;
9149
9150         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
9151                 Button : function(id, s, ed) {
9152                         this.parent(id, s, ed);
9153                         this.classPrefix = 'mceButton';
9154                 },
9155
9156                 renderHTML : function() {
9157                         var cp = this.classPrefix, s = this.settings, h, l;
9158
9159                         l = DOM.encode(s.label || '');
9160                         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) + '">';
9161
9162                         if (s.image)
9163                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
9164                         else
9165                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
9166
9167                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
9168                         h += '</a>';
9169                         return h;
9170                 },
9171
9172                 postRender : function() {
9173                         var t = this, s = t.settings;
9174
9175                         tinymce.dom.Event.add(t.id, 'click', function(e) {
9176                                 if (!t.isDisabled())
9177                                         return s.onclick.call(s.scope, e);
9178                         });
9179                 }
9180         });
9181 })(tinymce);
9182
9183 (function(tinymce) {
9184         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9185
9186         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
9187                 ListBox : function(id, s, ed) {
9188                         var t = this;
9189
9190                         t.parent(id, s, ed);
9191
9192                         t.items = [];
9193
9194                         t.onChange = new Dispatcher(t);
9195
9196                         t.onPostRender = new Dispatcher(t);
9197
9198                         t.onAdd = new Dispatcher(t);
9199
9200                         t.onRenderMenu = new tinymce.util.Dispatcher(this);
9201
9202                         t.classPrefix = 'mceListBox';
9203                 },
9204
9205                 select : function(va) {
9206                         var t = this, fv, f;
9207
9208                         if (va == undefined)
9209                                 return t.selectByIndex(-1);
9210
9211                         // Is string or number make function selector
9212                         if (va && va.call)
9213                                 f = va;
9214                         else {
9215                                 f = function(v) {
9216                                         return v == va;
9217                                 };
9218                         }
9219
9220                         // Do we need to do something?
9221                         if (va != t.selectedValue) {
9222                                 // Find item
9223                                 each(t.items, function(o, i) {
9224                                         if (f(o.value)) {
9225                                                 fv = 1;
9226                                                 t.selectByIndex(i);
9227                                                 return false;
9228                                         }
9229                                 });
9230
9231                                 if (!fv)
9232                                         t.selectByIndex(-1);
9233                         }
9234                 },
9235
9236                 selectByIndex : function(idx) {
9237                         var t = this, e, o;
9238
9239                         if (idx != t.selectedIndex) {
9240                                 e = DOM.get(t.id + '_text');
9241                                 o = t.items[idx];
9242
9243                                 if (o) {
9244                                         t.selectedValue = o.value;
9245                                         t.selectedIndex = idx;
9246                                         DOM.setHTML(e, DOM.encode(o.title));
9247                                         DOM.removeClass(e, 'mceTitle');
9248                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);
9249                                 } else {
9250                                         DOM.setHTML(e, DOM.encode(t.settings.title));
9251                                         DOM.addClass(e, 'mceTitle');
9252                                         t.selectedValue = t.selectedIndex = null;
9253                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
9254                                 }
9255                                 e = 0;
9256                         }
9257                 },
9258
9259                 add : function(n, v, o) {
9260                         var t = this;
9261
9262                         o = o || {};
9263                         o = tinymce.extend(o, {
9264                                 title : n,
9265                                 value : v
9266                         });
9267
9268                         t.items.push(o);
9269                         t.onAdd.dispatch(t, o);
9270                 },
9271
9272                 getLength : function() {
9273                         return this.items.length;
9274                 },
9275
9276                 renderHTML : function() {
9277                         var h = '', t = this, s = t.settings, cp = t.classPrefix;
9278
9279                         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>';
9280                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
9281                         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>';
9282                         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>';
9283                         h += '</tr></tbody></table></span>';
9284
9285                         return h;
9286                 },
9287
9288                 showMenu : function() {
9289                         var t = this, p1, p2, e = DOM.get(this.id), m;
9290
9291                         if (t.isDisabled() || t.items.length == 0)
9292                                 return;
9293
9294                         if (t.menu && t.menu.isMenuVisible)
9295                                 return t.hideMenu();
9296
9297                         if (!t.isMenuRendered) {
9298                                 t.renderMenu();
9299                                 t.isMenuRendered = true;
9300                         }
9301
9302                         p1 = DOM.getPos(this.settings.menu_container);
9303                         p2 = DOM.getPos(e);
9304
9305                         m = t.menu;
9306                         m.settings.offset_x = p2.x;
9307                         m.settings.offset_y = p2.y;
9308                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
9309
9310                         // Select in menu
9311                         if (t.oldID)
9312                                 m.items[t.oldID].setSelected(0);
9313
9314                         each(t.items, function(o) {
9315                                 if (o.value === t.selectedValue) {
9316                                         m.items[o.id].setSelected(1);
9317                                         t.oldID = o.id;
9318                                 }
9319                         });
9320
9321                         m.showMenu(0, e.clientHeight);
9322
9323                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9324                         DOM.addClass(t.id, t.classPrefix + 'Selected');
9325
9326                         //DOM.get(t.id + '_text').focus();
9327                 },
9328
9329                 hideMenu : function(e) {
9330                         var t = this;
9331
9332                         if (t.menu && t.menu.isMenuVisible) {
9333                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');
9334
9335                                 // Prevent double toogles by canceling the mouse click event to the button
9336                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
9337                                         return;
9338
9339                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9340                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');
9341                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9342                                         t.menu.hideMenu();
9343                                 }
9344                         }
9345                 },
9346
9347                 renderMenu : function() {
9348                         var t = this, m;
9349
9350                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9351                                 menu_line : 1,
9352                                 'class' : t.classPrefix + 'Menu mceNoIcons',
9353                                 max_width : 150,
9354                                 max_height : 150
9355                         });
9356
9357                         m.onHideMenu.add(function() {
9358                                 t.hideMenu();
9359                                 t.focus();
9360                         });
9361
9362                         m.add({
9363                                 title : t.settings.title,
9364                                 'class' : 'mceMenuItemTitle',
9365                                 onclick : function() {
9366                                         if (t.settings.onselect('') !== false)
9367                                                 t.select(''); // Must be runned after
9368                                 }
9369                         });
9370
9371                         each(t.items, function(o) {
9372                                 // No value then treat it as a title
9373                                 if (o.value === undefined) {
9374                                         m.add({
9375                                                 title : o.title,
9376                                                 'class' : 'mceMenuItemTitle',
9377                                                 onclick : function() {
9378                                                         if (t.settings.onselect('') !== false)
9379                                                                 t.select(''); // Must be runned after
9380                                                 }
9381                                         });
9382                                 } else {
9383                                         o.id = DOM.uniqueId();
9384                                         o.onclick = function() {
9385                                                 if (t.settings.onselect(o.value) !== false)
9386                                                         t.select(o.value); // Must be runned after
9387                                         };
9388
9389                                         m.add(o);
9390                                 }
9391                         });
9392
9393                         t.onRenderMenu.dispatch(t, m);
9394                         t.menu = m;
9395                 },
9396
9397                 postRender : function() {
9398                         var t = this, cp = t.classPrefix;
9399
9400                         Event.add(t.id, 'click', t.showMenu, t);
9401                         Event.add(t.id, 'keydown', function(evt) {
9402                                 if (evt.keyCode == 32) { // Space
9403                                         t.showMenu(evt);
9404                                         Event.cancel(evt);
9405                                 }
9406                         });
9407                         Event.add(t.id, 'focus', function() {
9408                                 if (!t._focused) {
9409                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
9410                                                 if (e.keyCode == 40) {
9411                                                         t.showMenu();
9412                                                         Event.cancel(e);
9413                                                 }
9414                                         });
9415                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
9416                                                 var v;
9417                                                 if (e.keyCode == 13) {
9418                                                         // Fake select on enter
9419                                                         v = t.selectedValue;
9420                                                         t.selectedValue = null; // Needs to be null to fake change
9421                                                         Event.cancel(e);
9422                                                         t.settings.onselect(v);
9423                                                 }
9424                                         });
9425                                 }
9426
9427                                 t._focused = 1;
9428                         });
9429                         Event.add(t.id, 'blur', function() {
9430                                 Event.remove(t.id, 'keydown', t.keyDownHandler);
9431                                 Event.remove(t.id, 'keypress', t.keyPressHandler);
9432                                 t._focused = 0;
9433                         });
9434
9435                         // Old IE doesn't have hover on all elements
9436                         if (tinymce.isIE6 || !DOM.boxModel) {
9437                                 Event.add(t.id, 'mouseover', function() {
9438                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9439                                                 DOM.addClass(t.id, cp + 'Hover');
9440                                 });
9441
9442                                 Event.add(t.id, 'mouseout', function() {
9443                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9444                                                 DOM.removeClass(t.id, cp + 'Hover');
9445                                 });
9446                         }
9447
9448                         t.onPostRender.dispatch(t, DOM.get(t.id));
9449                 },
9450
9451                 destroy : function() {
9452                         this.parent();
9453
9454                         Event.clear(this.id + '_text');
9455                         Event.clear(this.id + '_open');
9456                 }
9457         });
9458 })(tinymce);
9459 (function(tinymce) {
9460         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9461
9462         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
9463                 NativeListBox : function(id, s) {
9464                         this.parent(id, s);
9465                         this.classPrefix = 'mceNativeListBox';
9466                 },
9467
9468                 setDisabled : function(s) {
9469                         DOM.get(this.id).disabled = s;
9470                         this.setAriaProperty('disabled', s);
9471                 },
9472
9473                 isDisabled : function() {
9474                         return DOM.get(this.id).disabled;
9475                 },
9476
9477                 select : function(va) {
9478                         var t = this, fv, f;
9479
9480                         if (va == undefined)
9481                                 return t.selectByIndex(-1);
9482
9483                         // Is string or number make function selector
9484                         if (va && va.call)
9485                                 f = va;
9486                         else {
9487                                 f = function(v) {
9488                                         return v == va;
9489                                 };
9490                         }
9491
9492                         // Do we need to do something?
9493                         if (va != t.selectedValue) {
9494                                 // Find item
9495                                 each(t.items, function(o, i) {
9496                                         if (f(o.value)) {
9497                                                 fv = 1;
9498                                                 t.selectByIndex(i);
9499                                                 return false;
9500                                         }
9501                                 });
9502
9503                                 if (!fv)
9504                                         t.selectByIndex(-1);
9505                         }
9506                 },
9507
9508                 selectByIndex : function(idx) {
9509                         DOM.get(this.id).selectedIndex = idx + 1;
9510                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;
9511                 },
9512
9513                 add : function(n, v, a) {
9514                         var o, t = this;
9515
9516                         a = a || {};
9517                         a.value = v;
9518
9519                         if (t.isRendered())
9520                                 DOM.add(DOM.get(this.id), 'option', a, n);
9521
9522                         o = {
9523                                 title : n,
9524                                 value : v,
9525                                 attribs : a
9526                         };
9527
9528                         t.items.push(o);
9529                         t.onAdd.dispatch(t, o);
9530                 },
9531
9532                 getLength : function() {
9533                         return this.items.length;
9534                 },
9535
9536                 renderHTML : function() {
9537                         var h, t = this;
9538
9539                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
9540
9541                         each(t.items, function(it) {
9542                                 h += DOM.createHTML('option', {value : it.value}, it.title);
9543                         });
9544
9545                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
9546                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
9547                         return h;
9548                 },
9549
9550                 postRender : function() {
9551                         var t = this, ch, changeListenerAdded = true;
9552
9553                         t.rendered = true;
9554
9555                         function onChange(e) {
9556                                 var v = t.items[e.target.selectedIndex - 1];
9557
9558                                 if (v && (v = v.value)) {
9559                                         t.onChange.dispatch(t, v);
9560
9561                                         if (t.settings.onselect)
9562                                                 t.settings.onselect(v);
9563                                 }
9564                         };
9565
9566                         Event.add(t.id, 'change', onChange);
9567
9568                         // Accessibility keyhandler
9569                         Event.add(t.id, 'keydown', function(e) {
9570                                 var bf;
9571
9572                                 Event.remove(t.id, 'change', ch);
9573                                 changeListenerAdded = false;
9574
9575                                 bf = Event.add(t.id, 'blur', function() {
9576                                         if (changeListenerAdded) return;
9577                                         changeListenerAdded = true;
9578                                         Event.add(t.id, 'change', onChange);
9579                                         Event.remove(t.id, 'blur', bf);
9580                                 });
9581
9582                                 if (e.keyCode == 13 || e.keyCode == 32) {
9583                                         onChange(e);
9584                                         return Event.cancel(e);
9585                                 }
9586                         });
9587
9588                         t.onPostRender.dispatch(t, DOM.get(t.id));
9589                 }
9590         });
9591 })(tinymce);
9592 (function(tinymce) {
9593         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9594
9595         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
9596                 MenuButton : function(id, s, ed) {
9597                         this.parent(id, s, ed);
9598
9599                         this.onRenderMenu = new tinymce.util.Dispatcher(this);
9600
9601                         s.menu_container = s.menu_container || DOM.doc.body;
9602                 },
9603
9604                 showMenu : function() {
9605                         var t = this, p1, p2, e = DOM.get(t.id), m;
9606
9607                         if (t.isDisabled())
9608                                 return;
9609
9610                         if (!t.isMenuRendered) {
9611                                 t.renderMenu();
9612                                 t.isMenuRendered = true;
9613                         }
9614
9615                         if (t.isMenuVisible)
9616                                 return t.hideMenu();
9617
9618                         p1 = DOM.getPos(t.settings.menu_container);
9619                         p2 = DOM.getPos(e);
9620
9621                         m = t.menu;
9622                         m.settings.offset_x = p2.x;
9623                         m.settings.offset_y = p2.y;
9624                         m.settings.vp_offset_x = p2.x;
9625                         m.settings.vp_offset_y = p2.y;
9626                         m.settings.keyboard_focus = t._focused;
9627                         m.showMenu(0, e.clientHeight);
9628
9629                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9630                         t.setState('Selected', 1);
9631
9632                         t.isMenuVisible = 1;
9633                 },
9634
9635                 renderMenu : function() {
9636                         var t = this, m;
9637
9638                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9639                                 menu_line : 1,
9640                                 'class' : this.classPrefix + 'Menu',
9641                                 icons : t.settings.icons
9642                         });
9643
9644                         m.onHideMenu.add(function() {
9645                                 t.hideMenu();
9646                                 t.focus();
9647                         });
9648
9649                         t.onRenderMenu.dispatch(t, m);
9650                         t.menu = m;
9651                 },
9652
9653                 hideMenu : function(e) {
9654                         var t = this;
9655
9656                         // Prevent double toogles by canceling the mouse click event to the button
9657                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
9658                                 return;
9659
9660                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9661                                 t.setState('Selected', 0);
9662                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9663                                 if (t.menu)
9664                                         t.menu.hideMenu();
9665                         }
9666
9667                         t.isMenuVisible = 0;
9668                 },
9669
9670                 postRender : function() {
9671                         var t = this, s = t.settings;
9672
9673                         Event.add(t.id, 'click', function() {
9674                                 if (!t.isDisabled()) {
9675                                         if (s.onclick)
9676                                                 s.onclick(t.value);
9677
9678                                         t.showMenu();
9679                                 }
9680                         });
9681                 }
9682         });
9683 })(tinymce);
9684
9685 (function(tinymce) {
9686         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9687
9688         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
9689                 SplitButton : function(id, s, ed) {
9690                         this.parent(id, s, ed);
9691                         this.classPrefix = 'mceSplitButton';
9692                 },
9693
9694                 renderHTML : function() {
9695                         var h, t = this, s = t.settings, h1;
9696
9697                         h = '<tbody><tr>';
9698
9699                         if (s.image)
9700                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
9701                         else
9702                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
9703
9704                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
9705                         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>';
9706         
9707                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
9708                         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>';
9709
9710                         h += '</tr></tbody>';
9711                         h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
9712                         return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
9713                 },
9714
9715                 postRender : function() {
9716                         var t = this, s = t.settings, activate;
9717
9718                         if (s.onclick) {
9719                                 activate = function(evt) {
9720                                         if (!t.isDisabled()) {
9721                                                 s.onclick(t.value);
9722                                                 Event.cancel(evt);
9723                                         }
9724                                 };
9725                                 Event.add(t.id + '_action', 'click', activate);
9726                                 Event.add(t.id, ['click', 'keydown'], function(evt) {
9727                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
9728                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
9729                                                 activate();
9730                                                 Event.cancel(evt);
9731                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
9732                                                 t.showMenu();
9733                                                 Event.cancel(evt);
9734                                         }
9735                                 });
9736                         }
9737
9738                         Event.add(t.id + '_open', 'click', function (evt) {
9739                                 t.showMenu();
9740                                 Event.cancel(evt);
9741                         });
9742                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
9743                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
9744
9745                         // Old IE doesn't have hover on all elements
9746                         if (tinymce.isIE6 || !DOM.boxModel) {
9747                                 Event.add(t.id, 'mouseover', function() {
9748                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9749                                                 DOM.addClass(t.id, 'mceSplitButtonHover');
9750                                 });
9751
9752                                 Event.add(t.id, 'mouseout', function() {
9753                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9754                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');
9755                                 });
9756                         }
9757                 },
9758
9759                 destroy : function() {
9760                         this.parent();
9761
9762                         Event.clear(this.id + '_action');
9763                         Event.clear(this.id + '_open');
9764                         Event.clear(this.id);
9765                 }
9766         });
9767 })(tinymce);
9768
9769 (function(tinymce) {
9770         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
9771
9772         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
9773                 ColorSplitButton : function(id, s, ed) {
9774                         var t = this;
9775
9776                         t.parent(id, s, ed);
9777
9778                         t.settings = s = tinymce.extend({
9779                                 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',
9780                                 grid_width : 8,
9781                                 default_color : '#888888'
9782                         }, t.settings);
9783
9784                         t.onShowMenu = new tinymce.util.Dispatcher(t);
9785
9786                         t.onHideMenu = new tinymce.util.Dispatcher(t);
9787
9788                         t.value = s.default_color;
9789                 },
9790
9791                 showMenu : function() {
9792                         var t = this, r, p, e, p2;
9793
9794                         if (t.isDisabled())
9795                                 return;
9796
9797                         if (!t.isMenuRendered) {
9798                                 t.renderMenu();
9799                                 t.isMenuRendered = true;
9800                         }
9801
9802                         if (t.isMenuVisible)
9803                                 return t.hideMenu();
9804
9805                         e = DOM.get(t.id);
9806                         DOM.show(t.id + '_menu');
9807                         DOM.addClass(e, 'mceSplitButtonSelected');
9808                         p2 = DOM.getPos(e);
9809                         DOM.setStyles(t.id + '_menu', {
9810                                 left : p2.x,
9811                                 top : p2.y + e.clientHeight,
9812                                 zIndex : 200000
9813                         });
9814                         e = 0;
9815
9816                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9817                         t.onShowMenu.dispatch(t);
9818
9819                         if (t._focused) {
9820                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
9821                                         if (e.keyCode == 27)
9822                                                 t.hideMenu();
9823                                 });
9824
9825                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
9826                         }
9827
9828                         t.isMenuVisible = 1;
9829                 },
9830
9831                 hideMenu : function(e) {
9832                         var t = this;
9833
9834                         if (t.isMenuVisible) {
9835                                 // Prevent double toogles by canceling the mouse click event to the button
9836                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
9837                                         return;
9838
9839                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
9840                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');
9841                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9842                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
9843                                         DOM.hide(t.id + '_menu');
9844                                 }
9845
9846                                 t.isMenuVisible = 0;
9847                         }
9848                 },
9849
9850                 renderMenu : function() {
9851                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
9852
9853                         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;'});
9854                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
9855                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});
9856
9857                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
9858                         tb = DOM.add(n, 'tbody');
9859
9860                         // Generate color grid
9861                         i = 0;
9862                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
9863                                 c = c.replace(/^#/, '');
9864
9865                                 if (!i--) {
9866                                         tr = DOM.add(tb, 'tr');
9867                                         i = s.grid_width - 1;
9868                                 }
9869
9870                                 n = DOM.add(tr, 'td');
9871                                 n = DOM.add(n, 'a', {
9872                                         role : 'option',
9873                                         href : 'javascript:;',
9874                                         style : {
9875                                                 backgroundColor : '#' + c
9876                                         },
9877                                         'title': t.editor.getLang('colors.' + c, c),
9878                                         'data-mce-color' : '#' + c
9879                                 });
9880
9881                                 if (t.editor.forcedHighContrastMode) {
9882                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
9883                                         if (n.getContext && (context = n.getContext("2d"))) {
9884                                                 context.fillStyle = '#' + c;
9885                                                 context.fillRect(0, 0, 16, 16);
9886                                         } else {
9887                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.
9888                                                 DOM.remove(n);
9889                                         }
9890                                 }
9891                         });
9892
9893                         if (s.more_colors_func) {
9894                                 n = DOM.add(tb, 'tr');
9895                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
9896                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
9897
9898                                 Event.add(n, 'click', function(e) {
9899                                         s.more_colors_func.call(s.more_colors_scope || this);
9900                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
9901                                 });
9902                         }
9903
9904                         DOM.addClass(m, 'mceColorSplitMenu');
9905                         
9906                         new tinymce.ui.KeyboardNavigation({
9907                                 root: t.id + '_menu',
9908                                 items: DOM.select('a', t.id + '_menu'),
9909                                 onCancel: function() {
9910                                         t.hideMenu();
9911                                         t.focus();
9912                                 }
9913                         });
9914
9915                         // Prevent IE from scrolling and hindering click to occur #4019
9916                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
9917
9918                         Event.add(t.id + '_menu', 'click', function(e) {
9919                                 var c;
9920
9921                                 e = DOM.getParent(e.target, 'a', tb);
9922
9923                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
9924                                         t.setColor(c);
9925
9926                                 return Event.cancel(e); // Prevent IE auto save warning
9927                         });
9928
9929                         return w;
9930                 },
9931
9932                 setColor : function(c) {
9933                         this.displayColor(c);
9934                         this.hideMenu();
9935                         this.settings.onselect(c);
9936                 },
9937                 
9938                 displayColor : function(c) {
9939                         var t = this;
9940
9941                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
9942
9943                         t.value = c;
9944                 },
9945
9946                 postRender : function() {
9947                         var t = this, id = t.id;
9948
9949                         t.parent();
9950                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
9951                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
9952                 },
9953
9954                 destroy : function() {
9955                         this.parent();
9956
9957                         Event.clear(this.id + '_menu');
9958                         Event.clear(this.id + '_more');
9959                         DOM.remove(this.id + '_menu');
9960                 }
9961         });
9962 })(tinymce);
9963
9964 (function(tinymce) {
9965 // Shorten class names
9966 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
9967 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
9968         renderHTML : function() {
9969                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
9970
9971                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
9972                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
9973                 h.push("<span role='application'>");
9974                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
9975                 each(controls, function(toolbar) {
9976                         h.push(toolbar.renderHTML());
9977                 });
9978                 h.push("</span>");
9979                 h.push('</div>');
9980
9981                 return h.join('');
9982         },
9983         
9984         focus : function() {
9985                 this.keyNav.focus();
9986         },
9987         
9988         postRender : function() {
9989                 var t = this, items = [];
9990
9991                 each(t.controls, function(toolbar) {
9992                         each (toolbar.controls, function(control) {
9993                                 if (control.id) {
9994                                         items.push(control);
9995                                 }
9996                         });
9997                 });
9998
9999                 t.keyNav = new tinymce.ui.KeyboardNavigation({
10000                         root: t.id,
10001                         items: items,
10002                         onCancel: function() {
10003                                 t.editor.focus();
10004                         },
10005                         excludeFromTabOrder: !t.settings.tab_focus_toolbar
10006                 });
10007         },
10008         
10009         destroy : function() {
10010                 var self = this;
10011
10012                 self.parent();
10013                 self.keyNav.destroy();
10014                 Event.clear(self.id);
10015         }
10016 });
10017 })(tinymce);
10018
10019 (function(tinymce) {
10020 // Shorten class names
10021 var dom = tinymce.DOM, each = tinymce.each;
10022 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10023         renderHTML : function() {
10024                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
10025
10026                 cl = t.controls;
10027                 for (i=0; i<cl.length; i++) {
10028                         // Get current control, prev control, next control and if the control is a list box or not
10029                         co = cl[i];
10030                         pr = cl[i - 1];
10031                         nx = cl[i + 1];
10032
10033                         // Add toolbar start
10034                         if (i === 0) {
10035                                 c = 'mceToolbarStart';
10036
10037                                 if (co.Button)
10038                                         c += ' mceToolbarStartButton';
10039                                 else if (co.SplitButton)
10040                                         c += ' mceToolbarStartSplitButton';
10041                                 else if (co.ListBox)
10042                                         c += ' mceToolbarStartListBox';
10043
10044                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10045                         }
10046
10047                         // Add toolbar end before list box and after the previous button
10048                         // This is to fix the o2k7 editor skins
10049                         if (pr && co.ListBox) {
10050                                 if (pr.Button || pr.SplitButton)
10051                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
10052                         }
10053
10054                         // Render control HTML
10055
10056                         // IE 8 quick fix, needed to propertly generate a hit area for anchors
10057                         if (dom.stdMode)
10058                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
10059                         else
10060                                 h += '<td>' + co.renderHTML() + '</td>';
10061
10062                         // Add toolbar start after list box and before the next button
10063                         // This is to fix the o2k7 editor skins
10064                         if (nx && co.ListBox) {
10065                                 if (nx.Button || nx.SplitButton)
10066                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
10067                         }
10068                 }
10069
10070                 c = 'mceToolbarEnd';
10071
10072                 if (co.Button)
10073                         c += ' mceToolbarEndButton';
10074                 else if (co.SplitButton)
10075                         c += ' mceToolbarEndSplitButton';
10076                 else if (co.ListBox)
10077                         c += ' mceToolbarEndListBox';
10078
10079                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10080
10081                 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>');
10082         }
10083 });
10084 })(tinymce);
10085
10086 (function(tinymce) {
10087         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
10088
10089         tinymce.create('tinymce.AddOnManager', {
10090                 AddOnManager : function() {
10091                         var self = this;
10092
10093                         self.items = [];
10094                         self.urls = {};
10095                         self.lookup = {};
10096                         self.onAdd = new Dispatcher(self);
10097                 },
10098
10099                 get : function(n) {
10100                         return this.lookup[n];
10101                 },
10102
10103                 requireLangPack : function(n) {
10104                         var s = tinymce.settings;
10105
10106                         if (s && s.language && s.language_load !== false)
10107                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
10108                 },
10109
10110                 add : function(id, o) {
10111                         this.items.push(o);
10112                         this.lookup[id] = o;
10113                         this.onAdd.dispatch(this, id, o);
10114
10115                         return o;
10116                 },
10117
10118                 load : function(n, u, cb, s) {
10119                         var t = this;
10120
10121                         if (t.urls[n])
10122                                 return;
10123
10124                         if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
10125                                 u = tinymce.baseURL + '/' + u;
10126
10127                         t.urls[n] = u.substring(0, u.lastIndexOf('/'));
10128
10129                         if (!t.lookup[n])
10130                                 tinymce.ScriptLoader.add(u, cb, s);
10131                 }
10132         });
10133
10134         // Create plugin and theme managers
10135         tinymce.PluginManager = new tinymce.AddOnManager();
10136         tinymce.ThemeManager = new tinymce.AddOnManager();
10137 }(tinymce));
10138
10139 (function(tinymce) {
10140         // Shorten names
10141         var each = tinymce.each, extend = tinymce.extend,
10142                 DOM = tinymce.DOM, Event = tinymce.dom.Event,
10143                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10144                 explode = tinymce.explode,
10145                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
10146
10147         // Setup some URLs where the editor API is located and where the document is
10148         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
10149         if (!/[\/\\]$/.test(tinymce.documentBaseURL))
10150                 tinymce.documentBaseURL += '/';
10151
10152         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
10153
10154         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
10155
10156         // Add before unload listener
10157         // This was required since IE was leaking memory if you added and removed beforeunload listeners
10158         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
10159         tinymce.onBeforeUnload = new Dispatcher(tinymce);
10160
10161         // Must be on window or IE will leak if the editor is placed in frame or iframe
10162         Event.add(window, 'beforeunload', function(e) {
10163                 tinymce.onBeforeUnload.dispatch(tinymce, e);
10164         });
10165
10166         tinymce.onAddEditor = new Dispatcher(tinymce);
10167
10168         tinymce.onRemoveEditor = new Dispatcher(tinymce);
10169
10170         tinymce.EditorManager = extend(tinymce, {
10171                 editors : [],
10172
10173                 i18n : {},
10174
10175                 activeEditor : null,
10176
10177                 init : function(s) {
10178                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
10179
10180                         function execCallback(se, n, s) {
10181                                 var f = se[n];
10182
10183                                 if (!f)
10184                                         return;
10185
10186                                 if (tinymce.is(f, 'string')) {
10187                                         s = f.replace(/\.\w+$/, '');
10188                                         s = s ? tinymce.resolve(s) : 0;
10189                                         f = tinymce.resolve(f);
10190                                 }
10191
10192                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
10193                         };
10194
10195                         s = extend({
10196                                 theme : "simple",
10197                                 language : "en"
10198                         }, s);
10199
10200                         t.settings = s;
10201
10202                         // Legacy call
10203                         Event.add(document, 'init', function() {
10204                                 var l, co;
10205
10206                                 execCallback(s, 'onpageload');
10207
10208                                 switch (s.mode) {
10209                                         case "exact":
10210                                                 l = s.elements || '';
10211
10212                                                 if(l.length > 0) {
10213                                                         each(explode(l), function(v) {
10214                                                                 if (DOM.get(v)) {
10215                                                                         ed = new tinymce.Editor(v, s);
10216                                                                         el.push(ed);
10217                                                                         ed.render(1);
10218                                                                 } else {
10219                                                                         each(document.forms, function(f) {
10220                                                                                 each(f.elements, function(e) {
10221                                                                                         if (e.name === v) {
10222                                                                                                 v = 'mce_editor_' + instanceCounter++;
10223                                                                                                 DOM.setAttrib(e, 'id', v);
10224
10225                                                                                                 ed = new tinymce.Editor(v, s);
10226                                                                                                 el.push(ed);
10227                                                                                                 ed.render(1);
10228                                                                                         }
10229                                                                                 });
10230                                                                         });
10231                                                                 }
10232                                                         });
10233                                                 }
10234                                                 break;
10235
10236                                         case "textareas":
10237                                         case "specific_textareas":
10238                                                 function hasClass(n, c) {
10239                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
10240                                                 };
10241
10242                                                 each(DOM.select('textarea'), function(v) {
10243                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))
10244                                                                 return;
10245
10246                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {
10247                                                                 // Can we use the name
10248                                                                 e = DOM.get(v.name);
10249                                                                 if (!v.id && !e)
10250                                                                         v.id = v.name;
10251
10252                                                                 // Generate unique name if missing or already exists
10253                                                                 if (!v.id || t.get(v.id))
10254                                                                         v.id = DOM.uniqueId();
10255
10256                                                                 ed = new tinymce.Editor(v.id, s);
10257                                                                 el.push(ed);
10258                                                                 ed.render(1);
10259                                                         }
10260                                                 });
10261                                                 break;
10262                                 }
10263
10264                                 // Call onInit when all editors are initialized
10265                                 if (s.oninit) {
10266                                         l = co = 0;
10267
10268                                         each(el, function(ed) {
10269                                                 co++;
10270
10271                                                 if (!ed.initialized) {
10272                                                         // Wait for it
10273                                                         ed.onInit.add(function() {
10274                                                                 l++;
10275
10276                                                                 // All done
10277                                                                 if (l == co)
10278                                                                         execCallback(s, 'oninit');
10279                                                         });
10280                                                 } else
10281                                                         l++;
10282
10283                                                 // All done
10284                                                 if (l == co)
10285                                                         execCallback(s, 'oninit');                                      
10286                                         });
10287                                 }
10288                         });
10289                 },
10290
10291                 get : function(id) {
10292                         if (id === undefined)
10293                                 return this.editors;
10294
10295                         return this.editors[id];
10296                 },
10297
10298                 getInstanceById : function(id) {
10299                         return this.get(id);
10300                 },
10301
10302                 add : function(editor) {
10303                         var self = this, editors = self.editors;
10304
10305                         // Add named and index editor instance
10306                         editors[editor.id] = editor;
10307                         editors.push(editor);
10308
10309                         self._setActive(editor);
10310                         self.onAddEditor.dispatch(self, editor);
10311
10312
10313                         return editor;
10314                 },
10315
10316                 remove : function(editor) {
10317                         var t = this, i, editors = t.editors;
10318
10319                         // Not in the collection
10320                         if (!editors[editor.id])
10321                                 return null;
10322
10323                         delete editors[editor.id];
10324
10325                         for (i = 0; i < editors.length; i++) {
10326                                 if (editors[i] == editor) {
10327                                         editors.splice(i, 1);
10328                                         break;
10329                                 }
10330                         }
10331
10332                         // Select another editor since the active one was removed
10333                         if (t.activeEditor == editor)
10334                                 t._setActive(editors[0]);
10335
10336                         editor.destroy();
10337                         t.onRemoveEditor.dispatch(t, editor);
10338
10339                         return editor;
10340                 },
10341
10342                 execCommand : function(c, u, v) {
10343                         var t = this, ed = t.get(v), w;
10344
10345                         // Manager commands
10346                         switch (c) {
10347                                 case "mceFocus":
10348                                         ed.focus();
10349                                         return true;
10350
10351                                 case "mceAddEditor":
10352                                 case "mceAddControl":
10353                                         if (!t.get(v))
10354                                                 new tinymce.Editor(v, t.settings).render();
10355
10356                                         return true;
10357
10358                                 case "mceAddFrameControl":
10359                                         w = v.window;
10360
10361                                         // Add tinyMCE global instance and tinymce namespace to specified window
10362                                         w.tinyMCE = tinyMCE;
10363                                         w.tinymce = tinymce;
10364
10365                                         tinymce.DOM.doc = w.document;
10366                                         tinymce.DOM.win = w;
10367
10368                                         ed = new tinymce.Editor(v.element_id, v);
10369                                         ed.render();
10370
10371                                         // Fix IE memory leaks
10372                                         if (tinymce.isIE) {
10373                                                 function clr() {
10374                                                         ed.destroy();
10375                                                         w.detachEvent('onunload', clr);
10376                                                         w = w.tinyMCE = w.tinymce = null; // IE leak
10377                                                 };
10378
10379                                                 w.attachEvent('onunload', clr);
10380                                         }
10381
10382                                         v.page_window = null;
10383
10384                                         return true;
10385
10386                                 case "mceRemoveEditor":
10387                                 case "mceRemoveControl":
10388                                         if (ed)
10389                                                 ed.remove();
10390
10391                                         return true;
10392
10393                                 case 'mceToggleEditor':
10394                                         if (!ed) {
10395                                                 t.execCommand('mceAddControl', 0, v);
10396                                                 return true;
10397                                         }
10398
10399                                         if (ed.isHidden())
10400                                                 ed.show();
10401                                         else
10402                                                 ed.hide();
10403
10404                                         return true;
10405                         }
10406
10407                         // Run command on active editor
10408                         if (t.activeEditor)
10409                                 return t.activeEditor.execCommand(c, u, v);
10410
10411                         return false;
10412                 },
10413
10414                 execInstanceCommand : function(id, c, u, v) {
10415                         var ed = this.get(id);
10416
10417                         if (ed)
10418                                 return ed.execCommand(c, u, v);
10419
10420                         return false;
10421                 },
10422
10423                 triggerSave : function() {
10424                         each(this.editors, function(e) {
10425                                 e.save();
10426                         });
10427                 },
10428
10429                 addI18n : function(p, o) {
10430                         var lo, i18n = this.i18n;
10431
10432                         if (!tinymce.is(p, 'string')) {
10433                                 each(p, function(o, lc) {
10434                                         each(o, function(o, g) {
10435                                                 each(o, function(o, k) {
10436                                                         if (g === 'common')
10437                                                                 i18n[lc + '.' + k] = o;
10438                                                         else
10439                                                                 i18n[lc + '.' + g + '.' + k] = o;
10440                                                 });
10441                                         });
10442                                 });
10443                         } else {
10444                                 each(o, function(o, k) {
10445                                         i18n[p + '.' + k] = o;
10446                                 });
10447                         }
10448                 },
10449
10450                 // Private methods
10451
10452                 _setActive : function(editor) {
10453                         this.selectedInstance = this.activeEditor = editor;
10454                 }
10455         });
10456 })(tinymce);
10457
10458 (function(tinymce) {
10459         // Shorten these names
10460         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
10461                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10462                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10463                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10464                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
10465
10466         tinymce.create('tinymce.Editor', {
10467                 Editor : function(id, s) {
10468                         var t = this;
10469
10470                         t.id = t.editorId = id;
10471
10472                         t.execCommands = {};
10473                         t.queryStateCommands = {};
10474                         t.queryValueCommands = {};
10475
10476                         t.isNotDirty = false;
10477
10478                         t.plugins = {};
10479
10480                         // Add events to the editor
10481                         each([
10482                                 'onPreInit',
10483
10484                                 'onBeforeRenderUI',
10485
10486                                 'onPostRender',
10487
10488                                 'onInit',
10489
10490                                 'onRemove',
10491
10492                                 'onActivate',
10493
10494                                 'onDeactivate',
10495
10496                                 'onClick',
10497
10498                                 'onEvent',
10499
10500                                 'onMouseUp',
10501
10502                                 'onMouseDown',
10503
10504                                 'onDblClick',
10505
10506                                 'onKeyDown',
10507
10508                                 'onKeyUp',
10509
10510                                 'onKeyPress',
10511
10512                                 'onContextMenu',
10513
10514                                 'onSubmit',
10515
10516                                 'onReset',
10517
10518                                 'onPaste',
10519
10520                                 'onPreProcess',
10521
10522                                 'onPostProcess',
10523
10524                                 'onBeforeSetContent',
10525
10526                                 'onBeforeGetContent',
10527
10528                                 'onSetContent',
10529
10530                                 'onGetContent',
10531
10532                                 'onLoadContent',
10533
10534                                 'onSaveContent',
10535
10536                                 'onNodeChange',
10537
10538                                 'onChange',
10539
10540                                 'onBeforeExecCommand',
10541
10542                                 'onExecCommand',
10543
10544                                 'onUndo',
10545
10546                                 'onRedo',
10547
10548                                 'onVisualAid',
10549
10550                                 'onSetProgressState'
10551                         ], function(e) {
10552                                 t[e] = new Dispatcher(t);
10553                         });
10554
10555                         t.settings = s = extend({
10556                                 id : id,
10557                                 language : 'en',
10558                                 docs_language : 'en',
10559                                 theme : 'simple',
10560                                 skin : 'default',
10561                                 delta_width : 0,
10562                                 delta_height : 0,
10563                                 popup_css : '',
10564                                 plugins : '',
10565                                 document_base_url : tinymce.documentBaseURL,
10566                                 add_form_submit_trigger : 1,
10567                                 submit_patch : 1,
10568                                 add_unload_trigger : 1,
10569                                 convert_urls : 1,
10570                                 relative_urls : 1,
10571                                 remove_script_host : 1,
10572                                 table_inline_editing : 0,
10573                                 object_resizing : 1,
10574                                 cleanup : 1,
10575                                 accessibility_focus : 1,
10576                                 custom_shortcuts : 1,
10577                                 custom_undo_redo_keyboard_shortcuts : 1,
10578                                 custom_undo_redo_restore_selection : 1,
10579                                 custom_undo_redo : 1,
10580                                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
10581                                 visual_table_class : 'mceItemTable',
10582                                 visual : 1,
10583                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
10584                                 apply_source_formatting : 1,
10585                                 directionality : 'ltr',
10586                                 forced_root_block : 'p',
10587                                 hidden_input : 1,
10588                                 padd_empty_editor : 1,
10589                                 render_ui : 1,
10590                                 init_theme : 1,
10591                                 force_p_newlines : 1,
10592                                 indentation : '30px',
10593                                 keep_styles : 1,
10594                                 fix_table_elements : 1,
10595                                 inline_styles : 1,
10596                                 convert_fonts_to_spans : true,
10597                                 indent : 'simple',
10598                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10599                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10600                                 validate : true,
10601                                 entity_encoding : 'named',
10602                                 url_converter : t.convertURL,
10603                                 url_converter_scope : t,
10604                                 ie7_compat : true
10605                         }, s);
10606
10607                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
10608                                 base_uri : tinyMCE.baseURI
10609                         });
10610
10611                         t.baseURI = tinymce.baseURI;
10612
10613                         t.contentCSS = [];
10614
10615                         // Call setup
10616                         t.execCallback('setup', t);
10617                 },
10618
10619                 render : function(nst) {
10620                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
10621
10622                         // Page is not loaded yet, wait for it
10623                         if (!Event.domLoaded) {
10624                                 Event.add(document, 'init', function() {
10625                                         t.render();
10626                                 });
10627                                 return;
10628                         }
10629
10630                         tinyMCE.settings = s;
10631
10632                         // Element not found, then skip initialization
10633                         if (!t.getElement())
10634                                 return;
10635
10636                         // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
10637                         // browser says it has contentEditable support but there is no visible caret
10638                         // We will remove this check ones Apple implements full contentEditable support
10639                         if (tinymce.isIDevice)
10640                                 return;
10641
10642                         // Add hidden input for non input elements inside form elements
10643                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
10644                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
10645
10646                         if (tinymce.WindowManager)
10647                                 t.windowManager = new tinymce.WindowManager(t);
10648
10649                         if (s.encoding == 'xml') {
10650                                 t.onGetContent.add(function(ed, o) {
10651                                         if (o.save)
10652                                                 o.content = DOM.encode(o.content);
10653                                 });
10654                         }
10655
10656                         if (s.add_form_submit_trigger) {
10657                                 t.onSubmit.addToTop(function() {
10658                                         if (t.initialized) {
10659                                                 t.save();
10660                                                 t.isNotDirty = 1;
10661                                         }
10662                                 });
10663                         }
10664
10665                         if (s.add_unload_trigger) {
10666                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
10667                                         if (t.initialized && !t.destroyed && !t.isHidden())
10668                                                 t.save({format : 'raw', no_events : true});
10669                                 });
10670                         }
10671
10672                         tinymce.addUnload(t.destroy, t);
10673
10674                         if (s.submit_patch) {
10675                                 t.onBeforeRenderUI.add(function() {
10676                                         var n = t.getElement().form;
10677
10678                                         if (!n)
10679                                                 return;
10680
10681                                         // Already patched
10682                                         if (n._mceOldSubmit)
10683                                                 return;
10684
10685                                         // Check page uses id="submit" or name="submit" for it's submit button
10686                                         if (!n.submit.nodeType && !n.submit.length) {
10687                                                 t.formElement = n;
10688                                                 n._mceOldSubmit = n.submit;
10689                                                 n.submit = function() {
10690                                                         // Save all instances
10691                                                         tinymce.triggerSave();
10692                                                         t.isNotDirty = 1;
10693
10694                                                         return t.formElement._mceOldSubmit(t.formElement);
10695                                                 };
10696                                         }
10697
10698                                         n = null;
10699                                 });
10700                         }
10701
10702                         // Load scripts
10703                         function loadScripts() {
10704                                 if (s.language && s.language_load !== false)
10705                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
10706
10707                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
10708                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
10709
10710                                 each(explode(s.plugins), function(p) {
10711                                         if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
10712                                                 // Skip safari plugin, since it is removed as of 3.3b1
10713                                                 if (p == 'safari')
10714                                                         return;
10715
10716                                                 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
10717                                         }
10718                                 });
10719
10720                                 // Init when que is loaded
10721                                 sl.loadQueue(function() {
10722                                         if (!t.removed)
10723                                                 t.init();
10724                                 });
10725                         };
10726
10727                         loadScripts();
10728                 },
10729
10730                 init : function() {
10731                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i;
10732
10733                         tinymce.add(t);
10734
10735                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
10736
10737                         if (s.theme) {
10738                                 s.theme = s.theme.replace(/-/, '');
10739                                 o = ThemeManager.get(s.theme);
10740                                 t.theme = new o();
10741
10742                                 if (t.theme.init && s.init_theme)
10743                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
10744                         }
10745
10746                         // Create all plugins
10747                         each(explode(s.plugins.replace(/\-/g, '')), function(p) {
10748                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
10749
10750                                 if (c) {
10751                                         po = new c(t, u);
10752
10753                                         t.plugins[p] = po;
10754
10755                                         if (po.init)
10756                                                 po.init(t, u);
10757                                 }
10758                         });
10759
10760                         // Setup popup CSS path(s)
10761                         if (s.popup_css !== false) {
10762                                 if (s.popup_css)
10763                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
10764                                 else
10765                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
10766                         }
10767
10768                         if (s.popup_css_add)
10769                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
10770
10771                         t.controlManager = new tinymce.ControlManager(t);
10772
10773                         if (s.custom_undo_redo) {
10774                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
10775                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10776                                                 t.undoManager.beforeChange();
10777                                 });
10778
10779                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
10780                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10781                                                 t.undoManager.add();
10782                                 });
10783                         }
10784
10785                         t.onExecCommand.add(function(ed, c) {
10786                                 // Don't refresh the select lists until caret move
10787                                 if (!/^(FontName|FontSize)$/.test(c))
10788                                         t.nodeChanged();
10789                         });
10790
10791                         // Remove ghost selections on images and tables in Gecko
10792                         if (isGecko) {
10793                                 function repaint(a, o) {
10794                                         if (!o || !o.initial)
10795                                                 t.execCommand('mceRepaint');
10796                                 };
10797
10798                                 t.onUndo.add(repaint);
10799                                 t.onRedo.add(repaint);
10800                                 t.onSetContent.add(repaint);
10801                         }
10802
10803                         // Enables users to override the control factory
10804                         t.onBeforeRenderUI.dispatch(t, t.controlManager);
10805
10806                         // Measure box
10807                         if (s.render_ui) {
10808                                 w = s.width || e.style.width || e.offsetWidth;
10809                                 h = s.height || e.style.height || e.offsetHeight;
10810                                 t.orgDisplay = e.style.display;
10811                                 re = /^[0-9\.]+(|px)$/i;
10812
10813                                 if (re.test('' + w))
10814                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
10815
10816                                 if (re.test('' + h))
10817                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
10818
10819                                 // Render UI
10820                                 o = t.theme.renderUI({
10821                                         targetNode : e,
10822                                         width : w,
10823                                         height : h,
10824                                         deltaWidth : s.delta_width,
10825                                         deltaHeight : s.delta_height
10826                                 });
10827
10828                                 t.editorContainer = o.editorContainer;
10829                         }
10830
10831
10832                         // User specified a document.domain value
10833                         if (document.domain && location.hostname != document.domain)
10834                                 tinymce.relaxedDomain = document.domain;
10835
10836                         // Resize editor
10837                         DOM.setStyles(o.sizeContainer || o.editorContainer, {
10838                                 width : w,
10839                                 height : h
10840                         });
10841
10842                         // Load specified content CSS last
10843                         if (s.content_css) {
10844                                 tinymce.each(explode(s.content_css), function(u) {
10845                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
10846                                 });
10847                         }
10848
10849                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
10850                         if (h < 100)
10851                                 h = 100;
10852
10853                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
10854
10855                         // We only need to override paths if we have to
10856                         // IE has a bug where it remove site absolute urls to relative ones if this is specified
10857                         if (s.document_base_url != tinymce.documentBaseURL)
10858                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
10859
10860                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
10861                         if (s.ie7_compat)
10862                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
10863                         else
10864                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
10865
10866                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
10867
10868                         // Firefox 2 doesn't load stylesheets correctly this way
10869                         if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
10870                                 for (i = 0; i < t.contentCSS.length; i++)
10871                                         t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
10872
10873                                 t.contentCSS = [];
10874                         }
10875
10876                         bi = s.body_id || 'tinymce';
10877                         if (bi.indexOf('=') != -1) {
10878                                 bi = t.getParam('body_id', '', 'hash');
10879                                 bi = bi[t.id] || bi;
10880                         }
10881
10882                         bc = s.body_class || '';
10883                         if (bc.indexOf('=') != -1) {
10884                                 bc = t.getParam('body_class', '', 'hash');
10885                                 bc = bc[t.id] || '';
10886                         }
10887
10888                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
10889
10890                         // Domain relaxing enabled, then set document domain
10891                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
10892                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
10893                                 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();})()';                         
10894                         }
10895
10896                         // Create iframe
10897                         // TODO: ACC add the appropriate description on this.
10898                         n = DOM.add(o.iframeContainer, 'iframe', { 
10899                                 id : t.id + "_ifr",
10900                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
10901                                 frameBorder : '0', 
10902                                 title : s.aria_label,
10903                                 style : {
10904                                         width : '100%',
10905                                         height : h
10906                                 }
10907                         });
10908
10909                         t.contentAreaContainer = o.iframeContainer;
10910                         DOM.get(o.editorContainer).style.display = t.orgDisplay;
10911                         DOM.get(t.id).style.display = 'none';
10912                         DOM.setAttrib(t.id, 'aria-hidden', true);
10913
10914                         if (!tinymce.relaxedDomain || !u)
10915                                 t.setupIframe();
10916
10917                         e = n = o = null; // Cleanup
10918                 },
10919
10920                 setupIframe : function() {
10921                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
10922
10923                         // Setup iframe body
10924                         if (!isIE || !tinymce.relaxedDomain) {
10925                                 d.open();
10926                                 d.write(t.iframeHTML);
10927                                 d.close();
10928
10929                                 if (tinymce.relaxedDomain)
10930                                         d.domain = tinymce.relaxedDomain;
10931                         }
10932
10933                         // Design mode needs to be added here Ctrl+A will fail otherwise
10934                         if (!isIE) {
10935                                 try {
10936                                         if (!s.readonly)
10937                                                 d.designMode = 'On';
10938                                 } catch (ex) {
10939                                         // Will fail on Gecko if the editor is placed in an hidden container element
10940                                         // The design mode will be set ones the editor is focused
10941                                 }
10942                         }
10943
10944                         // IE needs to use contentEditable or it will display non secure items for HTTPS
10945                         if (isIE) {
10946                                 // It will not steal focus if we hide it while setting contentEditable
10947                                 b = t.getBody();
10948                                 DOM.hide(b);
10949
10950                                 if (!s.readonly)
10951                                         b.contentEditable = true;
10952
10953                                 DOM.show(b);
10954                         }
10955
10956                         t.schema = new tinymce.html.Schema(s);
10957
10958                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
10959                                 keep_values : true,
10960                                 url_converter : t.convertURL,
10961                                 url_converter_scope : t,
10962                                 hex_colors : s.force_hex_style_colors,
10963                                 class_filter : s.class_filter,
10964                                 update_styles : 1,
10965                                 fix_ie_paragraphs : 1,
10966                                 schema : t.schema
10967                         });
10968
10969                         t.parser = new tinymce.html.DomParser(s, t.schema);
10970
10971                         // Force anchor names closed
10972                         t.parser.addAttributeFilter('name', function(nodes, name) {
10973                                 var i = nodes.length, sibling, prevSibling, parent, node;
10974
10975                                 while (i--) {
10976                                         node = nodes[i];
10977                                         if (node.name === 'a' && node.firstChild) {
10978                                                 parent = node.parent;
10979
10980                                                 // Move children after current node
10981                                                 sibling = node.lastChild;
10982                                                 do {
10983                                                         prevSibling = sibling.prev;
10984                                                         parent.insert(sibling, node);
10985                                                         sibling = prevSibling;
10986                                                 } while (sibling);
10987                                         }
10988                                 }
10989                         });
10990
10991                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style
10992                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
10993                                 var i = nodes.length, node, dom = t.dom, value;
10994
10995                                 while (i--) {
10996                                         node = nodes[i];
10997                                         value = node.attr(name);
10998
10999                                         if (name === "style")
11000                                                 node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name));
11001                                         else
11002                                                 node.attr('data-mce-' + name, t.convertURL(value, name, node.name));
11003                                 }
11004                         });
11005
11006                         // Keep scripts from executing
11007                         t.parser.addNodeFilter('script', function(nodes, name) {
11008                                 var i = nodes.length;
11009
11010                                 while (i--)
11011                                         nodes[i].attr('type', 'mce-text/javascript');
11012                         });
11013
11014                         t.parser.addNodeFilter('#cdata', function(nodes, name) {
11015                                 var i = nodes.length, node;
11016
11017                                 while (i--) {
11018                                         node = nodes[i];
11019                                         node.type = 8;
11020                                         node.name = '#comment';
11021                                         node.value = '[CDATA[' + node.value + ']]';
11022                                 }
11023                         });
11024
11025                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
11026                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
11027
11028                                 while (i--) {
11029                                         node = nodes[i];
11030
11031                                         if (node.isEmpty(nonEmptyElements))
11032                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
11033                                 }
11034                         });
11035
11036                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
11037
11038                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
11039
11040                         t.formatter = new tinymce.Formatter(this);
11041
11042                         // Register default formats
11043                         t.formatter.register({
11044                                 alignleft : [
11045                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
11046                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
11047                                 ],
11048
11049                                 aligncenter : [
11050                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
11051                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
11052                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
11053                                 ],
11054
11055                                 alignright : [
11056                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
11057                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
11058                                 ],
11059
11060                                 alignfull : [
11061                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
11062                                 ],
11063
11064                                 bold : [
11065                                         {inline : 'strong', remove : 'all'},
11066                                         {inline : 'span', styles : {fontWeight : 'bold'}},
11067                                         {inline : 'b', remove : 'all'}
11068                                 ],
11069
11070                                 italic : [
11071                                         {inline : 'em', remove : 'all'},
11072                                         {inline : 'span', styles : {fontStyle : 'italic'}},
11073                                         {inline : 'i', remove : 'all'}
11074                                 ],
11075
11076                                 underline : [
11077                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
11078                                         {inline : 'u', remove : 'all'}
11079                                 ],
11080
11081                                 strikethrough : [
11082                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
11083                                         {inline : 'strike', remove : 'all'}
11084                                 ],
11085
11086                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
11087                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
11088                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
11089                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
11090                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
11091                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
11092                                 subscript : {inline : 'sub'},
11093                                 superscript : {inline : 'sup'},
11094
11095                                 removeformat : [
11096                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
11097                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
11098                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
11099                                 ]
11100                         });
11101
11102                         // Register default block formats
11103                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
11104                                 t.formatter.register(name, {block : name, remove : 'all'});
11105                         });
11106
11107                         // Register user defined formats
11108                         t.formatter.register(t.settings.formats);
11109
11110                         t.undoManager = new tinymce.UndoManager(t);
11111
11112                         // Pass through
11113                         t.undoManager.onAdd.add(function(um, l) {
11114                                 if (um.hasUndo())
11115                                         return t.onChange.dispatch(t, l, um);
11116                         });
11117
11118                         t.undoManager.onUndo.add(function(um, l) {
11119                                 return t.onUndo.dispatch(t, l, um);
11120                         });
11121
11122                         t.undoManager.onRedo.add(function(um, l) {
11123                                 return t.onRedo.dispatch(t, l, um);
11124                         });
11125
11126                         t.forceBlocks = new tinymce.ForceBlocks(t, {
11127                                 forced_root_block : s.forced_root_block
11128                         });
11129
11130                         t.editorCommands = new tinymce.EditorCommands(t);
11131
11132                         // Pass through
11133                         t.serializer.onPreProcess.add(function(se, o) {
11134                                 return t.onPreProcess.dispatch(t, o, se);
11135                         });
11136
11137                         t.serializer.onPostProcess.add(function(se, o) {
11138                                 return t.onPostProcess.dispatch(t, o, se);
11139                         });
11140
11141                         t.onPreInit.dispatch(t);
11142
11143                         if (!s.gecko_spellcheck)
11144                                 t.getBody().spellcheck = 0;
11145
11146                         if (!s.readonly)
11147                                 t._addEvents();
11148
11149                         t.controlManager.onPostRender.dispatch(t, t.controlManager);
11150                         t.onPostRender.dispatch(t);
11151
11152                         if (s.directionality)
11153                                 t.getBody().dir = s.directionality;
11154
11155                         if (s.nowrap)
11156                                 t.getBody().style.whiteSpace = "nowrap";
11157
11158                         if (s.handle_node_change_callback) {
11159                                 t.onNodeChange.add(function(ed, cm, n) {
11160                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
11161                                 });
11162                         }
11163
11164                         if (s.save_callback) {
11165                                 t.onSaveContent.add(function(ed, o) {
11166                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
11167
11168                                         if (h)
11169                                                 o.content = h;
11170                                 });
11171                         }
11172
11173                         if (s.onchange_callback) {
11174                                 t.onChange.add(function(ed, l) {
11175                                         t.execCallback('onchange_callback', t, l);
11176                                 });
11177                         }
11178
11179                         if (s.protect) {
11180                                 t.onBeforeSetContent.add(function(ed, o) {
11181                                         if (s.protect) {
11182                                                 each(s.protect, function(pattern) {
11183                                                         o.content = o.content.replace(pattern, function(str) {
11184                                                                 return '<!--mce:protected ' + escape(str) + '-->';
11185                                                         });
11186                                                 });
11187                                         }
11188                                 });
11189                         }
11190
11191                         if (s.convert_newlines_to_brs) {
11192                                 t.onBeforeSetContent.add(function(ed, o) {
11193                                         if (o.initial)
11194                                                 o.content = o.content.replace(/\r?\n/g, '<br />');
11195                                 });
11196                         }
11197
11198                         if (s.preformatted) {
11199                                 t.onPostProcess.add(function(ed, o) {
11200                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');
11201                                         o.content = o.content.replace(/<\/pre>\s*$/, '');
11202
11203                                         if (o.set)
11204                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
11205                                 });
11206                         }
11207
11208                         if (s.verify_css_classes) {
11209                                 t.serializer.attribValueFilter = function(n, v) {
11210                                         var s, cl;
11211
11212                                         if (n == 'class') {
11213                                                 // Build regexp for classes
11214                                                 if (!t.classesRE) {
11215                                                         cl = t.dom.getClasses();
11216
11217                                                         if (cl.length > 0) {
11218                                                                 s = '';
11219
11220                                                                 each (cl, function(o) {
11221                                                                         s += (s ? '|' : '') + o['class'];
11222                                                                 });
11223
11224                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');
11225                                                         }
11226                                                 }
11227
11228                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
11229                                         }
11230
11231                                         return v;
11232                                 };
11233                         }
11234
11235                         if (s.cleanup_callback) {
11236                                 t.onBeforeSetContent.add(function(ed, o) {
11237                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11238                                 });
11239
11240                                 t.onPreProcess.add(function(ed, o) {
11241                                         if (o.set)
11242                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
11243
11244                                         if (o.get)
11245                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
11246                                 });
11247
11248                                 t.onPostProcess.add(function(ed, o) {
11249                                         if (o.set)
11250                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11251
11252                                         if (o.get)                                              
11253                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
11254                                 });
11255                         }
11256
11257                         if (s.save_callback) {
11258                                 t.onGetContent.add(function(ed, o) {
11259                                         if (o.save)
11260                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
11261                                 });
11262                         }
11263
11264                         if (s.handle_event_callback) {
11265                                 t.onEvent.add(function(ed, e, o) {
11266                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)
11267                                                 Event.cancel(e);
11268                                 });
11269                         }
11270
11271                         // Add visual aids when new contents is added
11272                         t.onSetContent.add(function() {
11273                                 t.addVisual(t.getBody());
11274                         });
11275
11276                         // Remove empty contents
11277                         if (s.padd_empty_editor) {
11278                                 t.onPostProcess.add(function(ed, o) {
11279                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
11280                                 });
11281                         }
11282
11283                         if (isGecko) {
11284                                 // Fix gecko link bug, when a link is placed at the end of block elements there is
11285                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link
11286                                 function fixLinks(ed, o) {
11287                                         each(ed.dom.select('a'), function(n) {
11288                                                 var pn = n.parentNode;
11289
11290                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)
11291                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
11292                                         });
11293                                 };
11294
11295                                 t.onExecCommand.add(function(ed, cmd) {
11296                                         if (cmd === 'CreateLink')
11297                                                 fixLinks(ed);
11298                                 });
11299
11300                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
11301
11302                                 if (!s.readonly) {
11303                                         try {
11304                                                 // Design mode must be set here once again to fix a bug where
11305                                                 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
11306                                                 d.designMode = 'Off';
11307                                                 d.designMode = 'On';
11308                                         } catch (ex) {
11309                                                 // Will fail on Gecko if the editor is placed in an hidden container element
11310                                                 // The design mode will be set ones the editor is focused
11311                                         }
11312                                 }
11313                         }
11314
11315                         // A small timeout was needed since firefox will remove. Bug: #1838304
11316                         setTimeout(function () {
11317                                 if (t.removed)
11318                                         return;
11319
11320                                 t.load({initial : true, format : 'html'});
11321                                 t.startContent = t.getContent({format : 'raw'});
11322                                 t.undoManager.add();
11323                                 t.initialized = true;
11324
11325                                 t.onInit.dispatch(t);
11326                                 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
11327                                 t.execCallback('init_instance_callback', t);
11328                                 t.focus(true);
11329                                 t.nodeChanged({initial : 1});
11330
11331                                 // Load specified content CSS last
11332                                 each(t.contentCSS, function(u) {
11333                                         t.dom.loadCSS(u);
11334                                 });
11335
11336                                 // Handle auto focus
11337                                 if (s.auto_focus) {
11338                                         setTimeout(function () {
11339                                                 var ed = tinymce.get(s.auto_focus);
11340
11341                                                 ed.selection.select(ed.getBody(), 1);
11342                                                 ed.selection.collapse(1);
11343                                                 ed.getWin().focus();
11344                                         }, 100);
11345                                 }
11346                         }, 1);
11347         
11348                         e = null;
11349                 },
11350
11351
11352                 focus : function(sf) {
11353                         var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
11354
11355                         if (!sf) {
11356                                 // Get selected control element
11357                                 ieRng = t.selection.getRng();
11358                                 if (ieRng.item) {
11359                                         controlElm = ieRng.item(0);
11360                                 }
11361
11362                                 // Is not content editable
11363                                 if (!ce)
11364                                         t.getWin().focus();
11365
11366                                 // Restore selected control element
11367                                 // This is needed when for example an image is selected within a
11368                                 // layer a call to focus will then remove the control selection
11369                                 if (controlElm && controlElm.ownerDocument == doc) {
11370                                         ieRng = doc.body.createControlRange();
11371                                         ieRng.addElement(controlElm);
11372                                         ieRng.select();
11373                                 }
11374
11375                         }
11376
11377                         if (tinymce.activeEditor != t) {
11378                                 if ((oed = tinymce.activeEditor) != null)
11379                                         oed.onDeactivate.dispatch(oed, t);
11380
11381                                 t.onActivate.dispatch(t, oed);
11382                         }
11383
11384                         tinymce._setActive(t);
11385                 },
11386
11387                 execCallback : function(n) {
11388                         var t = this, f = t.settings[n], s;
11389
11390                         if (!f)
11391                                 return;
11392
11393                         // Look through lookup
11394                         if (t.callbackLookup && (s = t.callbackLookup[n])) {
11395                                 f = s.func;
11396                                 s = s.scope;
11397                         }
11398
11399                         if (is(f, 'string')) {
11400                                 s = f.replace(/\.\w+$/, '');
11401                                 s = s ? tinymce.resolve(s) : 0;
11402                                 f = tinymce.resolve(f);
11403                                 t.callbackLookup = t.callbackLookup || {};
11404                                 t.callbackLookup[n] = {func : f, scope : s};
11405                         }
11406
11407                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
11408                 },
11409
11410                 translate : function(s) {
11411                         var c = this.settings.language || 'en', i18n = tinymce.i18n;
11412
11413                         if (!s)
11414                                 return '';
11415
11416                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
11417                                 return i18n[c + '.' + b] || '{#' + b + '}';
11418                         });
11419                 },
11420
11421                 getLang : function(n, dv) {
11422                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
11423                 },
11424
11425                 getParam : function(n, dv, ty) {
11426                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
11427
11428                         if (ty === 'hash') {
11429                                 o = {};
11430
11431                                 if (is(v, 'string')) {
11432                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
11433                                                 v = v.split('=');
11434
11435                                                 if (v.length > 1)
11436                                                         o[tr(v[0])] = tr(v[1]);
11437                                                 else
11438                                                         o[tr(v[0])] = tr(v);
11439                                         });
11440                                 } else
11441                                         o = v;
11442
11443                                 return o;
11444                         }
11445
11446                         return v;
11447                 },
11448
11449                 nodeChanged : function(o) {
11450                         var t = this, s = t.selection, n = s.getStart() || t.getBody();
11451
11452                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
11453                         if (t.initialized) {
11454                                 o = o || {};
11455                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
11456
11457                                 // Get parents and add them to object
11458                                 o.parents = [];
11459                                 t.dom.getParent(n, function(node) {
11460                                         if (node.nodeName == 'BODY')
11461                                                 return true;
11462
11463                                         o.parents.push(node);
11464                                 });
11465
11466                                 t.onNodeChange.dispatch(
11467                                         t,
11468                                         o ? o.controlManager || t.controlManager : t.controlManager,
11469                                         n,
11470                                         s.isCollapsed(),
11471                                         o
11472                                 );
11473                         }
11474                 },
11475
11476                 addButton : function(n, s) {
11477                         var t = this;
11478
11479                         t.buttons = t.buttons || {};
11480                         t.buttons[n] = s;
11481                 },
11482
11483                 addCommand : function(name, callback, scope) {
11484                         this.execCommands[name] = {func : callback, scope : scope || this};
11485                 },
11486
11487                 addQueryStateHandler : function(name, callback, scope) {
11488                         this.queryStateCommands[name] = {func : callback, scope : scope || this};
11489                 },
11490
11491                 addQueryValueHandler : function(name, callback, scope) {
11492                         this.queryValueCommands[name] = {func : callback, scope : scope || this};
11493                 },
11494
11495                 addShortcut : function(pa, desc, cmd_func, sc) {
11496                         var t = this, c;
11497
11498                         if (!t.settings.custom_shortcuts)
11499                                 return false;
11500
11501                         t.shortcuts = t.shortcuts || {};
11502
11503                         if (is(cmd_func, 'string')) {
11504                                 c = cmd_func;
11505
11506                                 cmd_func = function() {
11507                                         t.execCommand(c, false, null);
11508                                 };
11509                         }
11510
11511                         if (is(cmd_func, 'object')) {
11512                                 c = cmd_func;
11513
11514                                 cmd_func = function() {
11515                                         t.execCommand(c[0], c[1], c[2]);
11516                                 };
11517                         }
11518
11519                         each(explode(pa), function(pa) {
11520                                 var o = {
11521                                         func : cmd_func,
11522                                         scope : sc || this,
11523                                         desc : desc,
11524                                         alt : false,
11525                                         ctrl : false,
11526                                         shift : false
11527                                 };
11528
11529                                 each(explode(pa, '+'), function(v) {
11530                                         switch (v) {
11531                                                 case 'alt':
11532                                                 case 'ctrl':
11533                                                 case 'shift':
11534                                                         o[v] = true;
11535                                                         break;
11536
11537                                                 default:
11538                                                         o.charCode = v.charCodeAt(0);
11539                                                         o.keyCode = v.toUpperCase().charCodeAt(0);
11540                                         }
11541                                 });
11542
11543                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
11544                         });
11545
11546                         return true;
11547                 },
11548
11549                 execCommand : function(cmd, ui, val, a) {
11550                         var t = this, s = 0, o, st;
11551
11552                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
11553                                 t.focus();
11554
11555                         o = {};
11556                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
11557                         if (o.terminate)
11558                                 return false;
11559
11560                         // Command callback
11561                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
11562                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11563                                 return true;
11564                         }
11565
11566                         // Registred commands
11567                         if (o = t.execCommands[cmd]) {
11568                                 st = o.func.call(o.scope, ui, val);
11569
11570                                 // Fall through on true
11571                                 if (st !== true) {
11572                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11573                                         return st;
11574                                 }
11575                         }
11576
11577                         // Plugin commands
11578                         each(t.plugins, function(p) {
11579                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {
11580                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11581                                         s = 1;
11582                                         return false;
11583                                 }
11584                         });
11585
11586                         if (s)
11587                                 return true;
11588
11589                         // Theme commands
11590                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
11591                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11592                                 return true;
11593                         }
11594
11595                         // Editor commands
11596                         if (t.editorCommands.execCommand(cmd, ui, val)) {
11597                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11598                                 return true;
11599                         }
11600
11601                         // Browser commands
11602                         t.getDoc().execCommand(cmd, ui, val);
11603                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11604                 },
11605
11606                 queryCommandState : function(cmd) {
11607                         var t = this, o, s;
11608
11609                         // Is hidden then return undefined
11610                         if (t._isHidden())
11611                                 return;
11612
11613                         // Registred commands
11614                         if (o = t.queryStateCommands[cmd]) {
11615                                 s = o.func.call(o.scope);
11616
11617                                 // Fall though on true
11618                                 if (s !== true)
11619                                         return s;
11620                         }
11621
11622                         // Registred commands
11623                         o = t.editorCommands.queryCommandState(cmd);
11624                         if (o !== -1)
11625                                 return o;
11626
11627                         // Browser commands
11628                         try {
11629                                 return this.getDoc().queryCommandState(cmd);
11630                         } catch (ex) {
11631                                 // Fails sometimes see bug: 1896577
11632                         }
11633                 },
11634
11635                 queryCommandValue : function(c) {
11636                         var t = this, o, s;
11637
11638                         // Is hidden then return undefined
11639                         if (t._isHidden())
11640                                 return;
11641
11642                         // Registred commands
11643                         if (o = t.queryValueCommands[c]) {
11644                                 s = o.func.call(o.scope);
11645
11646                                 // Fall though on true
11647                                 if (s !== true)
11648                                         return s;
11649                         }
11650
11651                         // Registred commands
11652                         o = t.editorCommands.queryCommandValue(c);
11653                         if (is(o))
11654                                 return o;
11655
11656                         // Browser commands
11657                         try {
11658                                 return this.getDoc().queryCommandValue(c);
11659                         } catch (ex) {
11660                                 // Fails sometimes see bug: 1896577
11661                         }
11662                 },
11663
11664                 show : function() {
11665                         var t = this;
11666
11667                         DOM.show(t.getContainer());
11668                         DOM.hide(t.id);
11669                         t.load();
11670                 },
11671
11672                 hide : function() {
11673                         var t = this, d = t.getDoc();
11674
11675                         // Fixed bug where IE has a blinking cursor left from the editor
11676                         if (isIE && d)
11677                                 d.execCommand('SelectAll');
11678
11679                         // We must save before we hide so Safari doesn't crash
11680                         t.save();
11681                         DOM.hide(t.getContainer());
11682                         DOM.setStyle(t.id, 'display', t.orgDisplay);
11683                 },
11684
11685                 isHidden : function() {
11686                         return !DOM.isHidden(this.id);
11687                 },
11688
11689                 setProgressState : function(b, ti, o) {
11690                         this.onSetProgressState.dispatch(this, b, ti, o);
11691
11692                         return b;
11693                 },
11694
11695                 load : function(o) {
11696                         var t = this, e = t.getElement(), h;
11697
11698                         if (e) {
11699                                 o = o || {};
11700                                 o.load = true;
11701
11702                                 // Double encode existing entities in the value
11703                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
11704                                 o.element = e;
11705
11706                                 if (!o.no_events)
11707                                         t.onLoadContent.dispatch(t, o);
11708
11709                                 o.element = e = null;
11710
11711                                 return h;
11712                         }
11713                 },
11714
11715                 save : function(o) {
11716                         var t = this, e = t.getElement(), h, f;
11717
11718                         if (!e || !t.initialized)
11719                                 return;
11720
11721                         o = o || {};
11722                         o.save = true;
11723
11724                         // Add undo level will trigger onchange event
11725                         if (!o.no_events) {
11726                                 t.undoManager.typing = false;
11727                                 t.undoManager.add();
11728                         }
11729
11730                         o.element = e;
11731                         h = o.content = t.getContent(o);
11732
11733                         if (!o.no_events)
11734                                 t.onSaveContent.dispatch(t, o);
11735
11736                         h = o.content;
11737
11738                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
11739                                 e.innerHTML = h;
11740
11741                                 // Update hidden form element
11742                                 if (f = DOM.getParent(t.id, 'form')) {
11743                                         each(f.elements, function(e) {
11744                                                 if (e.name == t.id) {
11745                                                         e.value = h;
11746                                                         return false;
11747                                                 }
11748                                         });
11749                                 }
11750                         } else
11751                                 e.value = h;
11752
11753                         o.element = e = null;
11754
11755                         return h;
11756                 },
11757
11758                 setContent : function(content, args) {
11759                         var self = this, rootNode, body = self.getBody();
11760
11761                         // Setup args object
11762                         args = args || {};
11763                         args.format = args.format || 'html';
11764                         args.set = true;
11765                         args.content = content;
11766
11767                         // Do preprocessing
11768                         if (!args.no_events)
11769                                 self.onBeforeSetContent.dispatch(self, args);
11770
11771                         content = args.content;
11772
11773                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
11774                         // It will also be impossible to place the caret in the editor unless there is a BR element present
11775                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
11776                                 body.innerHTML = '<br data-mce-bogus="1" />';
11777                                 return;
11778                         }
11779
11780                         // Parse and serialize the html
11781                         if (args.format !== 'raw') {
11782                                 content = new tinymce.html.Serializer({}, self.schema).serialize(
11783                                         self.parser.parse(content)
11784                                 );
11785                         }
11786
11787                         // Set the new cleaned contents to the editor
11788                         args.content = tinymce.trim(content);
11789                         self.dom.setHTML(body, args.content);
11790
11791                         // Do post processing
11792                         if (!args.no_events)
11793                                 self.onSetContent.dispatch(self, args);
11794
11795                         return args.content;
11796                 },
11797
11798                 getContent : function(args) {
11799                         var self = this, content;
11800
11801                         // Setup args object
11802                         args = args || {};
11803                         args.format = args.format || 'html';
11804                         args.get = true;
11805
11806                         // Do preprocessing
11807                         if (!args.no_events)
11808                                 self.onBeforeGetContent.dispatch(self, args);
11809
11810                         // Get raw contents or by default the cleaned contents
11811                         if (args.format == 'raw')
11812                                 content = self.getBody().innerHTML;
11813                         else
11814                                 content = self.serializer.serialize(self.getBody(), args);
11815
11816                         args.content = tinymce.trim(content);
11817
11818                         // Do post processing
11819                         if (!args.no_events)
11820                                 self.onGetContent.dispatch(self, args);
11821
11822                         return args.content;
11823                 },
11824
11825                 isDirty : function() {
11826                         var self = this;
11827
11828                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
11829                 },
11830
11831                 getContainer : function() {
11832                         var t = this;
11833
11834                         if (!t.container)
11835                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');
11836
11837                         return t.container;
11838                 },
11839
11840                 getContentAreaContainer : function() {
11841                         return this.contentAreaContainer;
11842                 },
11843
11844                 getElement : function() {
11845                         return DOM.get(this.settings.content_element || this.id);
11846                 },
11847
11848                 getWin : function() {
11849                         var t = this, e;
11850
11851                         if (!t.contentWindow) {
11852                                 e = DOM.get(t.id + "_ifr");
11853
11854                                 if (e)
11855                                         t.contentWindow = e.contentWindow;
11856                         }
11857
11858                         return t.contentWindow;
11859                 },
11860
11861                 getDoc : function() {
11862                         var t = this, w;
11863
11864                         if (!t.contentDocument) {
11865                                 w = t.getWin();
11866
11867                                 if (w)
11868                                         t.contentDocument = w.document;
11869                         }
11870
11871                         return t.contentDocument;
11872                 },
11873
11874                 getBody : function() {
11875                         return this.bodyElement || this.getDoc().body;
11876                 },
11877
11878                 convertURL : function(u, n, e) {
11879                         var t = this, s = t.settings;
11880
11881                         // Use callback instead
11882                         if (s.urlconverter_callback)
11883                                 return t.execCallback('urlconverter_callback', u, e, true, n);
11884
11885                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
11886                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
11887                                 return u;
11888
11889                         // Convert to relative
11890                         if (s.relative_urls)
11891                                 return t.documentBaseURI.toRelative(u);
11892
11893                         // Convert to absolute
11894                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
11895
11896                         return u;
11897                 },
11898
11899                 addVisual : function(e) {
11900                         var t = this, s = t.settings;
11901
11902                         e = e || t.getBody();
11903
11904                         if (!is(t.hasVisual))
11905                                 t.hasVisual = s.visual;
11906
11907                         each(t.dom.select('table,a', e), function(e) {
11908                                 var v;
11909
11910                                 switch (e.nodeName) {
11911                                         case 'TABLE':
11912                                                 v = t.dom.getAttrib(e, 'border');
11913
11914                                                 if (!v || v == '0') {
11915                                                         if (t.hasVisual)
11916                                                                 t.dom.addClass(e, s.visual_table_class);
11917                                                         else
11918                                                                 t.dom.removeClass(e, s.visual_table_class);
11919                                                 }
11920
11921                                                 return;
11922
11923                                         case 'A':
11924                                                 v = t.dom.getAttrib(e, 'name');
11925
11926                                                 if (v) {
11927                                                         if (t.hasVisual)
11928                                                                 t.dom.addClass(e, 'mceItemAnchor');
11929                                                         else
11930                                                                 t.dom.removeClass(e, 'mceItemAnchor');
11931                                                 }
11932
11933                                                 return;
11934                                 }
11935                         });
11936
11937                         t.onVisualAid.dispatch(t, e, t.hasVisual);
11938                 },
11939
11940                 remove : function() {
11941                         var t = this, e = t.getContainer();
11942
11943                         t.removed = 1; // Cancels post remove event execution
11944                         t.hide();
11945
11946                         t.execCallback('remove_instance_callback', t);
11947                         t.onRemove.dispatch(t);
11948
11949                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
11950                         t.onExecCommand.listeners = [];
11951
11952                         tinymce.remove(t);
11953                         DOM.remove(e);
11954                 },
11955
11956                 destroy : function(s) {
11957                         var t = this;
11958
11959                         // One time is enough
11960                         if (t.destroyed)
11961                                 return;
11962
11963                         if (!s) {
11964                                 tinymce.removeUnload(t.destroy);
11965                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
11966
11967                                 // Manual destroy
11968                                 if (t.theme && t.theme.destroy)
11969                                         t.theme.destroy();
11970
11971                                 // Destroy controls, selection and dom
11972                                 t.controlManager.destroy();
11973                                 t.selection.destroy();
11974                                 t.dom.destroy();
11975
11976                                 // Remove all events
11977
11978                                 // Don't clear the window or document if content editable
11979                                 // is enabled since other instances might still be present
11980                                 if (!t.settings.content_editable) {
11981                                         Event.clear(t.getWin());
11982                                         Event.clear(t.getDoc());
11983                                 }
11984
11985                                 Event.clear(t.getBody());
11986                                 Event.clear(t.formElement);
11987                         }
11988
11989                         if (t.formElement) {
11990                                 t.formElement.submit = t.formElement._mceOldSubmit;
11991                                 t.formElement._mceOldSubmit = null;
11992                         }
11993
11994                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
11995
11996                         if (t.selection)
11997                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
11998
11999                         t.destroyed = 1;
12000                 },
12001
12002                 // Internal functions
12003
12004                 _addEvents : function() {
12005                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
12006                         var t = this, i, s = t.settings, dom = t.dom, lo = {
12007                                 mouseup : 'onMouseUp',
12008                                 mousedown : 'onMouseDown',
12009                                 click : 'onClick',
12010                                 keyup : 'onKeyUp',
12011                                 keydown : 'onKeyDown',
12012                                 keypress : 'onKeyPress',
12013                                 submit : 'onSubmit',
12014                                 reset : 'onReset',
12015                                 contextmenu : 'onContextMenu',
12016                                 dblclick : 'onDblClick',
12017                                 paste : 'onPaste' // Doesn't work in all browsers yet
12018                         };
12019
12020                         function eventHandler(e, o) {
12021                                 var ty = e.type;
12022
12023                                 // Don't fire events when it's removed
12024                                 if (t.removed)
12025                                         return;
12026
12027                                 // Generic event handler
12028                                 if (t.onEvent.dispatch(t, e, o) !== false) {
12029                                         // Specific event handler
12030                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);
12031                                 }
12032                         };
12033
12034                         // Add DOM events
12035                         each(lo, function(v, k) {
12036                                 switch (k) {
12037                                         case 'contextmenu':
12038                                                 dom.bind(t.getDoc(), k, eventHandler);
12039                                                 break;
12040
12041                                         case 'paste':
12042                                                 dom.bind(t.getBody(), k, function(e) {
12043                                                         eventHandler(e);
12044                                                 });
12045                                                 break;
12046
12047                                         case 'submit':
12048                                         case 'reset':
12049                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
12050                                                 break;
12051
12052                                         default:
12053                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
12054                                 }
12055                         });
12056
12057                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
12058                                 t.focus(true);
12059                         });
12060
12061
12062                         // Fixes bug where a specified document_base_uri could result in broken images
12063                         // This will also fix drag drop of images in Gecko
12064                         if (tinymce.isGecko) {
12065                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
12066                                         var v;
12067
12068                                         e = e.target;
12069
12070                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
12071                                                 e.src = t.documentBaseURI.toAbsolute(v);
12072                                 });
12073                         }
12074
12075                         // Set various midas options in Gecko
12076                         if (isGecko) {
12077                                 function setOpts() {
12078                                         var t = this, d = t.getDoc(), s = t.settings;
12079
12080                                         if (isGecko && !s.readonly) {
12081                                                 if (t._isHidden()) {
12082                                                         try {
12083                                                                 if (!s.content_editable)
12084                                                                         d.designMode = 'On';
12085                                                         } catch (ex) {
12086                                                                 // Fails if it's hidden
12087                                                         }
12088                                                 }
12089
12090                                                 try {
12091                                                         // Try new Gecko method
12092                                                         d.execCommand("styleWithCSS", 0, false);
12093                                                 } catch (ex) {
12094                                                         // Use old method
12095                                                         if (!t._isHidden())
12096                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
12097                                                 }
12098
12099                                                 if (!s.table_inline_editing)
12100                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
12101
12102                                                 if (!s.object_resizing)
12103                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
12104                                         }
12105                                 };
12106
12107                                 t.onBeforeExecCommand.add(setOpts);
12108                                 t.onMouseDown.add(setOpts);
12109                         }
12110
12111                         // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
12112                         // WebKit can't even do simple things like selecting an image
12113                         // This also fixes so it's possible to select mceItemAnchors
12114                         if (tinymce.isWebKit) {
12115                                 t.onClick.add(function(ed, e) {
12116                                         e = e.target;
12117
12118                                         // Needs tobe the setBaseAndExtend or it will fail to select floated images
12119                                         if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
12120                                                 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
12121                                                 t.nodeChanged();
12122                                         }
12123                                 });
12124                         }
12125
12126                         // Add node change handlers
12127                         t.onMouseUp.add(t.nodeChanged);
12128                         //t.onClick.add(t.nodeChanged);
12129                         t.onKeyUp.add(function(ed, e) {
12130                                 var c = e.keyCode;
12131
12132                                 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)
12133                                         t.nodeChanged();
12134                         });
12135
12136                         // Add reset handler
12137                         t.onReset.add(function() {
12138                                 t.setContent(t.startContent, {format : 'raw'});
12139                         });
12140
12141                         // Add shortcuts
12142                         if (s.custom_shortcuts) {
12143                                 if (s.custom_undo_redo_keyboard_shortcuts) {
12144                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
12145                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
12146                                 }
12147
12148                                 // Add default shortcuts for gecko
12149                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
12150                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
12151                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
12152
12153                                 // BlockFormat shortcuts keys
12154                                 for (i=1; i<=6; i++)
12155                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
12156
12157                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
12158                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
12159                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
12160
12161                                 function find(e) {
12162                                         var v = null;
12163
12164                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)
12165                                                 return v;
12166
12167                                         each(t.shortcuts, function(o) {
12168                                                 if (tinymce.isMac && o.ctrl != e.metaKey)
12169                                                         return;
12170                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
12171                                                         return;
12172
12173                                                 if (o.alt != e.altKey)
12174                                                         return;
12175
12176                                                 if (o.shift != e.shiftKey)
12177                                                         return;
12178
12179                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
12180                                                         v = o;
12181                                                         return false;
12182                                                 }
12183                                         });
12184
12185                                         return v;
12186                                 };
12187
12188                                 t.onKeyUp.add(function(ed, e) {
12189                                         var o = find(e);
12190
12191                                         if (o)
12192                                                 return Event.cancel(e);
12193                                 });
12194
12195                                 t.onKeyPress.add(function(ed, e) {
12196                                         var o = find(e);
12197
12198                                         if (o)
12199                                                 return Event.cancel(e);
12200                                 });
12201
12202                                 t.onKeyDown.add(function(ed, e) {
12203                                         var o = find(e);
12204
12205                                         if (o) {
12206                                                 o.func.call(o.scope);
12207                                                 return Event.cancel(e);
12208                                         }
12209                                 });
12210                         }
12211
12212                         if (tinymce.isIE) {
12213                                 // Fix so resize will only update the width and height attributes not the styles of an image
12214                                 // It will also block mceItemNoResize items
12215                                 dom.bind(t.getDoc(), 'controlselect', function(e) {
12216                                         var re = t.resizeInfo, cb;
12217
12218                                         e = e.target;
12219
12220                                         // Don't do this action for non image elements
12221                                         if (e.nodeName !== 'IMG')
12222                                                 return;
12223
12224                                         if (re)
12225                                                 dom.unbind(re.node, re.ev, re.cb);
12226
12227                                         if (!dom.hasClass(e, 'mceItemNoResize')) {
12228                                                 ev = 'resizeend';
12229                                                 cb = dom.bind(e, ev, function(e) {
12230                                                         var v;
12231
12232                                                         e = e.target;
12233
12234                                                         if (v = dom.getStyle(e, 'width')) {
12235                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
12236                                                                 dom.setStyle(e, 'width', '');
12237                                                         }
12238
12239                                                         if (v = dom.getStyle(e, 'height')) {
12240                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
12241                                                                 dom.setStyle(e, 'height', '');
12242                                                         }
12243                                                 });
12244                                         } else {
12245                                                 ev = 'resizestart';
12246                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
12247                                         }
12248
12249                                         re = t.resizeInfo = {
12250                                                 node : e,
12251                                                 ev : ev,
12252                                                 cb : cb
12253                                         };
12254                                 });
12255
12256                                 t.onKeyDown.add(function(ed, e) {
12257                                         var sel;
12258
12259                                         switch (e.keyCode) {
12260                                                 case 8:
12261                                                         sel = t.getDoc().selection;
12262
12263                                                         // Fix IE control + backspace browser bug
12264                                                         if (sel.createRange && sel.createRange().item) {
12265                                                                 ed.dom.remove(sel.createRange().item(0));
12266                                                                 return Event.cancel(e);
12267                                                         }
12268                                         }
12269                                 });
12270                         }
12271
12272                         if (tinymce.isOpera) {
12273                                 t.onClick.add(function(ed, e) {
12274                                         Event.prevent(e);
12275                                 });
12276                         }
12277
12278                         // Add custom undo/redo handlers
12279                         if (s.custom_undo_redo) {
12280                                 function addUndo() {
12281                                         t.undoManager.typing = false;
12282                                         t.undoManager.add();
12283                                 };
12284
12285                                 dom.bind(t.getDoc(), 'focusout', function(e) {
12286                                         if (!t.removed && t.undoManager.typing)
12287                                                 addUndo();
12288                                 });
12289
12290                                 // Add undo level when contents is drag/dropped within the editor
12291                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
12292                                         addUndo();
12293                                 });
12294
12295                                 t.onKeyUp.add(function(ed, e) {
12296                                         var rng, parent, bookmark;
12297
12298                                         // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node.
12299                                         if (isIE && e.keyCode == 8) {
12300                                                 rng = t.selection.getRng();
12301                                                 if (rng.parentElement) {
12302                                                         parent = rng.parentElement();
12303                                                         bookmark = t.selection.getBookmark();
12304                                                         parent.innerHTML = parent.innerHTML;
12305                                                         t.selection.moveToBookmark(bookmark);
12306                                                 }
12307                                         }
12308
12309                                         if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
12310                                                 addUndo();
12311                                 });
12312
12313                                 t.onKeyDown.add(function(ed, e) {
12314                                         var rng, parent, bookmark, keyCode = e.keyCode;
12315
12316                                         // IE has a really odd bug where the DOM might include an node that doesn't have
12317                                         // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
12318                                         // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
12319                                         // after you delete contents from it. See: #3008923
12320                                         if (isIE && keyCode == 46) {
12321                                                 rng = t.selection.getRng();
12322
12323                                                 if (rng.parentElement) {
12324                                                         parent = rng.parentElement();
12325
12326                                                         if (!t.undoManager.typing) {
12327                                                                 t.undoManager.beforeChange();
12328                                                                 t.undoManager.typing = true;
12329                                                                 t.undoManager.add();
12330                                                         }
12331
12332                                                         // Select next word when ctrl key is used in combo with delete
12333                                                         if (e.ctrlKey) {
12334                                                                 rng.moveEnd('word', 1);
12335                                                                 rng.select();
12336                                                         }
12337
12338                                                         // Delete contents
12339                                                         t.selection.getSel().clear();
12340
12341                                                         // Check if we are within the same parent
12342                                                         if (rng.parentElement() == parent) {
12343                                                                 bookmark = t.selection.getBookmark();
12344
12345                                                                 try {
12346                                                                         // Update the HTML and hopefully it will remove the artifacts
12347                                                                         parent.innerHTML = parent.innerHTML;
12348                                                                 } catch (ex) {
12349                                                                         // And since it's IE it can sometimes produce an unknown runtime error
12350                                                                 }
12351
12352                                                                 // Restore the caret position
12353                                                                 t.selection.moveToBookmark(bookmark);
12354                                                         }
12355
12356                                                         // Block the default delete behavior since it might be broken
12357                                                         e.preventDefault();
12358                                                         return;
12359                                                 }
12360                                         }
12361
12362                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
12363                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
12364                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
12365                                                 // Todo: Remove this once we normalize enter behavior on IE
12366                                                 if (tinymce.isIE && keyCode == 13)
12367                                                         t.undoManager.beforeChange();
12368
12369                                                 if (t.undoManager.typing)
12370                                                         addUndo();
12371
12372                                                 return;
12373                                         }
12374
12375                                         // If key isn't shift,ctrl,alt,capslock,metakey
12376                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
12377                                                 t.undoManager.beforeChange();
12378                                                 t.undoManager.add();
12379                                                 t.undoManager.typing = true;
12380                                         }
12381                                 });
12382
12383                                 t.onMouseDown.add(function() {
12384                                         if (t.undoManager.typing)
12385                                                 addUndo();
12386                                 });
12387                         }
12388                         
12389                         // Bug fix for FireFox keeping styles from end of selection instead of start.
12390                         if (tinymce.isGecko) {
12391                                 function getAttributeApplyFunction() {
12392                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
12393
12394                                         return function() {
12395                                                 var target = t.selection.getStart();
12396                                                 t.dom.removeAllAttribs(target);
12397                                                 each(template, function(attr) {
12398                                                         target.setAttributeNode(attr.cloneNode(true));
12399                                                 });
12400                                         };
12401                                 }
12402
12403                                 function isSelectionAcrossElements() {
12404                                         var s = t.selection;
12405
12406                                         return !s.isCollapsed() && s.getStart() != s.getEnd();
12407                                 }
12408
12409                                 t.onKeyPress.add(function(ed, e) {
12410                                         var applyAttributes;
12411
12412                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
12413                                                 applyAttributes = getAttributeApplyFunction();
12414                                                 t.getDoc().execCommand('delete', false, null);
12415                                                 applyAttributes();
12416
12417                                                 return Event.cancel(e);
12418                                         }
12419                                 });
12420
12421                                 t.dom.bind(t.getDoc(), 'cut', function(e) {
12422                                         var applyAttributes;
12423
12424                                         if (isSelectionAcrossElements()) {
12425                                                 applyAttributes = getAttributeApplyFunction();
12426                                                 t.onKeyUp.addToTop(Event.cancel, Event);
12427
12428                                                 setTimeout(function() {
12429                                                         applyAttributes();
12430                                                         t.onKeyUp.remove(Event.cancel, Event);
12431                                                 }, 0);
12432                                         }
12433                                 });
12434                         }
12435                 },
12436
12437                 _isHidden : function() {
12438                         var s;
12439
12440                         if (!isGecko)
12441                                 return 0;
12442
12443                         // Weird, wheres that cursor selection?
12444                         s = this.selection.getSel();
12445                         return (!s || !s.rangeCount || s.rangeCount == 0);
12446                 }
12447         });
12448 })(tinymce);
12449
12450 (function(tinymce) {
12451         // Added for compression purposes
12452         var each = tinymce.each, undefined, TRUE = true, FALSE = false;
12453
12454         tinymce.EditorCommands = function(editor) {
12455                 var dom = editor.dom,
12456                         selection = editor.selection,
12457                         commands = {state: {}, exec : {}, value : {}},
12458                         settings = editor.settings,
12459                         bookmark;
12460
12461                 function execCommand(command, ui, value) {
12462                         var func;
12463
12464                         command = command.toLowerCase();
12465                         if (func = commands.exec[command]) {
12466                                 func(command, ui, value);
12467                                 return TRUE;
12468                         }
12469
12470                         return FALSE;
12471                 };
12472
12473                 function queryCommandState(command) {
12474                         var func;
12475
12476                         command = command.toLowerCase();
12477                         if (func = commands.state[command])
12478                                 return func(command);
12479
12480                         return -1;
12481                 };
12482
12483                 function queryCommandValue(command) {
12484                         var func;
12485
12486                         command = command.toLowerCase();
12487                         if (func = commands.value[command])
12488                                 return func(command);
12489
12490                         return FALSE;
12491                 };
12492
12493                 function addCommands(command_list, type) {
12494                         type = type || 'exec';
12495
12496                         each(command_list, function(callback, command) {
12497                                 each(command.toLowerCase().split(','), function(command) {
12498                                         commands[type][command] = callback;
12499                                 });
12500                         });
12501                 };
12502
12503                 // Expose public methods
12504                 tinymce.extend(this, {
12505                         execCommand : execCommand,
12506                         queryCommandState : queryCommandState,
12507                         queryCommandValue : queryCommandValue,
12508                         addCommands : addCommands
12509                 });
12510
12511                 // Private methods
12512
12513                 function execNativeCommand(command, ui, value) {
12514                         if (ui === undefined)
12515                                 ui = FALSE;
12516
12517                         if (value === undefined)
12518                                 value = null;
12519
12520                         return editor.getDoc().execCommand(command, ui, value);
12521                 };
12522
12523                 function isFormatMatch(name) {
12524                         return editor.formatter.match(name);
12525                 };
12526
12527                 function toggleFormat(name, value) {
12528                         editor.formatter.toggle(name, value ? {value : value} : undefined);
12529                 };
12530
12531                 function storeSelection(type) {
12532                         bookmark = selection.getBookmark(type);
12533                 };
12534
12535                 function restoreSelection() {
12536                         selection.moveToBookmark(bookmark);
12537                 };
12538
12539                 // Add execCommand overrides
12540                 addCommands({
12541                         // Ignore these, added for compatibility
12542                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},
12543
12544                         // Add undo manager logic
12545                         'mceEndUndoLevel,mceAddUndoLevel' : function() {
12546                                 editor.undoManager.add();
12547                         },
12548
12549                         'Cut,Copy,Paste' : function(command) {
12550                                 var doc = editor.getDoc(), failed;
12551
12552                                 // Try executing the native command
12553                                 try {
12554                                         execNativeCommand(command);
12555                                 } catch (ex) {
12556                                         // Command failed
12557                                         failed = TRUE;
12558                                 }
12559
12560                                 // Present alert message about clipboard access not being available
12561                                 if (failed || !doc.queryCommandSupported(command)) {
12562                                         if (tinymce.isGecko) {
12563                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
12564                                                         if (state)
12565                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
12566                                                 });
12567                                         } else
12568                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
12569                                 }
12570                         },
12571
12572                         // Override unlink command
12573                         unlink : function(command) {
12574                                 if (selection.isCollapsed())
12575                                         selection.select(selection.getNode());
12576
12577                                 execNativeCommand(command);
12578                                 selection.collapse(FALSE);
12579                         },
12580
12581                         // Override justify commands to use the text formatter engine
12582                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12583                                 var align = command.substring(7);
12584
12585                                 // Remove all other alignments first
12586                                 each('left,center,right,full'.split(','), function(name) {
12587                                         if (align != name)
12588                                                 editor.formatter.remove('align' + name);
12589                                 });
12590
12591                                 toggleFormat('align' + align);
12592                                 execCommand('mceRepaint');
12593                         },
12594
12595                         // Override list commands to fix WebKit bug
12596                         'InsertUnorderedList,InsertOrderedList' : function(command) {
12597                                 var listElm, listParent;
12598
12599                                 execNativeCommand(command);
12600
12601                                 // WebKit produces lists within block elements so we need to split them
12602                                 // we will replace the native list creation logic to custom logic later on
12603                                 // TODO: Remove this when the list creation logic is removed
12604                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
12605                                 if (listElm) {
12606                                         listParent = listElm.parentNode;
12607
12608                                         // If list is within a text block then split that block
12609                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
12610                                                 storeSelection();
12611                                                 dom.split(listParent, listElm);
12612                                                 restoreSelection();
12613                                         }
12614                                 }
12615                         },
12616
12617                         // Override commands to use the text formatter engine
12618                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12619                                 toggleFormat(command);
12620                         },
12621
12622                         // Override commands to use the text formatter engine
12623                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
12624                                 toggleFormat(command, value);
12625                         },
12626
12627                         FontSize : function(command, ui, value) {
12628                                 var fontClasses, fontSizes;
12629
12630                                 // Convert font size 1-7 to styles
12631                                 if (value >= 1 && value <= 7) {
12632                                         fontSizes = tinymce.explode(settings.font_size_style_values);
12633                                         fontClasses = tinymce.explode(settings.font_size_classes);
12634
12635                                         if (fontClasses)
12636                                                 value = fontClasses[value - 1] || value;
12637                                         else
12638                                                 value = fontSizes[value - 1] || value;
12639                                 }
12640
12641                                 toggleFormat(command, value);
12642                         },
12643
12644                         RemoveFormat : function(command) {
12645                                 editor.formatter.remove(command);
12646                         },
12647
12648                         mceBlockQuote : function(command) {
12649                                 toggleFormat('blockquote');
12650                         },
12651
12652                         FormatBlock : function(command, ui, value) {
12653                                 return toggleFormat(value || 'p');
12654                         },
12655
12656                         mceCleanup : function() {
12657                                 var bookmark = selection.getBookmark();
12658
12659                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
12660
12661                                 selection.moveToBookmark(bookmark);
12662                         },
12663
12664                         mceRemoveNode : function(command, ui, value) {
12665                                 var node = value || selection.getNode();
12666
12667                                 // Make sure that the body node isn't removed
12668                                 if (node != editor.getBody()) {
12669                                         storeSelection();
12670                                         editor.dom.remove(node, TRUE);
12671                                         restoreSelection();
12672                                 }
12673                         },
12674
12675                         mceSelectNodeDepth : function(command, ui, value) {
12676                                 var counter = 0;
12677
12678                                 dom.getParent(selection.getNode(), function(node) {
12679                                         if (node.nodeType == 1 && counter++ == value) {
12680                                                 selection.select(node);
12681                                                 return FALSE;
12682                                         }
12683                                 }, editor.getBody());
12684                         },
12685
12686                         mceSelectNode : function(command, ui, value) {
12687                                 selection.select(value);
12688                         },
12689
12690                         mceInsertContent : function(command, ui, value) {
12691                                 var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
12692
12693                                 function findSuitableCaretNode(node, root_node, next) {
12694                                         var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node);
12695
12696                                         while ((node = walker.current())) {
12697                                                 if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
12698                                                         return node;
12699
12700                                                 if (next)
12701                                                         walker.next();
12702                                                 else
12703                                                         walker.prev();
12704                                         }
12705                                 };
12706
12707                                 args = {content: value, format: 'html'};
12708                                 selection.onBeforeSetContent.dispatch(selection, args);
12709                                 value = args.content;
12710
12711                                 // Add caret at end of contents if it's missing
12712                                 if (value.indexOf('{$caret}') == -1)
12713                                         value += '{$caret}';
12714
12715                                 // Set the content at selection to a span and replace it's contents with the value
12716                                 selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
12717                                 dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
12718
12719                                 caretNode = dom.select('#__mce')[0];
12720                                 rootNode = dom.getRoot();
12721
12722                                 // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
12723                                 if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
12724                                         node = findSuitableCaretNode(caretNode, rootNode);
12725                                         if (node) {
12726                                                 if (node.nodeName == 'BR')
12727                                                         node.parentNode.insertBefore(caretNode, node);
12728                                                 else
12729                                                         dom.insertAfter(caretNode, node);
12730                                         }
12731                                 }
12732
12733                                 // Find caret root parent and clean it up using the serializer to avoid nesting
12734                                 while (caretNode) {
12735                                         if (caretNode === rootNode) {
12736                                                 // Clean up the parent element by parsing and serializing it
12737                                                 // This will remove invalid elements/attributes and fix nesting issues
12738                                                 dom.setOuterHTML(parent, 
12739                                                         new tinymce.html.Serializer({}, editor.schema).serialize(
12740                                                                 editor.parser.parse(dom.getOuterHTML(parent))
12741                                                         )
12742                                                 );
12743
12744                                                 break;
12745                                         }
12746
12747                                         parent = caretNode;
12748                                         caretNode = caretNode.parentNode;
12749                                 }
12750
12751                                 // Find caret after cleanup and move selection to that location
12752                                 caretNode = dom.select('#__mce')[0];
12753                                 if (caretNode) {
12754                                         node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true);
12755                                         dom.remove(caretNode);
12756
12757                                         if (node) {
12758                                                 rng = dom.createRng();
12759
12760                                                 if (node.nodeType == 3) {
12761                                                         rng.setStart(node, node.length);
12762                                                         rng.setEnd(node, node.length);
12763                                                 } else {
12764                                                         if (node.nodeName == 'BR') {
12765                                                                 rng.setStartBefore(node);
12766                                                                 rng.setEndBefore(node);
12767                                                         } else {
12768                                                                 rng.setStartAfter(node);
12769                                                                 rng.setEndAfter(node);
12770                                                         }
12771                                                 }
12772
12773                                                 selection.setRng(rng);
12774
12775                                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
12776                                                 if (!tinymce.isIE) {
12777                                                         node = dom.create('span', null, '\u00a0');
12778                                                         rng.insertNode(node);
12779                                                         nodeRect = dom.getRect(node);
12780                                                         viewPortRect = dom.getViewPort(editor.getWin());
12781
12782                                                         // Check if node is out side the viewport if it is then scroll to it
12783                                                         if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
12784                                                                 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
12785                                                                 editor.getBody().scrollLeft = nodeRect.x;
12786                                                                 editor.getBody().scrollTop = nodeRect.y;
12787                                                         }
12788
12789                                                         dom.remove(node);
12790                                                 }
12791
12792                                                 // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
12793                                                 // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
12794                                                 selection.collapse(true);
12795                                         }
12796                                 }
12797
12798                                 selection.onSetContent.dispatch(selection, args);
12799                                 editor.addVisual();
12800                         },
12801
12802                         mceInsertRawHTML : function(command, ui, value) {
12803                                 selection.setContent('tiny_mce_marker');
12804                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
12805                         },
12806
12807                         mceSetContent : function(command, ui, value) {
12808                                 editor.setContent(value);
12809                         },
12810
12811                         'Indent,Outdent' : function(command) {
12812                                 var intentValue, indentUnit, value;
12813
12814                                 // Setup indent level
12815                                 intentValue = settings.indentation;
12816                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
12817                                 intentValue = parseInt(intentValue);
12818
12819                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
12820                                         each(selection.getSelectedBlocks(), function(element) {
12821                                                 if (command == 'outdent') {
12822                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
12823                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
12824                                                 } else
12825                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
12826                                         });
12827                                 } else
12828                                         execNativeCommand(command);
12829                         },
12830
12831                         mceRepaint : function() {
12832                                 var bookmark;
12833
12834                                 if (tinymce.isGecko) {
12835                                         try {
12836                                                 storeSelection(TRUE);
12837
12838                                                 if (selection.getSel())
12839                                                         selection.getSel().selectAllChildren(editor.getBody());
12840
12841                                                 selection.collapse(TRUE);
12842                                                 restoreSelection();
12843                                         } catch (ex) {
12844                                                 // Ignore
12845                                         }
12846                                 }
12847                         },
12848
12849                         mceToggleFormat : function(command, ui, value) {
12850                                 editor.formatter.toggle(value);
12851                         },
12852
12853                         InsertHorizontalRule : function() {
12854                                 editor.execCommand('mceInsertContent', false, '<hr />');
12855                         },
12856
12857                         mceToggleVisualAid : function() {
12858                                 editor.hasVisual = !editor.hasVisual;
12859                                 editor.addVisual();
12860                         },
12861
12862                         mceReplaceContent : function(command, ui, value) {
12863                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
12864                         },
12865
12866                         mceInsertLink : function(command, ui, value) {
12867                                 var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
12868
12869                                 if (tinymce.is(value, 'string'))
12870                                         value = {href : value};
12871
12872                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
12873                                 value.href = value.href.replace(' ', '%20');
12874
12875                                 if (!link) {
12876                                         // WebKit can't create links on float images for some odd reason so just remove it and restore it later
12877                                         if (tinymce.isWebKit) {
12878                                                 img = dom.getParent(selection.getNode(), 'img');
12879
12880                                                 if (img) {
12881                                                         floatVal = img.style.cssFloat;
12882                                                         img.style.cssFloat = null;
12883                                                 }
12884                                         }
12885
12886                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
12887
12888                                         // Restore float value
12889                                         if (floatVal)
12890                                                 img.style.cssFloat = floatVal;
12891
12892                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
12893                                                 dom.setAttribs(link, value);
12894                                         });
12895                                 } else {
12896                                         if (value.href)
12897                                                 dom.setAttribs(link, value);
12898                                         else
12899                                                 editor.dom.remove(link, TRUE);
12900                                 }
12901                         },
12902                         
12903                         selectAll : function() {
12904                                 var root = dom.getRoot(), rng = dom.createRng();
12905
12906                                 rng.setStart(root, 0);
12907                                 rng.setEnd(root, root.childNodes.length);
12908
12909                                 editor.selection.setRng(rng);
12910                         }
12911                 });
12912
12913                 // Add queryCommandState overrides
12914                 addCommands({
12915                         // Override justify commands
12916                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12917                                 return isFormatMatch('align' + command.substring(7));
12918                         },
12919
12920                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12921                                 return isFormatMatch(command);
12922                         },
12923
12924                         mceBlockQuote : function() {
12925                                 return isFormatMatch('blockquote');
12926                         },
12927
12928                         Outdent : function() {
12929                                 var node;
12930
12931                                 if (settings.inline_styles) {
12932                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12933                                                 return TRUE;
12934
12935                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12936                                                 return TRUE;
12937                                 }
12938
12939                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
12940                         },
12941
12942                         'InsertUnorderedList,InsertOrderedList' : function(command) {
12943                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
12944                         }
12945                 }, 'state');
12946
12947                 // Add queryCommandValue overrides
12948                 addCommands({
12949                         'FontSize,FontName' : function(command) {
12950                                 var value = 0, parent;
12951
12952                                 if (parent = dom.getParent(selection.getNode(), 'span')) {
12953                                         if (command == 'fontsize')
12954                                                 value = parent.style.fontSize;
12955                                         else
12956                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
12957                                 }
12958
12959                                 return value;
12960                         }
12961                 }, 'value');
12962
12963                 // Add undo manager logic
12964                 if (settings.custom_undo_redo) {
12965                         addCommands({
12966                                 Undo : function() {
12967                                         editor.undoManager.undo();
12968                                 },
12969
12970                                 Redo : function() {
12971                                         editor.undoManager.redo();
12972                                 }
12973                         });
12974                 }
12975         };
12976 })(tinymce);
12977
12978 (function(tinymce) {
12979         var Dispatcher = tinymce.util.Dispatcher;
12980
12981         tinymce.UndoManager = function(editor) {
12982                 var self, index = 0, data = [];
12983
12984                 function getContent() {
12985                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
12986                 };
12987
12988                 return self = {
12989                         typing : false,
12990
12991                         onAdd : new Dispatcher(self),
12992
12993                         onUndo : new Dispatcher(self),
12994
12995                         onRedo : new Dispatcher(self),
12996
12997                         beforeChange : function() {
12998                                 // Set before bookmark on previous level
12999                                 if (data[index])
13000                                         data[index].beforeBookmark = editor.selection.getBookmark(2, true);
13001                         },
13002
13003                         add : function(level) {
13004                                 var i, settings = editor.settings, lastLevel;
13005
13006                                 level = level || {};
13007                                 level.content = getContent();
13008
13009                                 // Add undo level if needed
13010                                 lastLevel = data[index];
13011                                 if (lastLevel && lastLevel.content == level.content)
13012                                         return null;
13013
13014                                 // Time to compress
13015                                 if (settings.custom_undo_redo_levels) {
13016                                         if (data.length > settings.custom_undo_redo_levels) {
13017                                                 for (i = 0; i < data.length - 1; i++)
13018                                                         data[i] = data[i + 1];
13019
13020                                                 data.length--;
13021                                                 index = data.length;
13022                                         }
13023                                 }
13024
13025                                 // Get a non intrusive normalized bookmark
13026                                 level.bookmark = editor.selection.getBookmark(2, true);
13027
13028                                 // Crop array if needed
13029                                 if (index < data.length - 1)
13030                                         data.length = index + 1;
13031
13032                                 data.push(level);
13033                                 index = data.length - 1;
13034
13035                                 self.onAdd.dispatch(self, level);
13036                                 editor.isNotDirty = 0;
13037
13038                                 return level;
13039                         },
13040
13041                         undo : function() {
13042                                 var level, i;
13043
13044                                 if (self.typing) {
13045                                         self.add();
13046                                         self.typing = false;
13047                                 }
13048
13049                                 if (index > 0) {
13050                                         level = data[--index];
13051
13052                                         editor.setContent(level.content, {format : 'raw'});
13053                                         editor.selection.moveToBookmark(level.beforeBookmark);
13054
13055                                         self.onUndo.dispatch(self, level);
13056                                 }
13057
13058                                 return level;
13059                         },
13060
13061                         redo : function() {
13062                                 var level;
13063
13064                                 if (index < data.length - 1) {
13065                                         level = data[++index];
13066
13067                                         editor.setContent(level.content, {format : 'raw'});
13068                                         editor.selection.moveToBookmark(level.bookmark);
13069
13070                                         self.onRedo.dispatch(self, level);
13071                                 }
13072
13073                                 return level;
13074                         },
13075
13076                         clear : function() {
13077                                 data = [];
13078                                 index = 0;
13079                                 self.typing = false;
13080                         },
13081
13082                         hasUndo : function() {
13083                                 return index > 0 || this.typing;
13084                         },
13085
13086                         hasRedo : function() {
13087                                 return index < data.length - 1 && !this.typing;
13088                         }
13089                 };
13090         };
13091 })(tinymce);
13092
13093 (function(tinymce) {
13094         // Shorten names
13095         var Event = tinymce.dom.Event,
13096                 isIE = tinymce.isIE,
13097                 isGecko = tinymce.isGecko,
13098                 isOpera = tinymce.isOpera,
13099                 each = tinymce.each,
13100                 extend = tinymce.extend,
13101                 TRUE = true,
13102                 FALSE = false;
13103
13104         function cloneFormats(node) {
13105                 var clone, temp, inner;
13106
13107                 do {
13108                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
13109                                 if (clone) {
13110                                         temp = node.cloneNode(false);
13111                                         temp.appendChild(clone);
13112                                         clone = temp;
13113                                 } else {
13114                                         clone = inner = node.cloneNode(false);
13115                                 }
13116
13117                                 clone.removeAttribute('id');
13118                         }
13119                 } while (node = node.parentNode);
13120
13121                 if (clone)
13122                         return {wrapper : clone, inner : inner};
13123         };
13124
13125         // Checks if the selection/caret is at the end of the specified block element
13126         function isAtEnd(rng, par) {
13127                 var rng2 = par.ownerDocument.createRange();
13128
13129                 rng2.setStart(rng.endContainer, rng.endOffset);
13130                 rng2.setEndAfter(par);
13131
13132                 // 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
13133                 return rng2.cloneContents().textContent.length == 0;
13134         };
13135
13136         function splitList(selection, dom, li) {
13137                 var listBlock, block;
13138
13139                 if (dom.isEmpty(li)) {
13140                         listBlock = dom.getParent(li, 'ul,ol');
13141
13142                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
13143                                 dom.split(listBlock, li);
13144                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
13145                                 dom.replace(block, li);
13146                                 selection.select(block, 1);
13147                         }
13148
13149                         return FALSE;
13150                 }
13151
13152                 return TRUE;
13153         };
13154
13155         tinymce.create('tinymce.ForceBlocks', {
13156                 ForceBlocks : function(ed) {
13157                         var t = this, s = ed.settings, elm;
13158
13159                         t.editor = ed;
13160                         t.dom = ed.dom;
13161                         elm = (s.forced_root_block || 'p').toLowerCase();
13162                         s.element = elm.toUpperCase();
13163
13164                         ed.onPreInit.add(t.setup, t);
13165
13166                         if (s.forced_root_block) {
13167                                 ed.onInit.add(t.forceRoots, t);
13168                                 ed.onSetContent.add(t.forceRoots, t);
13169                                 ed.onBeforeGetContent.add(t.forceRoots, t);
13170                                 ed.onExecCommand.add(function(ed, cmd) {
13171                                         if (cmd == 'mceInsertContent') {
13172                                                 t.forceRoots();
13173                                                 ed.nodeChanged();
13174                                         }
13175                                 });
13176                         }
13177                 },
13178
13179                 setup : function() {
13180                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
13181
13182                         // Force root blocks when typing and when getting output
13183                         if (s.forced_root_block) {
13184                                 ed.onBeforeExecCommand.add(t.forceRoots, t);
13185                                 ed.onKeyUp.add(t.forceRoots, t);
13186                                 ed.onPreProcess.add(t.forceRoots, t);
13187                         }
13188
13189                         if (s.force_br_newlines) {
13190                                 // Force IE to produce BRs on enter
13191                                 if (isIE) {
13192                                         ed.onKeyPress.add(function(ed, e) {
13193                                                 var n;
13194
13195                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
13196                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});
13197                                                         n = dom.get('__');
13198                                                         n.removeAttribute('id');
13199                                                         selection.select(n);
13200                                                         selection.collapse();
13201                                                         return Event.cancel(e);
13202                                                 }
13203                                         });
13204                                 }
13205                         }
13206
13207                         if (s.force_p_newlines) {
13208                                 if (!isIE) {
13209                                         ed.onKeyPress.add(function(ed, e) {
13210                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
13211                                                         Event.cancel(e);
13212                                         });
13213                                 } else {
13214                                         // Ungly hack to for IE to preserve the formatting when you press
13215                                         // enter at the end of a block element with formatted contents
13216                                         // This logic overrides the browsers default logic with
13217                                         // custom logic that enables us to control the output
13218                                         tinymce.addUnload(function() {
13219                                                 t._previousFormats = 0; // Fix IE leak
13220                                         });
13221
13222                                         ed.onKeyPress.add(function(ed, e) {
13223                                                 t._previousFormats = 0;
13224
13225                                                 // Clone the current formats, this will later be applied to the new block contents
13226                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
13227                                                         t._previousFormats = cloneFormats(ed.selection.getStart());
13228                                         });
13229
13230                                         ed.onKeyUp.add(function(ed, e) {
13231                                                 // Let IE break the element and the wrap the new caret location in the previous formats
13232                                                 if (e.keyCode == 13 && !e.shiftKey) {
13233                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;
13234
13235                                                         // Parent is an empty block
13236                                                         if (!parent.hasChildNodes() && fmt) {
13237                                                                 parent = dom.getParent(parent, dom.isBlock);
13238
13239                                                                 if (parent && parent.nodeName != 'LI') {
13240                                                                         parent.innerHTML = '';
13241
13242                                                                         if (t._previousFormats) {
13243                                                                                 parent.appendChild(fmt.wrapper);
13244                                                                                 fmt.inner.innerHTML = '\uFEFF';
13245                                                                         } else
13246                                                                                 parent.innerHTML = '\uFEFF';
13247
13248                                                                         selection.select(parent, 1);
13249                                                                         selection.collapse(true);
13250                                                                         ed.getDoc().execCommand('Delete', false, null);
13251                                                                         t._previousFormats = 0;
13252                                                                 }
13253                                                         }
13254                                                 }
13255                                         });
13256                                 }
13257
13258                                 if (isGecko) {
13259                                         ed.onKeyDown.add(function(ed, e) {
13260                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
13261                                                         t.backspaceDelete(e, e.keyCode == 8);
13262                                         });
13263                                 }
13264                         }
13265
13266                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
13267                         if (tinymce.isWebKit) {
13268                                 function insertBr(ed) {
13269                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
13270
13271                                         // Insert BR element
13272                                         rng.insertNode(br = dom.create('br'));
13273
13274                                         // Place caret after BR
13275                                         rng.setStartAfter(br);
13276                                         rng.setEndAfter(br);
13277                                         selection.setRng(rng);
13278
13279                                         // Could not place caret after BR then insert an nbsp entity and move the caret
13280                                         if (selection.getSel().focusNode == br.previousSibling) {
13281                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
13282                                                 selection.collapse(TRUE);
13283                                         }
13284
13285                                         // Create a temporary DIV after the BR and get the position as it
13286                                         // seems like getPos() returns 0 for text nodes and BR elements.
13287                                         dom.insertAfter(div, br);
13288                                         divYPos = dom.getPos(div).y;
13289                                         dom.remove(div);
13290
13291                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
13292                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
13293                                                 ed.getWin().scrollTo(0, divYPos);
13294                                 };
13295
13296                                 ed.onKeyPress.add(function(ed, e) {
13297                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
13298                                                 insertBr(ed);
13299                                                 Event.cancel(e);
13300                                         }
13301                                 });
13302                         }
13303
13304                         // IE specific fixes
13305                         if (isIE) {
13306                                 // Replaces IE:s auto generated paragraphs with the specified element name
13307                                 if (s.element != 'P') {
13308                                         ed.onKeyPress.add(function(ed, e) {
13309                                                 t.lastElm = selection.getNode().nodeName;
13310                                         });
13311
13312                                         ed.onKeyUp.add(function(ed, e) {
13313                                                 var bl, n = selection.getNode(), b = ed.getBody();
13314
13315                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {
13316                                                         n = dom.rename(n, s.element);
13317                                                         selection.select(n);
13318                                                         selection.collapse();
13319                                                         ed.nodeChanged();
13320                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
13321                                                         bl = dom.getParent(n, 'p');
13322
13323                                                         if (bl) {
13324                                                                 dom.rename(bl, s.element);
13325                                                                 ed.nodeChanged();
13326                                                         }
13327                                                 }
13328                                         });
13329                                 }
13330                         }
13331                 },
13332
13333                 find : function(n, t, s) {
13334                         var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
13335
13336                         while (n = w.nextNode()) {
13337                                 c++;
13338
13339                                 // Index by node
13340                                 if (t == 0 && n == s)
13341                                         return c;
13342
13343                                 // Node by index
13344                                 if (t == 1 && c == s)
13345                                         return n;
13346                         }
13347
13348                         return -1;
13349                 },
13350
13351                 forceRoots : function(ed, e) {
13352                         var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
13353                         var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
13354
13355                         // Fix for bug #1863847
13356                         //if (e && e.keyCode == 13)
13357                         //      return TRUE;
13358
13359                         // Wrap non blocks into blocks
13360                         for (i = nl.length - 1; i >= 0; i--) {
13361                                 nx = nl[i];
13362
13363                                 // Ignore internal elements
13364                                 if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
13365                                         bl = null;
13366                                         continue;
13367                                 }
13368
13369                                 // Is text or non block element
13370                                 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
13371                                         if (!bl) {
13372                                                 // Create new block but ignore whitespace
13373                                                 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
13374                                                         // Store selection
13375                                                         if (si == -2 && r) {
13376                                                                 if (!isIE || r.setStart) {
13377                                                                         // If selection is element then mark it
13378                                                                         if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
13379                                                                                 // Save the id of the selected element
13380                                                                                 eid = n.getAttribute("id");
13381                                                                                 n.setAttribute("id", "__mce");
13382                                                                         } else {
13383                                                                                 // If element is inside body, might not be the case in contentEdiable mode
13384                                                                                 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
13385                                                                                         so = r.startOffset;
13386                                                                                         eo = r.endOffset;
13387                                                                                         si = t.find(b, 0, r.startContainer);
13388                                                                                         ei = t.find(b, 0, r.endContainer);
13389                                                                                 }
13390                                                                         }
13391                                                                 } else {
13392                                                                         // Force control range into text range
13393                                                                         if (r.item) {
13394                                                                                 tr = d.body.createTextRange();
13395                                                                                 tr.moveToElementText(r.item(0));
13396                                                                                 r = tr;
13397                                                                         }
13398
13399                                                                         tr = d.body.createTextRange();
13400                                                                         tr.moveToElementText(b);
13401                                                                         tr.collapse(1);
13402                                                                         bp = tr.move('character', c) * -1;
13403
13404                                                                         tr = r.duplicate();
13405                                                                         tr.collapse(1);
13406                                                                         sp = tr.move('character', c) * -1;
13407
13408                                                                         tr = r.duplicate();
13409                                                                         tr.collapse(0);
13410                                                                         le = (tr.move('character', c) * -1) - sp;
13411
13412                                                                         si = sp - bp;
13413                                                                         ei = le;
13414                                                                 }
13415                                                         }
13416
13417                                                         // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
13418                                                         // See: http://support.microsoft.com/kb/829907
13419                                                         bl = ed.dom.create(ed.settings.forced_root_block);
13420                                                         nx.parentNode.replaceChild(bl, nx);
13421                                                         bl.appendChild(nx);
13422                                                 }
13423                                         } else {
13424                                                 if (bl.hasChildNodes())
13425                                                         bl.insertBefore(nx, bl.firstChild);
13426                                                 else
13427                                                         bl.appendChild(nx);
13428                                         }
13429                                 } else
13430                                         bl = null; // Time to create new block
13431                         }
13432
13433                         // Restore selection
13434                         if (si != -2) {
13435                                 if (!isIE || r.setStart) {
13436                                         bl = b.getElementsByTagName(ed.settings.element)[0];
13437                                         r = d.createRange();
13438
13439                                         // Select last location or generated block
13440                                         if (si != -1)
13441                                                 r.setStart(t.find(b, 1, si), so);
13442                                         else
13443                                                 r.setStart(bl, 0);
13444
13445                                         // Select last location or generated block
13446                                         if (ei != -1)
13447                                                 r.setEnd(t.find(b, 1, ei), eo);
13448                                         else
13449                                                 r.setEnd(bl, 0);
13450
13451                                         if (s) {
13452                                                 s.removeAllRanges();
13453                                                 s.addRange(r);
13454                                         }
13455                                 } else {
13456                                         try {
13457                                                 r = s.createRange();
13458                                                 r.moveToElementText(b);
13459                                                 r.collapse(1);
13460                                                 r.moveStart('character', si);
13461                                                 r.moveEnd('character', ei);
13462                                                 r.select();
13463                                         } catch (ex) {
13464                                                 // Ignore
13465                                         }
13466                                 }
13467                         } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
13468                                 // Restore the id of the selected element
13469                                 if (eid)
13470                                         n.setAttribute('id', eid);
13471                                 else
13472                                         n.removeAttribute('id');
13473
13474                                 // Move caret before selected element
13475                                 r = d.createRange();
13476                                 r.setStartBefore(n);
13477                                 r.setEndBefore(n);
13478                                 se.setRng(r);
13479                         }
13480                 },
13481
13482                 getParentBlock : function(n) {
13483                         var d = this.dom;
13484
13485                         return d.getParent(n, d.isBlock);
13486                 },
13487
13488                 insertPara : function(e) {
13489                         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;
13490                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
13491
13492                         ed.undoManager.beforeChange();
13493
13494                         // If root blocks are forced then use Operas default behavior since it's really good
13495 // Removed due to bug: #1853816
13496 //                      if (se.forced_root_block && isOpera)
13497 //                              return TRUE;
13498
13499                         // Setup before range
13500                         rb = d.createRange();
13501
13502                         // If is before the first block element and in body, then move it into first block element
13503                         rb.setStart(s.anchorNode, s.anchorOffset);
13504                         rb.collapse(TRUE);
13505
13506                         // Setup after range
13507                         ra = d.createRange();
13508
13509                         // If is before the first block element and in body, then move it into first block element
13510                         ra.setStart(s.focusNode, s.focusOffset);
13511                         ra.collapse(TRUE);
13512
13513                         // Setup start/end points
13514                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
13515                         sn = dir ? s.anchorNode : s.focusNode;
13516                         so = dir ? s.anchorOffset : s.focusOffset;
13517                         en = dir ? s.focusNode : s.anchorNode;
13518                         eo = dir ? s.focusOffset : s.anchorOffset;
13519
13520                         // If selection is in empty table cell
13521                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
13522                                 if (sn.firstChild.nodeName == 'BR')
13523                                         dom.remove(sn.firstChild); // Remove BR
13524
13525                                 // Create two new block elements
13526                                 if (sn.childNodes.length == 0) {
13527                                         ed.dom.add(sn, se.element, null, '<br />');
13528                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13529                                 } else {
13530                                         n = sn.innerHTML;
13531                                         sn.innerHTML = '';
13532                                         ed.dom.add(sn, se.element, null, n);
13533                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13534                                 }
13535
13536                                 // Move caret into the last one
13537                                 r = d.createRange();
13538                                 r.selectNodeContents(aft);
13539                                 r.collapse(1);
13540                                 ed.selection.setRng(r);
13541
13542                                 return FALSE;
13543                         }
13544
13545                         // If the caret is in an invalid location in FF we need to move it into the first block
13546                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
13547                                 sn = en = sn.firstChild;
13548                                 so = eo = 0;
13549                                 rb = d.createRange();
13550                                 rb.setStart(sn, 0);
13551                                 ra = d.createRange();
13552                                 ra.setStart(en, 0);
13553                         }
13554
13555                         // Never use body as start or end node
13556                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13557                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
13558                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13559                         en = en.nodeName == "BODY" ? en.firstChild : en;
13560
13561                         // Get start and end blocks
13562                         sb = t.getParentBlock(sn);
13563                         eb = t.getParentBlock(en);
13564                         bn = sb ? sb.nodeName : se.element; // Get block name to create
13565
13566                         // Return inside list use default browser behavior
13567                         if (n = t.dom.getParent(sb, 'li,pre')) {
13568                                 if (n.nodeName == 'LI')
13569                                         return splitList(ed.selection, t.dom, n);
13570
13571                                 return TRUE;
13572                         }
13573
13574                         // If caption or absolute layers then always generate new blocks within
13575                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13576                                 bn = se.element;
13577                                 sb = null;
13578                         }
13579
13580                         // If caption or absolute layers then always generate new blocks within
13581                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13582                                 bn = se.element;
13583                                 eb = null;
13584                         }
13585
13586                         // Use P instead
13587                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
13588                                 bn = se.element;
13589                                 sb = eb = null;
13590                         }
13591
13592                         // Setup new before and after blocks
13593                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
13594                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
13595
13596                         // Remove id from after clone
13597                         aft.removeAttribute('id');
13598
13599                         // Is header and cursor is at the end, then force paragraph under
13600                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
13601                                 aft = ed.dom.create(se.element);
13602
13603                         // Find start chop node
13604                         n = sc = sn;
13605                         do {
13606                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13607                                         break;
13608
13609                                 sc = n;
13610                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
13611
13612                         // Find end chop node
13613                         n = ec = en;
13614                         do {
13615                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13616                                         break;
13617
13618                                 ec = n;
13619                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
13620
13621                         // Place first chop part into before block element
13622                         if (sc.nodeName == bn)
13623                                 rb.setStart(sc, 0);
13624                         else
13625                                 rb.setStartBefore(sc);
13626
13627                         rb.setEnd(sn, so);
13628                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13629
13630                         // Place secnd chop part within new block element
13631                         try {
13632                                 ra.setEndAfter(ec);
13633                         } catch(ex) {
13634                                 //console.debug(s.focusNode, s.focusOffset);
13635                         }
13636
13637                         ra.setStart(en, eo);
13638                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13639
13640                         // Create range around everything
13641                         r = d.createRange();
13642                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
13643                                 r.setStartBefore(sc.parentNode);
13644                         } else {
13645                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
13646                                         r.setStartBefore(rb.startContainer);
13647                                 else
13648                                         r.setStart(rb.startContainer, rb.startOffset);
13649                         }
13650
13651                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)
13652                                 r.setEndAfter(ec.parentNode);
13653                         else
13654                                 r.setEnd(ra.endContainer, ra.endOffset);
13655
13656                         // Delete and replace it with new block elements
13657                         r.deleteContents();
13658
13659                         if (isOpera)
13660                                 ed.getWin().scrollTo(0, vp.y);
13661
13662                         // Never wrap blocks in blocks
13663                         if (bef.firstChild && bef.firstChild.nodeName == bn)
13664                                 bef.innerHTML = bef.firstChild.innerHTML;
13665
13666                         if (aft.firstChild && aft.firstChild.nodeName == bn)
13667                                 aft.innerHTML = aft.firstChild.innerHTML;
13668
13669                         // Padd empty blocks
13670                         if (dom.isEmpty(bef))
13671                                 bef.innerHTML = '<br />';
13672
13673                         function appendStyles(e, en) {
13674                                 var nl = [], nn, n, i;
13675
13676                                 e.innerHTML = '';
13677
13678                                 // Make clones of style elements
13679                                 if (se.keep_styles) {
13680                                         n = en;
13681                                         do {
13682                                                 // We only want style specific elements
13683                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
13684                                                         nn = n.cloneNode(FALSE);
13685                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
13686                                                         nl.push(nn);
13687                                                 }
13688                                         } while (n = n.parentNode);
13689                                 }
13690
13691                                 // Append style elements to aft
13692                                 if (nl.length > 0) {
13693                                         for (i = nl.length - 1, nn = e; i >= 0; i--)
13694                                                 nn = nn.appendChild(nl[i]);
13695
13696                                         // Padd most inner style element
13697                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13698                                         return nl[0]; // Move caret to most inner element
13699                                 } else
13700                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13701                         };
13702
13703                         // Fill empty afterblook with current style
13704                         if (dom.isEmpty(aft))
13705                                 car = appendStyles(aft, en);
13706
13707                         // Opera needs this one backwards for older versions
13708                         if (isOpera && parseFloat(opera.version()) < 9.5) {
13709                                 r.insertNode(bef);
13710                                 r.insertNode(aft);
13711                         } else {
13712                                 r.insertNode(aft);
13713                                 r.insertNode(bef);
13714                         }
13715
13716                         // Normalize
13717                         aft.normalize();
13718                         bef.normalize();
13719
13720                         function first(n) {
13721                                 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
13722                         };
13723
13724                         // Move cursor and scroll into view
13725                         r = d.createRange();
13726                         r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
13727                         r.collapse(1);
13728                         s.removeAllRanges();
13729                         s.addRange(r);
13730
13731                         // 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
13732                         y = ed.dom.getPos(aft).y;
13733                         //ch = aft.clientHeight;
13734
13735                         // Is element within viewport
13736                         if (y < vp.y || y + 25 > vp.y + vp.h) {
13737                                 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
13738
13739                                 /*console.debug(
13740                                         'Element: y=' + y + ', h=' + ch + ', ' +
13741                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
13742                                 );*/
13743                         }
13744
13745                         ed.undoManager.add();
13746
13747                         return FALSE;
13748                 },
13749
13750                 backspaceDelete : function(e, bs) {
13751                         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;
13752
13753                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
13754                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
13755                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
13756
13757                                 // Walk the dom backwards until we find a text node
13758                                 for (n = sc.lastChild; n; n = walker.prev()) {
13759                                         if (n.nodeType == 3) {
13760                                                 r.setStart(n, n.nodeValue.length);
13761                                                 r.collapse(true);
13762                                                 se.setRng(r);
13763                                                 return;
13764                                         }
13765                                 }
13766                         }
13767
13768                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
13769                         // This workaround removes the element by hand and moves the caret to the previous element
13770                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
13771                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
13772                                         // Find previous block element
13773                                         n = sc;
13774                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
13775
13776                                         if (n) {
13777                                                 if (sc != b.firstChild) {
13778                                                         // Find last text node
13779                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
13780                                                         while (tn = w.nextNode())
13781                                                                 n = tn;
13782
13783                                                         // Place caret at the end of last text node
13784                                                         r = ed.getDoc().createRange();
13785                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
13786                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
13787                                                         se.setRng(r);
13788
13789                                                         // Remove the target container
13790                                                         ed.dom.remove(sc);
13791                                                 }
13792
13793                                                 return Event.cancel(e);
13794                                         }
13795                                 }
13796                         }
13797                 }
13798         });
13799 })(tinymce);
13800
13801 (function(tinymce) {
13802         // Shorten names
13803         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
13804
13805         tinymce.create('tinymce.ControlManager', {
13806                 ControlManager : function(ed, s) {
13807                         var t = this, i;
13808
13809                         s = s || {};
13810                         t.editor = ed;
13811                         t.controls = {};
13812                         t.onAdd = new tinymce.util.Dispatcher(t);
13813                         t.onPostRender = new tinymce.util.Dispatcher(t);
13814                         t.prefix = s.prefix || ed.id + '_';
13815                         t._cls = {};
13816
13817                         t.onPostRender.add(function() {
13818                                 each(t.controls, function(c) {
13819                                         c.postRender();
13820                                 });
13821                         });
13822                 },
13823
13824                 get : function(id) {
13825                         return this.controls[this.prefix + id] || this.controls[id];
13826                 },
13827
13828                 setActive : function(id, s) {
13829                         var c = null;
13830
13831                         if (c = this.get(id))
13832                                 c.setActive(s);
13833
13834                         return c;
13835                 },
13836
13837                 setDisabled : function(id, s) {
13838                         var c = null;
13839
13840                         if (c = this.get(id))
13841                                 c.setDisabled(s);
13842
13843                         return c;
13844                 },
13845
13846                 add : function(c) {
13847                         var t = this;
13848
13849                         if (c) {
13850                                 t.controls[c.id] = c;
13851                                 t.onAdd.dispatch(c, t);
13852                         }
13853
13854                         return c;
13855                 },
13856
13857                 createControl : function(n) {
13858                         var c, t = this, ed = t.editor;
13859
13860                         each(ed.plugins, function(p) {
13861                                 if (p.createControl) {
13862                                         c = p.createControl(n, t);
13863
13864                                         if (c)
13865                                                 return false;
13866                                 }
13867                         });
13868
13869                         switch (n) {
13870                                 case "|":
13871                                 case "separator":
13872                                         return t.createSeparator();
13873                         }
13874
13875                         if (!c && ed.buttons && (c = ed.buttons[n]))
13876                                 return t.createButton(n, c);
13877
13878                         return t.add(c);
13879                 },
13880
13881                 createDropMenu : function(id, s, cc) {
13882                         var t = this, ed = t.editor, c, bm, v, cls;
13883
13884                         s = extend({
13885                                 'class' : 'mceDropDown',
13886                                 constrain : ed.settings.constrain_menus
13887                         }, s);
13888
13889                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
13890                         if (v = ed.getParam('skin_variant'))
13891                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
13892
13893                         id = t.prefix + id;
13894                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
13895                         c = t.controls[id] = new cls(id, s);
13896                         c.onAddItem.add(function(c, o) {
13897                                 var s = o.settings;
13898
13899                                 s.title = ed.getLang(s.title, s.title);
13900
13901                                 if (!s.onclick) {
13902                                         s.onclick = function(v) {
13903                                                 if (s.cmd)
13904                                                         ed.execCommand(s.cmd, s.ui || false, s.value);
13905                                         };
13906                                 }
13907                         });
13908
13909                         ed.onRemove.add(function() {
13910                                 c.destroy();
13911                         });
13912
13913                         // Fix for bug #1897785, #1898007
13914                         if (tinymce.isIE) {
13915                                 c.onShowMenu.add(function() {
13916                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
13917                                         ed.focus();
13918
13919                                         bm = ed.selection.getBookmark(1);
13920                                 });
13921
13922                                 c.onHideMenu.add(function() {
13923                                         if (bm) {
13924                                                 ed.selection.moveToBookmark(bm);
13925                                                 bm = 0;
13926                                         }
13927                                 });
13928                         }
13929
13930                         return t.add(c);
13931                 },
13932
13933                 createListBox : function(id, s, cc) {
13934                         var t = this, ed = t.editor, cmd, c, cls;
13935
13936                         if (t.get(id))
13937                                 return null;
13938
13939                         s.title = ed.translate(s.title);
13940                         s.scope = s.scope || ed;
13941
13942                         if (!s.onselect) {
13943                                 s.onselect = function(v) {
13944                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13945                                 };
13946                         }
13947
13948                         s = extend({
13949                                 title : s.title,
13950                                 'class' : 'mce_' + id,
13951                                 scope : s.scope,
13952                                 control_manager : t
13953                         }, s);
13954
13955                         id = t.prefix + id;
13956
13957                         if (ed.settings.use_native_selects)
13958                                 c = new tinymce.ui.NativeListBox(id, s);
13959                         else {
13960                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
13961                                 c = new cls(id, s, ed);
13962                         }
13963
13964                         t.controls[id] = c;
13965
13966                         // Fix focus problem in Safari
13967                         if (tinymce.isWebKit) {
13968                                 c.onPostRender.add(function(c, n) {
13969                                         // Store bookmark on mousedown
13970                                         Event.add(n, 'mousedown', function() {
13971                                                 ed.bookmark = ed.selection.getBookmark(1);
13972                                         });
13973
13974                                         // Restore on focus, since it might be lost
13975                                         Event.add(n, 'focus', function() {
13976                                                 ed.selection.moveToBookmark(ed.bookmark);
13977                                                 ed.bookmark = null;
13978                                         });
13979                                 });
13980                         }
13981
13982                         if (c.hideMenu)
13983                                 ed.onMouseDown.add(c.hideMenu, c);
13984
13985                         return t.add(c);
13986                 },
13987
13988                 createButton : function(id, s, cc) {
13989                         var t = this, ed = t.editor, o, c, cls;
13990
13991                         if (t.get(id))
13992                                 return null;
13993
13994                         s.title = ed.translate(s.title);
13995                         s.label = ed.translate(s.label);
13996                         s.scope = s.scope || ed;
13997
13998                         if (!s.onclick && !s.menu_button) {
13999                                 s.onclick = function() {
14000                                         ed.execCommand(s.cmd, s.ui || false, s.value);
14001                                 };
14002                         }
14003
14004                         s = extend({
14005                                 title : s.title,
14006                                 'class' : 'mce_' + id,
14007                                 unavailable_prefix : ed.getLang('unavailable', ''),
14008                                 scope : s.scope,
14009                                 control_manager : t
14010                         }, s);
14011
14012                         id = t.prefix + id;
14013
14014                         if (s.menu_button) {
14015                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
14016                                 c = new cls(id, s, ed);
14017                                 ed.onMouseDown.add(c.hideMenu, c);
14018                         } else {
14019                                 cls = t._cls.button || tinymce.ui.Button;
14020                                 c = new cls(id, s);
14021                         }
14022
14023                         return t.add(c);
14024                 },
14025
14026                 createMenuButton : function(id, s, cc) {
14027                         s = s || {};
14028                         s.menu_button = 1;
14029
14030                         return this.createButton(id, s, cc);
14031                 },
14032
14033                 createSplitButton : function(id, s, cc) {
14034                         var t = this, ed = t.editor, cmd, c, cls;
14035
14036                         if (t.get(id))
14037                                 return null;
14038
14039                         s.title = ed.translate(s.title);
14040                         s.scope = s.scope || ed;
14041
14042                         if (!s.onclick) {
14043                                 s.onclick = function(v) {
14044                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14045                                 };
14046                         }
14047
14048                         if (!s.onselect) {
14049                                 s.onselect = function(v) {
14050                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14051                                 };
14052                         }
14053
14054                         s = extend({
14055                                 title : s.title,
14056                                 'class' : 'mce_' + id,
14057                                 scope : s.scope,
14058                                 control_manager : t
14059                         }, s);
14060
14061                         id = t.prefix + id;
14062                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
14063                         c = t.add(new cls(id, s, ed));
14064                         ed.onMouseDown.add(c.hideMenu, c);
14065
14066                         return c;
14067                 },
14068
14069                 createColorSplitButton : function(id, s, cc) {
14070                         var t = this, ed = t.editor, cmd, c, cls, bm;
14071
14072                         if (t.get(id))
14073                                 return null;
14074
14075                         s.title = ed.translate(s.title);
14076                         s.scope = s.scope || ed;
14077
14078                         if (!s.onclick) {
14079                                 s.onclick = function(v) {
14080                                         if (tinymce.isIE)
14081                                                 bm = ed.selection.getBookmark(1);
14082
14083                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14084                                 };
14085                         }
14086
14087                         if (!s.onselect) {
14088                                 s.onselect = function(v) {
14089                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14090                                 };
14091                         }
14092
14093                         s = extend({
14094                                 title : s.title,
14095                                 'class' : 'mce_' + id,
14096                                 'menu_class' : ed.getParam('skin') + 'Skin',
14097                                 scope : s.scope,
14098                                 more_colors_title : ed.getLang('more_colors')
14099                         }, s);
14100
14101                         id = t.prefix + id;
14102                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
14103                         c = new cls(id, s, ed);
14104                         ed.onMouseDown.add(c.hideMenu, c);
14105
14106                         // Remove the menu element when the editor is removed
14107                         ed.onRemove.add(function() {
14108                                 c.destroy();
14109                         });
14110
14111                         // Fix for bug #1897785, #1898007
14112                         if (tinymce.isIE) {
14113                                 c.onShowMenu.add(function() {
14114                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
14115                                         ed.focus();
14116                                         bm = ed.selection.getBookmark(1);
14117                                 });
14118
14119                                 c.onHideMenu.add(function() {
14120                                         if (bm) {
14121                                                 ed.selection.moveToBookmark(bm);
14122                                                 bm = 0;
14123                                         }
14124                                 });
14125                         }
14126
14127                         return t.add(c);
14128                 },
14129
14130                 createToolbar : function(id, s, cc) {
14131                         var c, t = this, cls;
14132
14133                         id = t.prefix + id;
14134                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
14135                         c = new cls(id, s, t.editor);
14136
14137                         if (t.get(id))
14138                                 return null;
14139
14140                         return t.add(c);
14141                 },
14142                 
14143                 createToolbarGroup : function(id, s, cc) {
14144                         var c, t = this, cls;
14145                         id = t.prefix + id;
14146                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14147                         c = new cls(id, s, t.editor);
14148                         
14149                         if (t.get(id))
14150                                 return null;
14151                         
14152                         return t.add(c);
14153                 },
14154
14155                 createSeparator : function(cc) {
14156                         var cls = cc || this._cls.separator || tinymce.ui.Separator;
14157
14158                         return new cls();
14159                 },
14160
14161                 setControlType : function(n, c) {
14162                         return this._cls[n.toLowerCase()] = c;
14163                 },
14164         
14165                 destroy : function() {
14166                         each(this.controls, function(c) {
14167                                 c.destroy();
14168                         });
14169
14170                         this.controls = null;
14171                 }
14172         });
14173 })(tinymce);
14174
14175 (function(tinymce) {
14176         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
14177
14178         tinymce.create('tinymce.WindowManager', {
14179                 WindowManager : function(ed) {
14180                         var t = this;
14181
14182                         t.editor = ed;
14183                         t.onOpen = new Dispatcher(t);
14184                         t.onClose = new Dispatcher(t);
14185                         t.params = {};
14186                         t.features = {};
14187                 },
14188
14189                 open : function(s, p) {
14190                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14191
14192                         // Default some options
14193                         s = s || {};
14194                         p = p || {};
14195                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14196                         sh = isOpera ? vp.h : screen.height;
14197                         s.name = s.name || 'mc_' + new Date().getTime();
14198                         s.width = parseInt(s.width || 320);
14199                         s.height = parseInt(s.height || 240);
14200                         s.resizable = true;
14201                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14202                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14203                         p.inline = false;
14204                         p.mce_width = s.width;
14205                         p.mce_height = s.height;
14206                         p.mce_auto_focus = s.auto_focus;
14207
14208                         if (mo) {
14209                                 if (isIE) {
14210                                         s.center = true;
14211                                         s.help = false;
14212                                         s.dialogWidth = s.width + 'px';
14213                                         s.dialogHeight = s.height + 'px';
14214                                         s.scroll = s.scrollbars || false;
14215                                 }
14216                         }
14217
14218                         // Build features string
14219                         each(s, function(v, k) {
14220                                 if (tinymce.is(v, 'boolean'))
14221                                         v = v ? 'yes' : 'no';
14222
14223                                 if (!/^(name|url)$/.test(k)) {
14224                                         if (isIE && mo)
14225                                                 f += (f ? ';' : '') + k + ':' + v;
14226                                         else
14227                                                 f += (f ? ',' : '') + k + '=' + v;
14228                                 }
14229                         });
14230
14231                         t.features = s;
14232                         t.params = p;
14233                         t.onOpen.dispatch(t, s, p);
14234
14235                         u = s.url || s.file;
14236                         u = tinymce._addVer(u);
14237
14238                         try {
14239                                 if (isIE && mo) {
14240                                         w = 1;
14241                                         window.showModalDialog(u, window, f);
14242                                 } else
14243                                         w = window.open(u, s.name, f);
14244                         } catch (ex) {
14245                                 // Ignore
14246                         }
14247
14248                         if (!w)
14249                                 alert(t.editor.getLang('popup_blocked'));
14250                 },
14251
14252                 close : function(w) {
14253                         w.close();
14254                         this.onClose.dispatch(this);
14255                 },
14256
14257                 createInstance : function(cl, a, b, c, d, e) {
14258                         var f = tinymce.resolve(cl);
14259
14260                         return new f(a, b, c, d, e);
14261                 },
14262
14263                 confirm : function(t, cb, s, w) {
14264                         w = w || window;
14265
14266                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
14267                 },
14268
14269                 alert : function(tx, cb, s, w) {
14270                         var t = this;
14271
14272                         w = w || window;
14273                         w.alert(t._decode(t.editor.getLang(tx, tx)));
14274
14275                         if (cb)
14276                                 cb.call(s || t);
14277                 },
14278
14279                 resizeBy : function(dw, dh, win) {
14280                         win.resizeBy(dw, dh);
14281                 },
14282
14283                 // Internal functions
14284
14285                 _decode : function(s) {
14286                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14287                 }
14288         });
14289 }(tinymce));
14290 (function(tinymce) {
14291         tinymce.Formatter = function(ed) {
14292                 var formats = {},
14293                         each = tinymce.each,
14294                         dom = ed.dom,
14295                         selection = ed.selection,
14296                         TreeWalker = tinymce.dom.TreeWalker,
14297                         rangeUtils = new tinymce.dom.RangeUtils(dom),
14298                         isValid = ed.schema.isValidChild,
14299                         isBlock = dom.isBlock,
14300                         forcedRootBlock = ed.settings.forced_root_block,
14301                         nodeIndex = dom.nodeIndex,
14302                         INVISIBLE_CHAR = '\uFEFF',
14303                         MCE_ATTR_RE = /^(src|href|style)$/,
14304                         FALSE = false,
14305                         TRUE = true,
14306                         undefined,
14307                         pendingFormats = {apply : [], remove : []};
14308
14309                 function isArray(obj) {
14310                         return obj instanceof Array;
14311                 };
14312
14313                 function getParents(node, selector) {
14314                         return dom.getParents(node, selector, dom.getRoot());
14315                 };
14316
14317                 function isCaretNode(node) {
14318                         return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
14319                 };
14320
14321                 // Public functions
14322
14323                 function get(name) {
14324                         return name ? formats[name] : formats;
14325                 };
14326
14327                 function register(name, format) {
14328                         if (name) {
14329                                 if (typeof(name) !== 'string') {
14330                                         each(name, function(format, name) {
14331                                                 register(name, format);
14332                                         });
14333                                 } else {
14334                                         // Force format into array and add it to internal collection
14335                                         format = format.length ? format : [format];
14336
14337                                         each(format, function(format) {
14338                                                 // Set deep to false by default on selector formats this to avoid removing
14339                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs
14340                                                 if (format.deep === undefined)
14341                                                         format.deep = !format.selector;
14342
14343                                                 // Default to true
14344                                                 if (format.split === undefined)
14345                                                         format.split = !format.selector || format.inline;
14346
14347                                                 // Default to true
14348                                                 if (format.remove === undefined && format.selector && !format.inline)
14349                                                         format.remove = 'none';
14350
14351                                                 // Mark format as a mixed format inline + block level
14352                                                 if (format.selector && format.inline) {
14353                                                         format.mixed = true;
14354                                                         format.block_expand = true;
14355                                                 }
14356
14357                                                 // Split classes if needed
14358                                                 if (typeof(format.classes) === 'string')
14359                                                         format.classes = format.classes.split(/\s+/);
14360                                         });
14361
14362                                         formats[name] = format;
14363                                 }
14364                         }
14365                 };
14366
14367                 var getTextDecoration = function(node) {
14368                         var decoration;
14369
14370                         ed.dom.getParent(node, function(n) {
14371                                 decoration = ed.dom.getStyle(n, 'text-decoration');
14372                                 return decoration && decoration !== 'none';
14373                         });
14374
14375                         return decoration;
14376                 };
14377
14378                 var processUnderlineAndColor = function(node) {
14379                         var textDecoration;
14380                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14381                                 textDecoration = getTextDecoration(node.parentNode);
14382                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
14383                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);
14384                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14385                                         ed.dom.setStyle(node, 'text-decoration', null);
14386                                 }
14387                         }
14388                 };
14389
14390                 function apply(name, vars, node) {
14391                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
14392
14393                         function moveStart(rng) {
14394                                 var container = rng.startContainer,
14395                                         offset = rng.startOffset,
14396                                         walker, node;
14397
14398                                 // Move startContainer/startOffset in to a suitable node
14399                                 if (container.nodeType == 1 || container.nodeValue === "") {
14400                                         container = container.nodeType == 1 ? container.childNodes[offset] : container;
14401
14402                                         // Might fail if the offset is behind the last element in it's container
14403                                         if (container) {
14404                                                 walker = new TreeWalker(container, container.parentNode);
14405                                                 for (node = walker.current(); node; node = walker.next()) {
14406                                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14407                                                                 rng.setStart(node, 0);
14408                                                                 break;
14409                                                         }
14410                                                 }
14411                                         }
14412                                 }
14413
14414                                 return rng;
14415                         };
14416
14417                         function setElementFormat(elm, fmt) {
14418                                 fmt = fmt || format;
14419
14420                                 if (elm) {
14421                                         each(fmt.styles, function(value, name) {
14422                                                 dom.setStyle(elm, name, replaceVars(value, vars));
14423                                         });
14424
14425                                         each(fmt.attributes, function(value, name) {
14426                                                 dom.setAttrib(elm, name, replaceVars(value, vars));
14427                                         });
14428
14429                                         each(fmt.classes, function(value) {
14430                                                 value = replaceVars(value, vars);
14431
14432                                                 if (!dom.hasClass(elm, value))
14433                                                         dom.addClass(elm, value);
14434                                         });
14435                                 }
14436                         };
14437
14438                         function applyRngStyle(rng) {
14439                                 var newWrappers = [], wrapName, wrapElm;
14440
14441                                 // Setup wrapper element
14442                                 wrapName = format.inline || format.block;
14443                                 wrapElm = dom.create(wrapName);
14444                                 setElementFormat(wrapElm);
14445
14446                                 rangeUtils.walk(rng, function(nodes) {
14447                                         var currentWrapElm;
14448
14449                                         function process(node) {
14450                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
14451
14452                                                 // Stop wrapping on br elements
14453                                                 if (isEq(nodeName, 'br')) {
14454                                                         currentWrapElm = 0;
14455
14456                                                         // Remove any br elements when we wrap things
14457                                                         if (format.block)
14458                                                                 dom.remove(node);
14459
14460                                                         return;
14461                                                 }
14462
14463                                                 // If node is wrapper type
14464                                                 if (format.wrapper && matchNode(node, name, vars)) {
14465                                                         currentWrapElm = 0;
14466                                                         return;
14467                                                 }
14468
14469                                                 // Can we rename the block
14470                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
14471                                                         node = dom.rename(node, wrapName);
14472                                                         setElementFormat(node);
14473                                                         newWrappers.push(node);
14474                                                         currentWrapElm = 0;
14475                                                         return;
14476                                                 }
14477
14478                                                 // Handle selector patterns
14479                                                 if (format.selector) {
14480                                                         // Look for matching formats
14481                                                         each(formatList, function(format) {
14482                                                                 // Check collapsed state if it exists
14483                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
14484                                                                         return;
14485                                                                 }
14486
14487                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
14488                                                                         setElementFormat(node, format);
14489                                                                         found = true;
14490                                                                 }
14491                                                         });
14492
14493                                                         // Continue processing if a selector match wasn't found and a inline element is defined
14494                                                         if (!format.inline || found) {
14495                                                                 currentWrapElm = 0;
14496                                                                 return;
14497                                                         }
14498                                                 }
14499
14500                                                 // Is it valid to wrap this item
14501                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
14502                                                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
14503                                                         // Start wrapping
14504                                                         if (!currentWrapElm) {
14505                                                                 // Wrap the node
14506                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14507                                                                 node.parentNode.insertBefore(currentWrapElm, node);
14508                                                                 newWrappers.push(currentWrapElm);
14509                                                         }
14510
14511                                                         currentWrapElm.appendChild(node);
14512                                                 } else {
14513                                                         // Start a new wrapper for possible children
14514                                                         currentWrapElm = 0;
14515
14516                                                         each(tinymce.grep(node.childNodes), process);
14517
14518                                                         // End the last wrapper
14519                                                         currentWrapElm = 0;
14520                                                 }
14521                                         };
14522
14523                                         // Process siblings from range
14524                                         each(nodes, process);
14525                                 });
14526
14527                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
14528                                 if (format.wrap_links === false) {
14529                                         each(newWrappers, function(node) {
14530                                                 function process(node) {
14531                                                         var i, currentWrapElm, children;
14532
14533                                                         if (node.nodeName === 'A') {
14534                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14535                                                                 newWrappers.push(currentWrapElm);
14536
14537                                                                 children = tinymce.grep(node.childNodes);
14538                                                                 for (i = 0; i < children.length; i++)
14539                                                                         currentWrapElm.appendChild(children[i]);
14540
14541                                                                 node.appendChild(currentWrapElm);
14542                                                         }
14543
14544                                                         each(tinymce.grep(node.childNodes), process);
14545                                                 };
14546
14547                                                 process(node);
14548                                         });
14549                                 }
14550
14551                                 // Cleanup
14552                                 each(newWrappers, function(node) {
14553                                         var childCount;
14554
14555                                         function getChildCount(node) {
14556                                                 var count = 0;
14557
14558                                                 each(node.childNodes, function(node) {
14559                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
14560                                                                 count++;
14561                                                 });
14562
14563                                                 return count;
14564                                         };
14565
14566                                         function mergeStyles(node) {
14567                                                 var child, clone;
14568
14569                                                 each(node.childNodes, function(node) {
14570                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
14571                                                                 child = node;
14572                                                                 return FALSE; // break loop
14573                                                         }
14574                                                 });
14575
14576                                                 // If child was found and of the same type as the current node
14577                                                 if (child && matchName(child, format)) {
14578                                                         clone = child.cloneNode(FALSE);
14579                                                         setElementFormat(clone);
14580
14581                                                         dom.replace(clone, node, TRUE);
14582                                                         dom.remove(child, 1);
14583                                                 }
14584
14585                                                 return clone || node;
14586                                         };
14587
14588                                         childCount = getChildCount(node);
14589
14590                                         // Remove empty nodes but only if there is multiple wrappers and they are not block
14591                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
14592                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
14593                                                 dom.remove(node, 1);
14594                                                 return;
14595                                         }
14596
14597                                         if (format.inline || format.wrapper) {
14598                                                 // Merges the current node with it's children of similar type to reduce the number of elements
14599                                                 if (!format.exact && childCount === 1)
14600                                                         node = mergeStyles(node);
14601
14602                                                 // Remove/merge children
14603                                                 each(formatList, function(format) {
14604                                                         // Merge all children of similar type will move styles from child to parent
14605                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
14606                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
14607                                                         each(dom.select(format.inline, node), function(child) {
14608                                                                 var parent;
14609
14610                                                                 // When wrap_links is set to false we don't want
14611                                                                 // to remove the format on children within links
14612                                                                 if (format.wrap_links === false) {
14613                                                                         parent = child.parentNode;
14614
14615                                                                         do {
14616                                                                                 if (parent.nodeName === 'A')
14617                                                                                         return;
14618                                                                         } while (parent = parent.parentNode);
14619                                                                 }
14620
14621                                                                 removeFormat(format, vars, child, format.exact ? child : null);
14622                                                         });
14623                                                 });
14624
14625                                                 // Remove child if direct parent is of same type
14626                                                 if (matchNode(node.parentNode, name, vars)) {
14627                                                         dom.remove(node, 1);
14628                                                         node = 0;
14629                                                         return TRUE;
14630                                                 }
14631
14632                                                 // Look for parent with similar style format
14633                                                 if (format.merge_with_parents) {
14634                                                         dom.getParent(node.parentNode, function(parent) {
14635                                                                 if (matchNode(parent, name, vars)) {
14636                                                                         dom.remove(node, 1);
14637                                                                         node = 0;
14638                                                                         return TRUE;
14639                                                                 }
14640                                                         });
14641                                                 }
14642
14643                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
14644                                                 if (node) {
14645                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
14646                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
14647                                                 }
14648                                         }
14649                                 });
14650                         };
14651
14652                         if (format) {
14653                                 if (node) {
14654                                         rng = dom.createRng();
14655
14656                                         rng.setStartBefore(node);
14657                                         rng.setEndAfter(node);
14658
14659                                         applyRngStyle(expandRng(rng, formatList));
14660                                 } else {
14661                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14662                                                 // Obtain selection node before selection is unselected by applyRngStyle()
14663                                                 var curSelNode = ed.selection.getNode();
14664
14665                                                 // Apply formatting to selection
14666                                                 bookmark = selection.getBookmark();
14667                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
14668
14669                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.
14670                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
14671                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
14672                                                         processUnderlineAndColor(curSelNode);
14673                                                 }
14674
14675                                                 selection.moveToBookmark(bookmark);
14676                                                 selection.setRng(moveStart(selection.getRng(TRUE)));
14677                                                 ed.nodeChanged();
14678                                         } else
14679                                                 performCaretAction('apply', name, vars);
14680                                 }
14681                         }
14682                 };
14683
14684                 function remove(name, vars, node) {
14685                         var formatList = get(name), format = formatList[0], bookmark, i, rng;
14686
14687                         function moveStart(rng) {
14688                                 var container = rng.startContainer,
14689                                         offset = rng.startOffset,
14690                                         walker, node, nodes, tmpNode;
14691
14692                                 // Convert text node into index if possible
14693                                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
14694                                         container = container.parentNode;
14695                                         offset = nodeIndex(container) + 1;
14696                                 }
14697
14698                                 // Move startContainer/startOffset in to a suitable node
14699                                 if (container.nodeType == 1) {
14700                                         nodes = container.childNodes;
14701                                         container = nodes[Math.min(offset, nodes.length - 1)];
14702                                         walker = new TreeWalker(container);
14703
14704                                         // If offset is at end of the parent node walk to the next one
14705                                         if (offset > nodes.length - 1)
14706                                                 walker.next();
14707
14708                                         for (node = walker.current(); node; node = walker.next()) {
14709                                                 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14710                                                         // IE has a "neat" feature where it moves the start node into the closest element
14711                                                         // we can avoid this by inserting an element before it and then remove it after we set the selection
14712                                                         tmpNode = dom.create('a', null, INVISIBLE_CHAR);
14713                                                         node.parentNode.insertBefore(tmpNode, node);
14714
14715                                                         // Set selection and remove tmpNode
14716                                                         rng.setStart(node, 0);
14717                                                         selection.setRng(rng);
14718                                                         dom.remove(tmpNode);
14719
14720                                                         return;
14721                                                 }
14722                                         }
14723                                 }
14724                         };
14725
14726                         // Merges the styles for each node
14727                         function process(node) {
14728                                 var children, i, l;
14729
14730                                 // Grab the children first since the nodelist might be changed
14731                                 children = tinymce.grep(node.childNodes);
14732
14733                                 // Process current node
14734                                 for (i = 0, l = formatList.length; i < l; i++) {
14735                                         if (removeFormat(formatList[i], vars, node, node))
14736                                                 break;
14737                                 }
14738
14739                                 // Process the children
14740                                 if (format.deep) {
14741                                         for (i = 0, l = children.length; i < l; i++)
14742                                                 process(children[i]);
14743                                 }
14744                         };
14745
14746                         function findFormatRoot(container) {
14747                                 var formatRoot;
14748
14749                                 // Find format root
14750                                 each(getParents(container.parentNode).reverse(), function(parent) {
14751                                         var format;
14752
14753                                         // Find format root element
14754                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
14755                                                 // Is the node matching the format we are looking for
14756                                                 format = matchNode(parent, name, vars);
14757                                                 if (format && format.split !== false)
14758                                                         formatRoot = parent;
14759                                         }
14760                                 });
14761
14762                                 return formatRoot;
14763                         };
14764
14765                         function wrapAndSplit(format_root, container, target, split) {
14766                                 var parent, clone, lastClone, firstClone, i, formatRootParent;
14767
14768                                 // Format root found then clone formats and split it
14769                                 if (format_root) {
14770                                         formatRootParent = format_root.parentNode;
14771
14772                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14773                                                 clone = parent.cloneNode(FALSE);
14774
14775                                                 for (i = 0; i < formatList.length; i++) {
14776                                                         if (removeFormat(formatList[i], vars, clone, clone)) {
14777                                                                 clone = 0;
14778                                                                 break;
14779                                                         }
14780                                                 }
14781
14782                                                 // Build wrapper node
14783                                                 if (clone) {
14784                                                         if (lastClone)
14785                                                                 clone.appendChild(lastClone);
14786
14787                                                         if (!firstClone)
14788                                                                 firstClone = clone;
14789
14790                                                         lastClone = clone;
14791                                                 }
14792                                         }
14793
14794                                         // Never split block elements if the format is mixed
14795                                         if (split && (!format.mixed || !isBlock(format_root)))
14796                                                 container = dom.split(format_root, container);
14797
14798                                         // Wrap container in cloned formats
14799                                         if (lastClone) {
14800                                                 target.parentNode.insertBefore(lastClone, target);
14801                                                 firstClone.appendChild(target);
14802                                         }
14803                                 }
14804
14805                                 return container;
14806                         };
14807
14808                         function splitToFormatRoot(container) {
14809                                 return wrapAndSplit(findFormatRoot(container), container, container, true);
14810                         };
14811
14812                         function unwrap(start) {
14813                                 var node = dom.get(start ? '_start' : '_end'),
14814                                         out = node[start ? 'firstChild' : 'lastChild'];
14815
14816                                 // If the end is placed within the start the result will be removed
14817                                 // So this checks if the out node is a bookmark node if it is it
14818                                 // checks for another more suitable node
14819                                 if (isBookmarkNode(out))
14820                                         out = out[start ? 'firstChild' : 'lastChild'];
14821
14822                                 dom.remove(node, true);
14823
14824                                 return out;
14825                         };
14826
14827                         function removeRngStyle(rng) {
14828                                 var startContainer, endContainer;
14829
14830                                 rng = expandRng(rng, formatList, TRUE);
14831
14832                                 if (format.split) {
14833                                         startContainer = getContainer(rng, TRUE);
14834                                         endContainer = getContainer(rng);
14835
14836                                         if (startContainer != endContainer) {
14837                                                 // Wrap start/end nodes in span element since these might be cloned/moved
14838                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
14839                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
14840
14841                                                 // Split start/end
14842                                                 splitToFormatRoot(startContainer);
14843                                                 splitToFormatRoot(endContainer);
14844
14845                                                 // Unwrap start/end to get real elements again
14846                                                 startContainer = unwrap(TRUE);
14847                                                 endContainer = unwrap();
14848                                         } else
14849                                                 startContainer = endContainer = splitToFormatRoot(startContainer);
14850
14851                                         // Update range positions since they might have changed after the split operations
14852                                         rng.startContainer = startContainer.parentNode;
14853                                         rng.startOffset = nodeIndex(startContainer);
14854                                         rng.endContainer = endContainer.parentNode;
14855                                         rng.endOffset = nodeIndex(endContainer) + 1;
14856                                 }
14857
14858                                 // Remove items between start/end
14859                                 rangeUtils.walk(rng, function(nodes) {
14860                                         each(nodes, function(node) {
14861                                                 process(node);
14862
14863                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14864                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14865                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
14866                                                 }
14867                                         });
14868                                 });
14869                         };
14870
14871                         // Handle node
14872                         if (node) {
14873                                 rng = dom.createRng();
14874                                 rng.setStartBefore(node);
14875                                 rng.setEndAfter(node);
14876                                 removeRngStyle(rng);
14877                                 return;
14878                         }
14879
14880                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14881                                 bookmark = selection.getBookmark();
14882                                 removeRngStyle(selection.getRng(TRUE));
14883                                 selection.moveToBookmark(bookmark);
14884
14885                                 // 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
14886                                 if (match(name, vars, selection.getStart())) {
14887                                         moveStart(selection.getRng(true));
14888                                 }
14889
14890                                 ed.nodeChanged();
14891                         } else
14892                                 performCaretAction('remove', name, vars);
14893                 };
14894
14895                 function toggle(name, vars, node) {
14896                         var fmt = get(name);
14897
14898                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
14899                                 remove(name, vars, node);
14900                         else
14901                                 apply(name, vars, node);
14902                 };
14903
14904                 function matchNode(node, name, vars, similar) {
14905                         var formatList = get(name), format, i, classes;
14906
14907                         function matchItems(node, format, item_name) {
14908                                 var key, value, items = format[item_name], i;
14909
14910                                 // Check all items
14911                                 if (items) {
14912                                         // Non indexed object
14913                                         if (items.length === undefined) {
14914                                                 for (key in items) {
14915                                                         if (items.hasOwnProperty(key)) {
14916                                                                 if (item_name === 'attributes')
14917                                                                         value = dom.getAttrib(node, key);
14918                                                                 else
14919                                                                         value = getStyle(node, key);
14920
14921                                                                 if (similar && !value && !format.exact)
14922                                                                         return;
14923
14924                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
14925                                                                         return;
14926                                                         }
14927                                                 }
14928                                         } else {
14929                                                 // Only one match needed for indexed arrays
14930                                                 for (i = 0; i < items.length; i++) {
14931                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
14932                                                                 return format;
14933                                                 }
14934                                         }
14935                                 }
14936
14937                                 return format;
14938                         };
14939
14940                         if (formatList && node) {
14941                                 // Check each format in list
14942                                 for (i = 0; i < formatList.length; i++) {
14943                                         format = formatList[i];
14944
14945                                         // Name name, attributes, styles and classes
14946                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14947                                                 // Match classes
14948                                                 if (classes = format.classes) {
14949                                                         for (i = 0; i < classes.length; i++) {
14950                                                                 if (!dom.hasClass(node, classes[i]))
14951                                                                         return;
14952                                                         }
14953                                                 }
14954
14955                                                 return format;
14956                                         }
14957                                 }
14958                         }
14959                 };
14960
14961                 function match(name, vars, node) {
14962                         var startNode, i;
14963
14964                         function matchParents(node) {
14965                                 // Find first node with similar format settings
14966                                 node = dom.getParent(node, function(node) {
14967                                         return !!matchNode(node, name, vars, true);
14968                                 });
14969
14970                                 // Do an exact check on the similar format element
14971                                 return matchNode(node, name, vars);
14972                         };
14973
14974                         // Check specified node
14975                         if (node)
14976                                 return matchParents(node);
14977
14978                         // Check pending formats
14979                         if (selection.isCollapsed()) {
14980                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14981                                         if (pendingFormats.apply[i].name == name)
14982                                                 return true;
14983                                 }
14984
14985                                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14986                                         if (pendingFormats.remove[i].name == name)
14987                                                 return false;
14988                                 }
14989
14990                                 return matchParents(selection.getNode());
14991                         }
14992
14993                         // Check selected node
14994                         node = selection.getNode();
14995                         if (matchParents(node))
14996                                 return TRUE;
14997
14998                         // Check start node if it's different
14999                         startNode = selection.getStart();
15000                         if (startNode != node) {
15001                                 if (matchParents(startNode))
15002                                         return TRUE;
15003                         }
15004
15005                         return FALSE;
15006                 };
15007
15008                 function matchAll(names, vars) {
15009                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15010
15011                         // If the selection is collapsed then check pending formats
15012                         if (selection.isCollapsed()) {
15013                                 for (ni = 0; ni < names.length; ni++) {
15014                                         // If the name is to be removed, then stop it from being added
15015                                         for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15016                                                 name = names[ni];
15017
15018                                                 if (pendingFormats.remove[i].name == name) {
15019                                                         checkedMap[name] = true;
15020                                                         break;
15021                                                 }
15022                                         }
15023                                 }
15024
15025                                 // If the format is to be applied
15026                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15027                                         for (ni = 0; ni < names.length; ni++) {
15028                                                 name = names[ni];
15029
15030                                                 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
15031                                                         checkedMap[name] = true;
15032                                                         matchedFormatNames.push(name);
15033                                                 }
15034                                         }
15035                                 }
15036                         }
15037
15038                         // Check start of selection for formats
15039                         startElement = selection.getStart();
15040                         dom.getParent(startElement, function(node) {
15041                                 var i, name;
15042
15043                                 for (i = 0; i < names.length; i++) {
15044                                         name = names[i];
15045
15046                                         if (!checkedMap[name] && matchNode(node, name, vars)) {
15047                                                 checkedMap[name] = true;
15048                                                 matchedFormatNames.push(name);
15049                                         }
15050                                 }
15051                         });
15052
15053                         return matchedFormatNames;
15054                 };
15055
15056                 function canApply(name) {
15057                         var formatList = get(name), startNode, parents, i, x, selector;
15058
15059                         if (formatList) {
15060                                 startNode = selection.getStart();
15061                                 parents = getParents(startNode);
15062
15063                                 for (x = formatList.length - 1; x >= 0; x--) {
15064                                         selector = formatList[x].selector;
15065
15066                                         // Format is not selector based, then always return TRUE
15067                                         if (!selector)
15068                                                 return TRUE;
15069
15070                                         for (i = parents.length - 1; i >= 0; i--) {
15071                                                 if (dom.is(parents[i], selector))
15072                                                         return TRUE;
15073                                         }
15074                                 }
15075                         }
15076
15077                         return FALSE;
15078                 };
15079
15080                 // Expose to public
15081                 tinymce.extend(this, {
15082                         get : get,
15083                         register : register,
15084                         apply : apply,
15085                         remove : remove,
15086                         toggle : toggle,
15087                         match : match,
15088                         matchAll : matchAll,
15089                         matchNode : matchNode,
15090                         canApply : canApply
15091                 });
15092
15093                 // Private functions
15094
15095                 function matchName(node, format) {
15096                         // Check for inline match
15097                         if (isEq(node, format.inline))
15098                                 return TRUE;
15099
15100                         // Check for block match
15101                         if (isEq(node, format.block))
15102                                 return TRUE;
15103
15104                         // Check for selector match
15105                         if (format.selector)
15106                                 return dom.is(node, format.selector);
15107                 };
15108
15109                 function isEq(str1, str2) {
15110                         str1 = str1 || '';
15111                         str2 = str2 || '';
15112
15113                         str1 = '' + (str1.nodeName || str1);
15114                         str2 = '' + (str2.nodeName || str2);
15115
15116                         return str1.toLowerCase() == str2.toLowerCase();
15117                 };
15118
15119                 function getStyle(node, name) {
15120                         var styleVal = dom.getStyle(node, name);
15121
15122                         // Force the format to hex
15123                         if (name == 'color' || name == 'backgroundColor')
15124                                 styleVal = dom.toHex(styleVal);
15125
15126                         // Opera will return bold as 700
15127                         if (name == 'fontWeight' && styleVal == 700)
15128                                 styleVal = 'bold';
15129
15130                         return '' + styleVal;
15131                 };
15132
15133                 function replaceVars(value, vars) {
15134                         if (typeof(value) != "string")
15135                                 value = value(vars);
15136                         else if (vars) {
15137                                 value = value.replace(/%(\w+)/g, function(str, name) {
15138                                         return vars[name] || str;
15139                                 });
15140                         }
15141
15142                         return value;
15143                 };
15144
15145                 function isWhiteSpaceNode(node) {
15146                         return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
15147                 };
15148
15149                 function wrap(node, name, attrs) {
15150                         var wrapper = dom.create(name, attrs);
15151
15152                         node.parentNode.insertBefore(wrapper, node);
15153                         wrapper.appendChild(node);
15154
15155                         return wrapper;
15156                 };
15157
15158                 function expandRng(rng, format, remove) {
15159                         var startContainer = rng.startContainer,
15160                                 startOffset = rng.startOffset,
15161                                 endContainer = rng.endContainer,
15162                                 endOffset = rng.endOffset, sibling, lastIdx, leaf;
15163
15164                         // This function walks up the tree if there is no siblings before/after the node
15165                         function findParentContainer(container, child_name, sibling_name, root) {
15166                                 var parent, child;
15167
15168                                 root = root || dom.getRoot();
15169
15170                                 for (;;) {
15171                                         // Check if we can move up are we at root level or body level
15172                                         parent = container.parentNode;
15173
15174                                         // Stop expanding on block elements or root depending on format
15175                                         if (parent == root || (!format[0].block_expand && isBlock(parent)))
15176                                                 return container;
15177
15178                                         for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
15179                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15180                                                         return container;
15181
15182                                                 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
15183                                                         return container;
15184                                         }
15185
15186                                         container = container.parentNode;
15187                                 }
15188
15189                                 return container;
15190                         };
15191
15192                         // This function walks down the tree to find the leaf at the selection.
15193                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15194                         function findLeaf(node, offset) {
15195                                 if (offset === undefined)
15196                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15197                                 while (node && node.hasChildNodes()) {
15198                                         node = node.childNodes[offset];
15199                                         if (node)
15200                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15201                                 }
15202                                 return { node: node, offset: offset };
15203                         }
15204
15205                         // If index based start position then resolve it
15206                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15207                                 lastIdx = startContainer.childNodes.length - 1;
15208                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15209
15210                                 if (startContainer.nodeType == 3)
15211                                         startOffset = 0;
15212                         }
15213
15214                         // If index based end position then resolve it
15215                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15216                                 lastIdx = endContainer.childNodes.length - 1;
15217                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15218
15219                                 if (endContainer.nodeType == 3)
15220                                         endOffset = endContainer.nodeValue.length;
15221                         }
15222
15223                         // Exclude bookmark nodes if possible
15224                         if (isBookmarkNode(startContainer.parentNode))
15225                                 startContainer = startContainer.parentNode;
15226
15227                         if (isBookmarkNode(startContainer))
15228                                 startContainer = startContainer.nextSibling || startContainer;
15229
15230                         if (isBookmarkNode(endContainer.parentNode)) {
15231                                 endOffset = dom.nodeIndex(endContainer);
15232                                 endContainer = endContainer.parentNode;
15233                         }
15234
15235                         if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
15236                                 endContainer = endContainer.previousSibling;
15237                                 endOffset = endContainer.length;
15238                         }
15239
15240                         if (format[0].inline) {
15241                                 // Avoid applying formatting to a trailing space.
15242                                 leaf = findLeaf(endContainer, endOffset);
15243                                 if (leaf.node) {
15244                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
15245                                                 leaf = findLeaf(leaf.node.previousSibling);
15246
15247                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15248                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15249
15250                                                 if (leaf.offset > 1) {
15251                                                         endContainer = leaf.node;
15252                                                         endContainer.splitText(leaf.offset - 1);
15253                                                 } else if (leaf.node.previousSibling) {
15254                                                         endContainer = leaf.node.previousSibling;
15255                                                 }
15256                                         }
15257                                 }
15258                         }
15259                         
15260                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers
15261                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15262                         // This will reduce the number of wrapper elements that needs to be created
15263                         // Move start point up the tree
15264                         if (format[0].inline || format[0].block_expand) {
15265                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15266                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15267                         }
15268
15269                         // Expand start/end container to matching selector
15270                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15271                                 function findSelectorEndPoint(container, sibling_name) {
15272                                         var parents, i, y, curFormat;
15273
15274                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
15275                                                 container = container[sibling_name];
15276
15277                                         parents = getParents(container);
15278                                         for (i = 0; i < parents.length; i++) {
15279                                                 for (y = 0; y < format.length; y++) {
15280                                                         curFormat = format[y];
15281
15282                                                         // If collapsed state is set then skip formats that doesn't match that
15283                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
15284                                                                 continue;
15285
15286                                                         if (dom.is(parents[i], curFormat.selector))
15287                                                                 return parents[i];
15288                                                 }
15289                                         }
15290
15291                                         return container;
15292                                 };
15293
15294                                 // Find new startContainer/endContainer if there is better one
15295                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15296                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15297                         }
15298
15299                         // Expand start/end container to matching block element or text node
15300                         if (format[0].block || format[0].selector) {
15301                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {
15302                                         var node;
15303
15304                                         // Expand to block of similar type
15305                                         if (!format[0].wrapper)
15306                                                 node = dom.getParent(container, format[0].block);
15307
15308                                         // Expand to first wrappable block element or any block element
15309                                         if (!node)
15310                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
15311
15312                                         // Exclude inner lists from wrapping
15313                                         if (node && format[0].wrapper)
15314                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;
15315
15316                                         // Didn't find a block element look for first/last wrappable element
15317                                         if (!node) {
15318                                                 node = container;
15319
15320                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {
15321                                                         node = node[sibling_name];
15322
15323                                                         // Break on BR but include it will be removed later on
15324                                                         // we can't remove it now since we need to check if it can be wrapped
15325                                                         if (isEq(node, 'br'))
15326                                                                 break;
15327                                                 }
15328                                         }
15329
15330                                         return node || container;
15331                                 };
15332
15333                                 // Find new startContainer/endContainer if there is better one
15334                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15335                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15336
15337                                 // Non block element then try to expand up the leaf
15338                                 if (format[0].block) {
15339                                         if (!isBlock(startContainer))
15340                                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15341
15342                                         if (!isBlock(endContainer))
15343                                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15344                                 }
15345                         }
15346
15347                         // Setup index for startContainer
15348                         if (startContainer.nodeType == 1) {
15349                                 startOffset = nodeIndex(startContainer);
15350                                 startContainer = startContainer.parentNode;
15351                         }
15352
15353                         // Setup index for endContainer
15354                         if (endContainer.nodeType == 1) {
15355                                 endOffset = nodeIndex(endContainer) + 1;
15356                                 endContainer = endContainer.parentNode;
15357                         }
15358
15359                         // Return new range like object
15360                         return {
15361                                 startContainer : startContainer,
15362                                 startOffset : startOffset,
15363                                 endContainer : endContainer,
15364                                 endOffset : endOffset
15365                         };
15366                 }
15367
15368                 function removeFormat(format, vars, node, compare_node) {
15369                         var i, attrs, stylesModified;
15370
15371                         // Check if node matches format
15372                         if (!matchName(node, format))
15373                                 return FALSE;
15374
15375                         // Should we compare with format attribs and styles
15376                         if (format.remove != 'all') {
15377                                 // Remove styles
15378                                 each(format.styles, function(value, name) {
15379                                         value = replaceVars(value, vars);
15380
15381                                         // Indexed array
15382                                         if (typeof(name) === 'number') {
15383                                                 name = value;
15384                                                 compare_node = 0;
15385                                         }
15386
15387                                         if (!compare_node || isEq(getStyle(compare_node, name), value))
15388                                                 dom.setStyle(node, name, '');
15389
15390                                         stylesModified = 1;
15391                                 });
15392
15393                                 // Remove style attribute if it's empty
15394                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {
15395                                         node.removeAttribute('style');
15396                                         node.removeAttribute('data-mce-style');
15397                                 }
15398
15399                                 // Remove attributes
15400                                 each(format.attributes, function(value, name) {
15401                                         var valueOut;
15402
15403                                         value = replaceVars(value, vars);
15404
15405                                         // Indexed array
15406                                         if (typeof(name) === 'number') {
15407                                                 name = value;
15408                                                 compare_node = 0;
15409                                         }
15410
15411                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15412                                                 // Keep internal classes
15413                                                 if (name == 'class') {
15414                                                         value = dom.getAttrib(node, name);
15415                                                         if (value) {
15416                                                                 // Build new class value where everything is removed except the internal prefixed classes
15417                                                                 valueOut = '';
15418                                                                 each(value.split(/\s+/), function(cls) {
15419                                                                         if (/mce\w+/.test(cls))
15420                                                                                 valueOut += (valueOut ? ' ' : '') + cls;
15421                                                                 });
15422
15423                                                                 // We got some internal classes left
15424                                                                 if (valueOut) {
15425                                                                         dom.setAttrib(node, name, valueOut);
15426                                                                         return;
15427                                                                 }
15428                                                         }
15429                                                 }
15430
15431                                                 // IE6 has a bug where the attribute doesn't get removed correctly
15432                                                 if (name == "class")
15433                                                         node.removeAttribute('className');
15434
15435                                                 // Remove mce prefixed attributes
15436                                                 if (MCE_ATTR_RE.test(name))
15437                                                         node.removeAttribute('data-mce-' + name);
15438
15439                                                 node.removeAttribute(name);
15440                                         }
15441                                 });
15442
15443                                 // Remove classes
15444                                 each(format.classes, function(value) {
15445                                         value = replaceVars(value, vars);
15446
15447                                         if (!compare_node || dom.hasClass(compare_node, value))
15448                                                 dom.removeClass(node, value);
15449                                 });
15450
15451                                 // Check for non internal attributes
15452                                 attrs = dom.getAttribs(node);
15453                                 for (i = 0; i < attrs.length; i++) {
15454                                         if (attrs[i].nodeName.indexOf('_') !== 0)
15455                                                 return FALSE;
15456                                 }
15457                         }
15458
15459                         // Remove the inline child if it's empty for example <b> or <span>
15460                         if (format.remove != 'none') {
15461                                 removeNode(node, format);
15462                                 return TRUE;
15463                         }
15464                 };
15465
15466                 function removeNode(node, format) {
15467                         var parentNode = node.parentNode, rootBlockElm;
15468
15469                         if (format.block) {
15470                                 if (!forcedRootBlock) {
15471                                         function find(node, next, inc) {
15472                                                 node = getNonWhiteSpaceSibling(node, next, inc);
15473
15474                                                 return !node || (node.nodeName == 'BR' || isBlock(node));
15475                                         };
15476
15477                                         // Append BR elements if needed before we remove the block
15478                                         if (isBlock(node) && !isBlock(parentNode)) {
15479                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
15480                                                         node.insertBefore(dom.create('br'), node.firstChild);
15481
15482                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
15483                                                         node.appendChild(dom.create('br'));
15484                                         }
15485                                 } else {
15486                                         // Wrap the block in a forcedRootBlock if we are at the root of document
15487                                         if (parentNode == dom.getRoot()) {
15488                                                 if (!format.list_block || !isEq(node, format.list_block)) {
15489                                                         each(tinymce.grep(node.childNodes), function(node) {
15490                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15491                                                                         if (!rootBlockElm)
15492                                                                                 rootBlockElm = wrap(node, forcedRootBlock);
15493                                                                         else
15494                                                                                 rootBlockElm.appendChild(node);
15495                                                                 } else
15496                                                                         rootBlockElm = 0;
15497                                                         });
15498                                                 }
15499                                         }
15500                                 }
15501                         }
15502
15503                         // Never remove nodes that isn't the specified inline element if a selector is specified too
15504                         if (format.selector && format.inline && !isEq(format.inline, node))
15505                                 return;
15506
15507                         dom.remove(node, 1);
15508                 };
15509
15510                 function getNonWhiteSpaceSibling(node, next, inc) {
15511                         if (node) {
15512                                 next = next ? 'nextSibling' : 'previousSibling';
15513
15514                                 for (node = inc ? node : node[next]; node; node = node[next]) {
15515                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))
15516                                                 return node;
15517                                 }
15518                         }
15519                 };
15520
15521                 function isBookmarkNode(node) {
15522                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
15523                 };
15524
15525                 function mergeSiblings(prev, next) {
15526                         var marker, sibling, tmpSibling;
15527
15528                         function compareElements(node1, node2) {
15529                                 // Not the same name
15530                                 if (node1.nodeName != node2.nodeName)
15531                                         return FALSE;
15532
15533                                 function getAttribs(node) {
15534                                         var attribs = {};
15535
15536                                         each(dom.getAttribs(node), function(attr) {
15537                                                 var name = attr.nodeName.toLowerCase();
15538
15539                                                 // Don't compare internal attributes or style
15540                                                 if (name.indexOf('_') !== 0 && name !== 'style')
15541                                                         attribs[name] = dom.getAttrib(node, name);
15542                                         });
15543
15544                                         return attribs;
15545                                 };
15546
15547                                 function compareObjects(obj1, obj2) {
15548                                         var value, name;
15549
15550                                         for (name in obj1) {
15551                                                 // Obj1 has item obj2 doesn't have
15552                                                 if (obj1.hasOwnProperty(name)) {
15553                                                         value = obj2[name];
15554
15555                                                         // Obj2 doesn't have obj1 item
15556                                                         if (value === undefined)
15557                                                                 return FALSE;
15558
15559                                                         // Obj2 item has a different value
15560                                                         if (obj1[name] != value)
15561                                                                 return FALSE;
15562
15563                                                         // Delete similar value
15564                                                         delete obj2[name];
15565                                                 }
15566                                         }
15567
15568                                         // Check if obj 2 has something obj 1 doesn't have
15569                                         for (name in obj2) {
15570                                                 // Obj2 has item obj1 doesn't have
15571                                                 if (obj2.hasOwnProperty(name))
15572                                                         return FALSE;
15573                                         }
15574
15575                                         return TRUE;
15576                                 };
15577
15578                                 // Attribs are not the same
15579                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
15580                                         return FALSE;
15581
15582                                 // Styles are not the same
15583                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
15584                                         return FALSE;
15585
15586                                 return TRUE;
15587                         };
15588
15589                         // Check if next/prev exists and that they are elements
15590                         if (prev && next) {
15591                                 function findElementSibling(node, sibling_name) {
15592                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {
15593                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
15594                                                         return node;
15595
15596                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15597                                                         return sibling;
15598                                         }
15599
15600                                         return node;
15601                                 };
15602
15603                                 // If previous sibling is empty then jump over it
15604                                 prev = findElementSibling(prev, 'previousSibling');
15605                                 next = findElementSibling(next, 'nextSibling');
15606
15607                                 // Compare next and previous nodes
15608                                 if (compareElements(prev, next)) {
15609                                         // Append nodes between
15610                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {
15611                                                 tmpSibling = sibling;
15612                                                 sibling = sibling.nextSibling;
15613                                                 prev.appendChild(tmpSibling);
15614                                         }
15615
15616                                         // Remove next node
15617                                         dom.remove(next);
15618
15619                                         // Move children into prev node
15620                                         each(tinymce.grep(next.childNodes), function(node) {
15621                                                 prev.appendChild(node);
15622                                         });
15623
15624                                         return prev;
15625                                 }
15626                         }
15627
15628                         return next;
15629                 };
15630
15631                 function isTextBlock(name) {
15632                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
15633                 };
15634
15635                 function getContainer(rng, start) {
15636                         var container, offset, lastIdx;
15637
15638                         container = rng[start ? 'startContainer' : 'endContainer'];
15639                         offset = rng[start ? 'startOffset' : 'endOffset'];
15640
15641                         if (container.nodeType == 1) {
15642                                 lastIdx = container.childNodes.length - 1;
15643
15644                                 if (!start && offset)
15645                                         offset--;
15646
15647                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
15648                         }
15649
15650                         return container;
15651                 };
15652
15653                 function performCaretAction(type, name, vars) {
15654                         var i, currentPendingFormats = pendingFormats[type],
15655                                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
15656
15657                         function hasPending() {
15658                                 return pendingFormats.apply.length || pendingFormats.remove.length;
15659                         };
15660
15661                         function resetPending() {
15662                                 pendingFormats.apply = [];
15663                                 pendingFormats.remove = [];
15664                         };
15665
15666                         function perform(caret_node) {
15667                                 // Apply pending formats
15668                                 each(pendingFormats.apply.reverse(), function(item) {
15669                                         apply(item.name, item.vars, caret_node);
15670
15671                                         // Colored nodes should be underlined so that the color of the underline matches the text color.
15672                                         if (item.name === 'forecolor' && item.vars.value)
15673                                                 processUnderlineAndColor(caret_node.parentNode);
15674                                 });
15675
15676                                 // Remove pending formats
15677                                 each(pendingFormats.remove.reverse(), function(item) {
15678                                         remove(item.name, item.vars, caret_node);
15679                                 });
15680
15681                                 dom.remove(caret_node, 1);
15682                                 resetPending();
15683                         };
15684
15685                         // Check if it already exists then ignore it
15686                         for (i = currentPendingFormats.length - 1; i >= 0; i--) {
15687                                 if (currentPendingFormats[i].name == name)
15688                                         return;
15689                         }
15690
15691                         currentPendingFormats.push({name : name, vars : vars});
15692
15693                         // Check if it's in the other type, then remove it
15694                         for (i = otherPendingFormats.length - 1; i >= 0; i--) {
15695                                 if (otherPendingFormats[i].name == name)
15696                                         otherPendingFormats.splice(i, 1);
15697                         }
15698
15699                         // Pending apply or remove formats
15700                         if (hasPending()) {
15701                                 ed.getDoc().execCommand('FontName', false, 'mceinline');
15702                                 pendingFormats.lastRng = selection.getRng();
15703
15704                                 // IE will convert the current word
15705                                 each(dom.select('font,span'), function(node) {
15706                                         var bookmark;
15707
15708                                         if (isCaretNode(node)) {
15709                                                 bookmark = selection.getBookmark();
15710                                                 perform(node);
15711                                                 selection.moveToBookmark(bookmark);
15712                                                 ed.nodeChanged();
15713                                         }
15714                                 });
15715
15716                                 // Only register listeners once if we need to
15717                                 if (!pendingFormats.isListening && hasPending()) {
15718                                         pendingFormats.isListening = true;
15719
15720                                         each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
15721                                                 ed[event].addToTop(function(ed, e) {
15722                                                         // Do we have pending formats and is the selection moved has moved
15723                                                         if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
15724                                                                 each(dom.select('font,span'), function(node) {
15725                                                                         var textNode, rng;
15726
15727                                                                         // Look for marker
15728                                                                         if (isCaretNode(node)) {
15729                                                                                 textNode = node.firstChild;
15730
15731                                                                                 if (textNode) {
15732                                                                                         perform(node);
15733
15734                                                                                         rng = dom.createRng();
15735                                                                                         rng.setStart(textNode, textNode.nodeValue.length);
15736                                                                                         rng.setEnd(textNode, textNode.nodeValue.length);
15737                                                                                         selection.setRng(rng);
15738                                                                                         ed.nodeChanged();
15739                                                                                 } else
15740                                                                                         dom.remove(node);
15741                                                                         }
15742                                                                 });
15743
15744                                                                 // Always unbind and clear pending styles on keyup
15745                                                                 if (e.type == 'keyup' || e.type == 'mouseup')
15746                                                                         resetPending();
15747                                                         }
15748                                                 });
15749                                         });
15750                                 }
15751                         }
15752                 };
15753         };
15754 })(tinymce);
15755
15756 tinymce.onAddEditor.add(function(tinymce, ed) {
15757         var filters, fontSizes, dom, settings = ed.settings;
15758
15759         if (settings.inline_styles) {
15760                 fontSizes = tinymce.explode(settings.font_size_style_values);
15761
15762                 function replaceWithSpan(node, styles) {
15763                         tinymce.each(styles, function(value, name) {
15764                                 if (value)
15765                                         dom.setStyle(node, name, value);
15766                         });
15767
15768                         dom.rename(node, 'span');
15769                 };
15770
15771                 filters = {
15772                         font : function(dom, node) {
15773                                 replaceWithSpan(node, {
15774                                         backgroundColor : node.style.backgroundColor,
15775                                         color : node.color,
15776                                         fontFamily : node.face,
15777                                         fontSize : fontSizes[parseInt(node.size) - 1]
15778                                 });
15779                         },
15780
15781                         u : function(dom, node) {
15782                                 replaceWithSpan(node, {
15783                                         textDecoration : 'underline'
15784                                 });
15785                         },
15786
15787                         strike : function(dom, node) {
15788                                 replaceWithSpan(node, {
15789                                         textDecoration : 'line-through'
15790                                 });
15791                         }
15792                 };
15793
15794                 function convert(editor, params) {
15795                         dom = editor.dom;
15796
15797                         if (settings.convert_fonts_to_spans) {
15798                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
15799                                         filters[node.nodeName.toLowerCase()](ed.dom, node);
15800                                 });
15801                         }
15802                 };
15803
15804                 ed.onPreProcess.add(convert);
15805                 ed.onSetContent.add(convert);
15806
15807                 ed.onInit.add(function() {
15808                         ed.selection.onSetContent.add(convert);
15809                 });
15810         }
15811 });
15812