]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/tiny_mce_src.js
Release 6.5.0
[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.4',
9
10                 releaseDate : '2011-08-04',
11
12                 _init : function() {
13                         var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
14
15                         t.isOpera = win.opera && opera.buildNumber;
16
17                         t.isWebKit = /WebKit/.test(ua);
18
19                         t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
20
21                         t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
22
23                         t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
24
25                         t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
26
27                         t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
28
29                         t.isGecko = !t.isWebKit && /Gecko/.test(ua);
30
31                         t.isMac = ua.indexOf('Mac') != -1;
32
33                         t.isAir = /adobeair/i.test(ua);
34
35                         t.isIDevice = /(iPad|iPhone)/.test(ua);
36                         
37                         t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
38
39                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE
40                         if (win.tinyMCEPreInit) {
41                                 t.suffix = tinyMCEPreInit.suffix;
42                                 t.baseURL = tinyMCEPreInit.base;
43                                 t.query = tinyMCEPreInit.query;
44                                 return;
45                         }
46
47                         // Get suffix and base
48                         t.suffix = '';
49
50                         // If base element found, add that infront of baseURL
51                         nl = d.getElementsByTagName('base');
52                         for (i=0; i<nl.length; i++) {
53                                 if (v = nl[i].href) {
54                                         // Host only value like http://site.com or http://site.com:8008
55                                         if (/^https?:\/\/[^\/]+$/.test(v))
56                                                 v += '/';
57
58                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory
59                                 }
60                         }
61
62                         function getBase(n) {
63                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
64                                         if (/_(src|dev)\.js/g.test(n.src))
65                                                 t.suffix = '_src';
66
67                                         if ((p = n.src.indexOf('?')) != -1)
68                                                 t.query = n.src.substring(p + 1);
69
70                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
71
72                                         // If path to script is relative and a base href was found add that one infront
73                                         // the src property will always be an absolute one on non IE browsers and IE 8
74                                         // so this logic will basically only be executed on older IE versions
75                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
76                                                 t.baseURL = base + t.baseURL;
77
78                                         return t.baseURL;
79                                 }
80
81                                 return null;
82                         };
83
84                         // Check document
85                         nl = d.getElementsByTagName('script');
86                         for (i=0; i<nl.length; i++) {
87                                 if (getBase(nl[i]))
88                                         return;
89                         }
90
91                         // Check head
92                         n = d.getElementsByTagName('head')[0];
93                         if (n) {
94                                 nl = n.getElementsByTagName('script');
95                                 for (i=0; i<nl.length; i++) {
96                                         if (getBase(nl[i]))
97                                                 return;
98                                 }
99                         }
100
101                         return;
102                 },
103
104                 is : function(o, t) {
105                         if (!t)
106                                 return o !== undefined;
107
108                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
109                                 return true;
110
111                         return typeof(o) == t;
112                 },
113
114                 makeMap : function(items, delim, map) {
115                         var i;
116
117                         items = items || [];
118                         delim = delim || ',';
119
120                         if (typeof(items) == "string")
121                                 items = items.split(delim);
122
123                         map = map || {};
124
125                         i = items.length;
126                         while (i--)
127                                 map[items[i]] = {};
128
129                         return map;
130                 },
131
132                 each : function(o, cb, s) {
133                         var n, l;
134
135                         if (!o)
136                                 return 0;
137
138                         s = s || o;
139
140                         if (o.length !== undefined) {
141                                 // Indexed arrays, needed for Safari
142                                 for (n=0, l = o.length; n < l; n++) {
143                                         if (cb.call(s, o[n], n, o) === false)
144                                                 return 0;
145                                 }
146                         } else {
147                                 // Hashtables
148                                 for (n in o) {
149                                         if (o.hasOwnProperty(n)) {
150                                                 if (cb.call(s, o[n], n, o) === false)
151                                                         return 0;
152                                         }
153                                 }
154                         }
155
156                         return 1;
157                 },
158
159
160                 map : function(a, f) {
161                         var o = [];
162
163                         tinymce.each(a, function(v) {
164                                 o.push(f(v));
165                         });
166
167                         return o;
168                 },
169
170                 grep : function(a, f) {
171                         var o = [];
172
173                         tinymce.each(a, function(v) {
174                                 if (!f || f(v))
175                                         o.push(v);
176                         });
177
178                         return o;
179                 },
180
181                 inArray : function(a, v) {
182                         var i, l;
183
184                         if (a) {
185                                 for (i = 0, l = a.length; i < l; i++) {
186                                         if (a[i] === v)
187                                                 return i;
188                                 }
189                         }
190
191                         return -1;
192                 },
193
194                 extend : function(o, e) {
195                         var i, l, a = arguments;
196
197                         for (i = 1, l = a.length; i < l; i++) {
198                                 e = a[i];
199
200                                 tinymce.each(e, function(v, n) {
201                                         if (v !== undefined)
202                                                 o[n] = v;
203                                 });
204                         }
205
206                         return o;
207                 },
208
209
210                 trim : function(s) {
211                         return (s ? '' + s : '').replace(whiteSpaceRe, '');
212                 },
213
214                 create : function(s, p, root) {
215                         var t = this, sp, ns, cn, scn, c, de = 0;
216
217                         // Parse : <prefix> <class>:<super class>
218                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
219                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
220
221                         // Create namespace for new class
222                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
223
224                         // Class already exists
225                         if (ns[cn])
226                                 return;
227
228                         // Make pure static class
229                         if (s[2] == 'static') {
230                                 ns[cn] = p;
231
232                                 if (this.onCreate)
233                                         this.onCreate(s[2], s[3], ns[cn]);
234
235                                 return;
236                         }
237
238                         // Create default constructor
239                         if (!p[cn]) {
240                                 p[cn] = function() {};
241                                 de = 1;
242                         }
243
244                         // Add constructor and methods
245                         ns[cn] = p[cn];
246                         t.extend(ns[cn].prototype, p);
247
248                         // Extend
249                         if (s[5]) {
250                                 sp = t.resolve(s[5]).prototype;
251                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
252
253                                 // Extend constructor
254                                 c = ns[cn];
255                                 if (de) {
256                                         // Add passthrough constructor
257                                         ns[cn] = function() {
258                                                 return sp[scn].apply(this, arguments);
259                                         };
260                                 } else {
261                                         // Add inherit constructor
262                                         ns[cn] = function() {
263                                                 this.parent = sp[scn];
264                                                 return c.apply(this, arguments);
265                                         };
266                                 }
267                                 ns[cn].prototype[cn] = ns[cn];
268
269                                 // Add super methods
270                                 t.each(sp, function(f, n) {
271                                         ns[cn].prototype[n] = sp[n];
272                                 });
273
274                                 // Add overridden methods
275                                 t.each(p, function(f, n) {
276                                         // Extend methods if needed
277                                         if (sp[n]) {
278                                                 ns[cn].prototype[n] = function() {
279                                                         this.parent = sp[n];
280                                                         return f.apply(this, arguments);
281                                                 };
282                                         } else {
283                                                 if (n != cn)
284                                                         ns[cn].prototype[n] = f;
285                                         }
286                                 });
287                         }
288
289                         // Add static methods
290                         t.each(p['static'], function(f, n) {
291                                 ns[cn][n] = f;
292                         });
293
294                         if (this.onCreate)
295                                 this.onCreate(s[2], s[3], ns[cn].prototype);
296                 },
297
298                 walk : function(o, f, n, s) {
299                         s = s || this;
300
301                         if (o) {
302                                 if (n)
303                                         o = o[n];
304
305                                 tinymce.each(o, function(o, i) {
306                                         if (f.call(s, o, i, n) === false)
307                                                 return false;
308
309                                         tinymce.walk(o, f, n, s);
310                                 });
311                         }
312                 },
313
314                 createNS : function(n, o) {
315                         var i, v;
316
317                         o = o || win;
318
319                         n = n.split('.');
320                         for (i=0; i<n.length; i++) {
321                                 v = n[i];
322
323                                 if (!o[v])
324                                         o[v] = {};
325
326                                 o = o[v];
327                         }
328
329                         return o;
330                 },
331
332                 resolve : function(n, o) {
333                         var i, l;
334
335                         o = o || win;
336
337                         n = n.split('.');
338                         for (i = 0, l = n.length; i < l; i++) {
339                                 o = o[n[i]];
340
341                                 if (!o)
342                                         break;
343                         }
344
345                         return o;
346                 },
347
348                 addUnload : function(f, s) {
349                         var t = this;
350
351                         f = {func : f, scope : s || this};
352
353                         if (!t.unloads) {
354                                 function unload() {
355                                         var li = t.unloads, o, n;
356
357                                         if (li) {
358                                                 // Call unload handlers
359                                                 for (n in li) {
360                                                         o = li[n];
361
362                                                         if (o && o.func)
363                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
364                                                 }
365
366                                                 // Detach unload function
367                                                 if (win.detachEvent) {
368                                                         win.detachEvent('onbeforeunload', fakeUnload);
369                                                         win.detachEvent('onunload', unload);
370                                                 } else if (win.removeEventListener)
371                                                         win.removeEventListener('unload', unload, false);
372
373                                                 // Destroy references
374                                                 t.unloads = o = li = w = unload = 0;
375
376                                                 // Run garbarge collector on IE
377                                                 if (win.CollectGarbage)
378                                                         CollectGarbage();
379                                         }
380                                 };
381
382                                 function fakeUnload() {
383                                         var d = document;
384
385                                         // Is there things still loading, then do some magic
386                                         if (d.readyState == 'interactive') {
387                                                 function stop() {
388                                                         // Prevent memory leak
389                                                         d.detachEvent('onstop', stop);
390
391                                                         // Call unload handler
392                                                         if (unload)
393                                                                 unload();
394
395                                                         d = 0;
396                                                 };
397
398                                                 // Fire unload when the currently loading page is stopped
399                                                 if (d)
400                                                         d.attachEvent('onstop', stop);
401
402                                                 // Remove onstop listener after a while to prevent the unload function
403                                                 // to execute if the user presses cancel in an onbeforeunload
404                                                 // confirm dialog and then presses the browser stop button
405                                                 win.setTimeout(function() {
406                                                         if (d)
407                                                                 d.detachEvent('onstop', stop);
408                                                 }, 0);
409                                         }
410                                 };
411
412                                 // Attach unload handler
413                                 if (win.attachEvent) {
414                                         win.attachEvent('onunload', unload);
415                                         win.attachEvent('onbeforeunload', fakeUnload);
416                                 } else if (win.addEventListener)
417                                         win.addEventListener('unload', unload, false);
418
419                                 // Setup initial unload handler array
420                                 t.unloads = [f];
421                         } else
422                                 t.unloads.push(f);
423
424                         return f;
425                 },
426
427                 removeUnload : function(f) {
428                         var u = this.unloads, r = null;
429
430                         tinymce.each(u, function(o, i) {
431                                 if (o && o.func == f) {
432                                         u.splice(i, 1);
433                                         r = f;
434                                         return false;
435                                 }
436                         });
437
438                         return r;
439                 },
440
441                 explode : function(s, d) {
442                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
443                 },
444
445                 _addVer : function(u) {
446                         var v;
447
448                         if (!this.query)
449                                 return u;
450
451                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
452
453                         if (u.indexOf('#') == -1)
454                                 return u + v;
455
456                         return u.replace('#', v + '#');
457                 },
458
459                 // Fix function for IE 9 where regexps isn't working correctly
460                 // Todo: remove me once MS fixes the bug
461                 _replace : function(find, replace, str) {
462                         // On IE9 we have to fake $x replacement
463                         if (isRegExpBroken) {
464                                 return str.replace(find, function() {
465                                         var val = replace, args = arguments, i;
466
467                                         for (i = 0; i < args.length - 2; i++) {
468                                                 if (args[i] === undefined) {
469                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');
470                                                 } else {
471                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
472                                                 }
473                                         }
474
475                                         return val;
476                                 });
477                         }
478
479                         return str.replace(find, replace);
480                 }
481
482                 };
483
484         // Initialize the API
485         tinymce._init();
486
487         // Expose tinymce namespace to the global namespace (window)
488         win.tinymce = win.tinyMCE = tinymce;
489
490         // Describe the different namespaces
491
492         })(window);
493
494
495
496 tinymce.create('tinymce.util.Dispatcher', {
497         scope : null,
498         listeners : null,
499
500         Dispatcher : function(s) {
501                 this.scope = s || this;
502                 this.listeners = [];
503         },
504
505         add : function(cb, s) {
506                 this.listeners.push({cb : cb, scope : s || this.scope});
507
508                 return cb;
509         },
510
511         addToTop : function(cb, s) {
512                 this.listeners.unshift({cb : cb, scope : s || this.scope});
513
514                 return cb;
515         },
516
517         remove : function(cb) {
518                 var l = this.listeners, o = null;
519
520                 tinymce.each(l, function(c, i) {
521                         if (cb == c.cb) {
522                                 o = cb;
523                                 l.splice(i, 1);
524                                 return false;
525                         }
526                 });
527
528                 return o;
529         },
530
531         dispatch : function() {
532                 var s, a = arguments, i, li = this.listeners, c;
533
534                 // Needs to be a real loop since the listener count might change while looping
535                 // And this is also more efficient
536                 for (i = 0; i<li.length; i++) {
537                         c = li[i];
538                         s = c.cb.apply(c.scope, a);
539
540                         if (s === false)
541                                 break;
542                 }
543
544                 return s;
545         }
546
547         });
548
549 (function() {
550         var each = tinymce.each;
551
552         tinymce.create('tinymce.util.URI', {
553                 URI : function(u, s) {
554                         var t = this, o, a, b, base_url;
555
556                         // Trim whitespace
557                         u = tinymce.trim(u);
558
559                         // Default settings
560                         s = t.settings = s || {};
561
562                         // Strange app protocol or local anchor
563                         if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
564                                 t.source = u;
565                                 return;
566                         }
567
568                         // Absolute path with no host, fake host and protocol
569                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
570                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
571
572                         // Relative path http:// or protocol relative //path
573                         if (!/^[\w-]*:?\/\//.test(u)) {
574                                 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
575                                 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
576                         }
577
578                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
579                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
580                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
581                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
582                                 var s = u[i];
583
584                                 // Zope 3 workaround, they use @@something
585                                 if (s)
586                                         s = s.replace(/\(mce_at\)/g, '@@');
587
588                                 t[v] = s;
589                         });
590
591                         if (b = s.base_uri) {
592                                 if (!t.protocol)
593                                         t.protocol = b.protocol;
594
595                                 if (!t.userInfo)
596                                         t.userInfo = b.userInfo;
597
598                                 if (!t.port && t.host == 'mce_host')
599                                         t.port = b.port;
600
601                                 if (!t.host || t.host == 'mce_host')
602                                         t.host = b.host;
603
604                                 t.source = '';
605                         }
606
607                         //t.path = t.path || '/';
608                 },
609
610                 setPath : function(p) {
611                         var t = this;
612
613                         p = /^(.*?)\/?(\w+)?$/.exec(p);
614
615                         // Update path parts
616                         t.path = p[0];
617                         t.directory = p[1];
618                         t.file = p[2];
619
620                         // Rebuild source
621                         t.source = '';
622                         t.getURI();
623                 },
624
625                 toRelative : function(u) {
626                         var t = this, o;
627
628                         if (u === "./")
629                                 return u;
630
631                         u = new tinymce.util.URI(u, {base_uri : t});
632
633                         // Not on same domain/port or protocol
634                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
635                                 return u.getURI();
636
637                         o = t.toRelPath(t.path, u.path);
638
639                         // Add query
640                         if (u.query)
641                                 o += '?' + u.query;
642
643                         // Add anchor
644                         if (u.anchor)
645                                 o += '#' + u.anchor;
646
647                         return o;
648                 },
649         
650                 toAbsolute : function(u, nh) {
651                         var u = new tinymce.util.URI(u, {base_uri : this});
652
653                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
654                 },
655
656                 toRelPath : function(base, path) {
657                         var items, bp = 0, out = '', i, l;
658
659                         // Split the paths
660                         base = base.substring(0, base.lastIndexOf('/'));
661                         base = base.split('/');
662                         items = path.split('/');
663
664                         if (base.length >= items.length) {
665                                 for (i = 0, l = base.length; i < l; i++) {
666                                         if (i >= items.length || base[i] != items[i]) {
667                                                 bp = i + 1;
668                                                 break;
669                                         }
670                                 }
671                         }
672
673                         if (base.length < items.length) {
674                                 for (i = 0, l = items.length; i < l; i++) {
675                                         if (i >= base.length || base[i] != items[i]) {
676                                                 bp = i + 1;
677                                                 break;
678                                         }
679                                 }
680                         }
681
682                         if (bp == 1)
683                                 return path;
684
685                         for (i = 0, l = base.length - (bp - 1); i < l; i++)
686                                 out += "../";
687
688                         for (i = bp - 1, l = items.length; i < l; i++) {
689                                 if (i != bp - 1)
690                                         out += "/" + items[i];
691                                 else
692                                         out += items[i];
693                         }
694
695                         return out;
696                 },
697
698                 toAbsPath : function(base, path) {
699                         var i, nb = 0, o = [], tr, outPath;
700
701                         // Split paths
702                         tr = /\/$/.test(path) ? '/' : '';
703                         base = base.split('/');
704                         path = path.split('/');
705
706                         // Remove empty chunks
707                         each(base, function(k) {
708                                 if (k)
709                                         o.push(k);
710                         });
711
712                         base = o;
713
714                         // Merge relURLParts chunks
715                         for (i = path.length - 1, o = []; i >= 0; i--) {
716                                 // Ignore empty or .
717                                 if (path[i].length == 0 || path[i] == ".")
718                                         continue;
719
720                                 // Is parent
721                                 if (path[i] == '..') {
722                                         nb++;
723                                         continue;
724                                 }
725
726                                 // Move up
727                                 if (nb > 0) {
728                                         nb--;
729                                         continue;
730                                 }
731
732                                 o.push(path[i]);
733                         }
734
735                         i = base.length - nb;
736
737                         // If /a/b/c or /
738                         if (i <= 0)
739                                 outPath = o.reverse().join('/');
740                         else
741                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
742
743                         // Add front / if it's needed
744                         if (outPath.indexOf('/') !== 0)
745                                 outPath = '/' + outPath;
746
747                         // Add traling / if it's needed
748                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
749                                 outPath += tr;
750
751                         return outPath;
752                 },
753
754                 getURI : function(nh) {
755                         var s, t = this;
756
757                         // Rebuild source
758                         if (!t.source || nh) {
759                                 s = '';
760
761                                 if (!nh) {
762                                         if (t.protocol)
763                                                 s += t.protocol + '://';
764
765                                         if (t.userInfo)
766                                                 s += t.userInfo + '@';
767
768                                         if (t.host)
769                                                 s += t.host;
770
771                                         if (t.port)
772                                                 s += ':' + t.port;
773                                 }
774
775                                 if (t.path)
776                                         s += t.path;
777
778                                 if (t.query)
779                                         s += '?' + t.query;
780
781                                 if (t.anchor)
782                                         s += '#' + t.anchor;
783
784                                 t.source = s;
785                         }
786
787                         return t.source;
788                 }
789         });
790 })();
791
792 (function() {
793         var each = tinymce.each;
794
795         tinymce.create('static tinymce.util.Cookie', {
796                 getHash : function(n) {
797                         var v = this.get(n), h;
798
799                         if (v) {
800                                 each(v.split('&'), function(v) {
801                                         v = v.split('=');
802                                         h = h || {};
803                                         h[unescape(v[0])] = unescape(v[1]);
804                                 });
805                         }
806
807                         return h;
808                 },
809
810                 setHash : function(n, v, e, p, d, s) {
811                         var o = '';
812
813                         each(v, function(v, k) {
814                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
815                         });
816
817                         this.set(n, o, e, p, d, s);
818                 },
819
820                 get : function(n) {
821                         var c = document.cookie, e, p = n + "=", b;
822
823                         // Strict mode
824                         if (!c)
825                                 return;
826
827                         b = c.indexOf("; " + p);
828
829                         if (b == -1) {
830                                 b = c.indexOf(p);
831
832                                 if (b != 0)
833                                         return null;
834                         } else
835                                 b += 2;
836
837                         e = c.indexOf(";", b);
838
839                         if (e == -1)
840                                 e = c.length;
841
842                         return unescape(c.substring(b + p.length, e));
843                 },
844
845                 set : function(n, v, e, p, d, s) {
846                         document.cookie = n + "=" + escape(v) +
847                                 ((e) ? "; expires=" + e.toGMTString() : "") +
848                                 ((p) ? "; path=" + escape(p) : "") +
849                                 ((d) ? "; domain=" + d : "") +
850                                 ((s) ? "; secure" : "");
851                 },
852
853                 remove : function(n, p) {
854                         var d = new Date();
855
856                         d.setTime(d.getTime() - 1000);
857
858                         this.set(n, '', d, p, d);
859                 }
860         });
861 })();
862
863 (function() {
864         function serialize(o, quote) {
865                 var i, v, t;
866
867                 quote = quote || '"';
868
869                 if (o == null)
870                         return 'null';
871
872                 t = typeof o;
873
874                 if (t == 'string') {
875                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
876
877                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
878                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
879                                 if (quote === '"' && a === "'")
880                                         return a;
881
882                                 i = v.indexOf(b);
883
884                                 if (i + 1)
885                                         return '\\' + v.charAt(i + 1);
886
887                                 a = b.charCodeAt().toString(16);
888
889                                 return '\\u' + '0000'.substring(a.length) + a;
890                         }) + quote;
891                 }
892
893                 if (t == 'object') {
894                         if (o.hasOwnProperty && o instanceof Array) {
895                                         for (i=0, v = '['; i<o.length; i++)
896                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
897
898                                         return v + ']';
899                                 }
900
901                                 v = '{';
902
903                                 for (i in o)
904                                         v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
905
906                                 return v + '}';
907                 }
908
909                 return '' + o;
910         };
911
912         tinymce.util.JSON = {
913                 serialize: serialize,
914
915                 parse: function(s) {
916                         try {
917                                 return eval('(' + s + ')');
918                         } catch (ex) {
919                                 // Ignore
920                         }
921                 }
922
923                 };
924 })();
925 tinymce.create('static tinymce.util.XHR', {
926         send : function(o) {
927                 var x, t, w = window, c = 0;
928
929                 // Default settings
930                 o.scope = o.scope || this;
931                 o.success_scope = o.success_scope || o.scope;
932                 o.error_scope = o.error_scope || o.scope;
933                 o.async = o.async === false ? false : true;
934                 o.data = o.data || '';
935
936                 function get(s) {
937                         x = 0;
938
939                         try {
940                                 x = new ActiveXObject(s);
941                         } catch (ex) {
942                         }
943
944                         return x;
945                 };
946
947                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
948
949                 if (x) {
950                         if (x.overrideMimeType)
951                                 x.overrideMimeType(o.content_type);
952
953                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
954
955                         if (o.content_type)
956                                 x.setRequestHeader('Content-Type', o.content_type);
957
958                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
959
960                         x.send(o.data);
961
962                         function ready() {
963                                 if (!o.async || x.readyState == 4 || c++ > 10000) {
964                                         if (o.success && c < 10000 && x.status == 200)
965                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);
966                                         else if (o.error)
967                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
968
969                                         x = null;
970                                 } else
971                                         w.setTimeout(ready, 10);
972                         };
973
974                         // Syncronous request
975                         if (!o.async)
976                                 return ready();
977
978                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
979                         t = w.setTimeout(ready, 10);
980                 }
981         }
982 });
983
984 (function() {
985         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
986
987         tinymce.create('tinymce.util.JSONRequest', {
988                 JSONRequest : function(s) {
989                         this.settings = extend({
990                         }, s);
991                         this.count = 0;
992                 },
993
994                 send : function(o) {
995                         var ecb = o.error, scb = o.success;
996
997                         o = extend(this.settings, o);
998
999                         o.success = function(c, x) {
1000                                 c = JSON.parse(c);
1001
1002                                 if (typeof(c) == 'undefined') {
1003                                         c = {
1004                                                 error : 'JSON Parse error.'
1005                                         };
1006                                 }
1007
1008                                 if (c.error)
1009                                         ecb.call(o.error_scope || o.scope, c.error, x);
1010                                 else
1011                                         scb.call(o.success_scope || o.scope, c.result);
1012                         };
1013
1014                         o.error = function(ty, x) {
1015                                 if (ecb)
1016                                         ecb.call(o.error_scope || o.scope, ty, x);
1017                         };
1018
1019                         o.data = JSON.serialize({
1020                                 id : o.id || 'c' + (this.count++),
1021                                 method : o.method,
1022                                 params : o.params
1023                         });
1024
1025                         // JSON content type for Ruby on rails. Bug: #1883287
1026                         o.content_type = 'application/json';
1027
1028                         XHR.send(o);
1029                 },
1030
1031                 'static' : {
1032                         sendRPC : function(o) {
1033                                 return new tinymce.util.JSONRequest().send(o);
1034                         }
1035                 }
1036         });
1037 }());
1038 (function(tinymce){
1039         tinymce.VK = {
1040                 DELETE:46,
1041                 BACKSPACE:8
1042                 
1043         }
1044
1045 })(tinymce);
1046
1047 (function(tinymce) {
1048         function cleanupStylesWhenDeleting(ed) {
1049                 var dom = ed.dom, selection = ed.selection, VK= tinymce.VK;
1050                         ed.onKeyUp.add(function(ed, e) {
1051                                 if (e.keyCode == VK.DELETE ||e.keyCode == VK.BACKSPACE) {
1052                                         var startContainer = selection.getRng().startContainer;
1053                                         var blockElement = startContainer;
1054                                         while (!dom.isBlock(blockElement)) {
1055                                                 blockElement = blockElement.parentNode;
1056                                         }
1057                                         var spans = dom.select("span.Apple-style-span", blockElement);
1058                                         dom.remove(spans, true);
1059                                 }
1060                         });
1061         }
1062
1063         tinymce.create('tinymce.util.Quirks', {
1064                 Quirks: function(ed) {
1065                         if (tinymce.isWebKit) {
1066                                 cleanupStylesWhenDeleting(ed);
1067                         }
1068                                                 
1069                 }
1070         });
1071 })(tinymce);    
1072
1073 (function(tinymce) {
1074         var namedEntities, baseEntities, reverseEntities,
1075                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1076                 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1077                 rawCharsRegExp = /[<>&\"\']/g,
1078                 entityRegExp = /&(#x|#)?([\w]+);/g,
1079                 asciiMap = {
1080                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1081                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1082                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1083                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1084                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1085                 };
1086
1087         // Raw entities
1088         baseEntities = {
1089                 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
1090                 "'" : '&#39;',
1091                 '<' : '&lt;',
1092                 '>' : '&gt;',
1093                 '&' : '&amp;'
1094         };
1095
1096         // Reverse lookup table for raw entities
1097         reverseEntities = {
1098                 '&lt;' : '<',
1099                 '&gt;' : '>',
1100                 '&amp;' : '&',
1101                 '&quot;' : '"',
1102                 '&apos;' : "'"
1103         };
1104
1105         // Decodes text by using the browser
1106         function nativeDecode(text) {
1107                 var elm;
1108
1109                 elm = document.createElement("div");
1110                 elm.innerHTML = text;
1111
1112                 return elm.textContent || elm.innerText || text;
1113         };
1114
1115         // Build a two way lookup table for the entities
1116         function buildEntitiesLookup(items, radix) {
1117                 var i, chr, entity, lookup = {};
1118
1119                 if (items) {
1120                         items = items.split(',');
1121                         radix = radix || 10;
1122
1123                         // Build entities lookup table
1124                         for (i = 0; i < items.length; i += 2) {
1125                                 chr = String.fromCharCode(parseInt(items[i], radix));
1126
1127                                 // Only add non base entities
1128                                 if (!baseEntities[chr]) {
1129                                         entity = '&' + items[i + 1] + ';';
1130                                         lookup[chr] = entity;
1131                                         lookup[entity] = chr;
1132                                 }
1133                         }
1134
1135                         return lookup;
1136                 }
1137         };
1138
1139         // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1140         namedEntities = buildEntitiesLookup(
1141                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1142                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1143                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1144                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1145                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1146                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1147                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1148                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1149                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1150                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1151                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1152                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1153                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1154                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1155                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1156                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1157                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1158                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1159                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1160                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1161                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1162                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1163                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1164                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1165                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1166         , 32);
1167
1168         tinymce.html = tinymce.html || {};
1169
1170         tinymce.html.Entities = {
1171                 encodeRaw : function(text, attr) {
1172                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1173                                 return baseEntities[chr] || chr;
1174                         });
1175                 },
1176
1177                 encodeAllRaw : function(text) {
1178                         return ('' + text).replace(rawCharsRegExp, function(chr) {
1179                                 return baseEntities[chr] || chr;
1180                         });
1181                 },
1182
1183                 encodeNumeric : function(text, attr) {
1184                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1185                                 // Multi byte sequence convert it to a single entity
1186                                 if (chr.length > 1)
1187                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1188
1189                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1190                         });
1191                 },
1192
1193                 encodeNamed : function(text, attr, entities) {
1194                         entities = entities || namedEntities;
1195
1196                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1197                                 return baseEntities[chr] || entities[chr] || chr;
1198                         });
1199                 },
1200
1201                 getEncodeFunc : function(name, entities) {
1202                         var Entities = tinymce.html.Entities;
1203
1204                         entities = buildEntitiesLookup(entities) || namedEntities;
1205
1206                         function encodeNamedAndNumeric(text, attr) {
1207                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1208                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1209                                 });
1210                         };
1211
1212                         function encodeCustomNamed(text, attr) {
1213                                 return Entities.encodeNamed(text, attr, entities);
1214                         };
1215
1216                         // Replace + with , to be compatible with previous TinyMCE versions
1217                         name = tinymce.makeMap(name.replace(/\+/g, ','));
1218
1219                         // Named and numeric encoder
1220                         if (name.named && name.numeric)
1221                                 return encodeNamedAndNumeric;
1222
1223                         // Named encoder
1224                         if (name.named) {
1225                                 // Custom names
1226                                 if (entities)
1227                                         return encodeCustomNamed;
1228
1229                                 return Entities.encodeNamed;
1230                         }
1231
1232                         // Numeric
1233                         if (name.numeric)
1234                                 return Entities.encodeNumeric;
1235
1236                         // Raw encoder
1237                         return Entities.encodeRaw;
1238                 },
1239
1240                 decode : function(text) {
1241                         return text.replace(entityRegExp, function(all, numeric, value) {
1242                                 if (numeric) {
1243                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);
1244
1245                                         // Support upper UTF
1246                                         if (value > 0xFFFF) {
1247                                                 value -= 0x10000;
1248
1249                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1250                                         } else
1251                                                 return asciiMap[value] || String.fromCharCode(value);
1252                                 }
1253
1254                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1255                         });
1256                 }
1257         };
1258 })(tinymce);
1259
1260 tinymce.html.Styles = function(settings, schema) {
1261         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1262                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1263                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1264                 trimRightRegExp = /\s+$/,
1265                 urlColorRegExp = /rgb/,
1266                 undef, i, encodingLookup = {}, encodingItems;
1267
1268         settings = settings || {};
1269
1270         encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1271         for (i = 0; i < encodingItems.length; i++) {
1272                 encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1273                 encodingLookup['\uFEFF' + i] = encodingItems[i];
1274         }
1275
1276         function toHex(match, r, g, b) {
1277                 function hex(val) {
1278                         val = parseInt(val).toString(16);
1279
1280                         return val.length > 1 ? val : '0' + val; // 0 -> 00
1281                 };
1282
1283                 return '#' + hex(r) + hex(g) + hex(b);
1284         };
1285
1286         return {
1287                 toHex : function(color) {
1288                         return color.replace(rgbRegExp, toHex);
1289                 },
1290
1291                 parse : function(css) {
1292                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1293
1294                         function compress(prefix, suffix) {
1295                                 var top, right, bottom, left;
1296
1297                                 // Get values and check it it needs compressing
1298                                 top = styles[prefix + '-top' + suffix];
1299                                 if (!top)
1300                                         return;
1301
1302                                 right = styles[prefix + '-right' + suffix];
1303                                 if (top != right)
1304                                         return;
1305
1306                                 bottom = styles[prefix + '-bottom' + suffix];
1307                                 if (right != bottom)
1308                                         return;
1309
1310                                 left = styles[prefix + '-left' + suffix];
1311                                 if (bottom != left)
1312                                         return;
1313
1314                                 // Compress
1315                                 styles[prefix + suffix] = left;
1316                                 delete styles[prefix + '-top' + suffix];
1317                                 delete styles[prefix + '-right' + suffix];
1318                                 delete styles[prefix + '-bottom' + suffix];
1319                                 delete styles[prefix + '-left' + suffix];
1320                         };
1321
1322                         function canCompress(key) {
1323                                 var value = styles[key], i;
1324
1325                                 if (!value || value.indexOf(' ') < 0)
1326                                         return;
1327
1328                                 value = value.split(' ');
1329                                 i = value.length;
1330                                 while (i--) {
1331                                         if (value[i] !== value[0])
1332                                                 return false;
1333                                 }
1334
1335                                 styles[key] = value[0];
1336
1337                                 return true;
1338                         };
1339
1340                         function compress2(target, a, b, c) {
1341                                 if (!canCompress(a))
1342                                         return;
1343
1344                                 if (!canCompress(b))
1345                                         return;
1346
1347                                 if (!canCompress(c))
1348                                         return;
1349
1350                                 // Compress
1351                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1352                                 delete styles[a];
1353                                 delete styles[b];
1354                                 delete styles[c];
1355                         };
1356
1357                         // Encodes the specified string by replacing all \" \' ; : with _<num>
1358                         function encode(str) {
1359                                 isEncoded = true;
1360
1361                                 return encodingLookup[str];
1362                         };
1363
1364                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1365                         // It will also decode the \" \' if keep_slashes is set to fale or omitted
1366                         function decode(str, keep_slashes) {
1367                                 if (isEncoded) {
1368                                         str = str.replace(/\uFEFF[0-9]/g, function(str) {
1369                                                 return encodingLookup[str];
1370                                         });
1371                                 }
1372
1373                                 if (!keep_slashes)
1374                                         str = str.replace(/\\([\'\";:])/g, "$1");
1375
1376                                 return str;
1377                         }
1378
1379                         if (css) {
1380                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1381                                 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1382                                         return str.replace(/[;:]/g, encode);
1383                                 });
1384
1385                                 // Parse styles
1386                                 while (matches = styleRegExp.exec(css)) {
1387                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1388                                         value = matches[2].replace(trimRightRegExp, '');
1389
1390                                         if (name && value.length > 0) {
1391                                                 // Opera will produce 700 instead of bold in their style values
1392                                                 if (name === 'font-weight' && value === '700')
1393                                                         value = 'bold';
1394                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1395                                                         value = value.toLowerCase();            
1396
1397                                                 // Convert RGB colors to HEX
1398                                                 value = value.replace(rgbRegExp, toHex);
1399
1400                                                 // Convert URLs and force them into url('value') format
1401                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1402                                                         str = str || str2;
1403
1404                                                         if (str) {
1405                                                                 str = decode(str);
1406
1407                                                                 // Force strings into single quote format
1408                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";
1409                                                         }
1410
1411                                                         url = decode(url || url2 || url3);
1412
1413                                                         // Convert the URL to relative/absolute depending on config
1414                                                         if (urlConverter)
1415                                                                 url = urlConverter.call(urlConverterScope, url, 'style');
1416
1417                                                         // Output new URL format
1418                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";
1419                                                 });
1420
1421                                                 styles[name] = isEncoded ? decode(value, true) : value;
1422                                         }
1423
1424                                         styleRegExp.lastIndex = matches.index + matches[0].length;
1425                                 }
1426
1427                                 // Compress the styles to reduce it's size for example IE will expand styles
1428                                 compress("border", "");
1429                                 compress("border", "-width");
1430                                 compress("border", "-color");
1431                                 compress("border", "-style");
1432                                 compress("padding", "");
1433                                 compress("margin", "");
1434                                 compress2('border', 'border-width', 'border-style', 'border-color');
1435
1436                                 // Remove pointless border, IE produces these
1437                                 if (styles.border === 'medium none')
1438                                         delete styles.border;
1439                         }
1440
1441                         return styles;
1442                 },
1443
1444                 serialize : function(styles, element_name) {
1445                         var css = '', name, value;
1446
1447                         function serializeStyles(name) {
1448                                 var styleList, i, l, value;
1449
1450                                 styleList = schema.styles[name];
1451                                 if (styleList) {
1452                                         for (i = 0, l = styleList.length; i < l; i++) {
1453                                                 name = styleList[i];
1454                                                 value = styles[name];
1455
1456                                                 if (value !== undef && value.length > 0)
1457                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1458                                         }
1459                                 }
1460                         };
1461
1462                         // Serialize styles according to schema
1463                         if (element_name && schema && schema.styles) {
1464                                 // Serialize global styles and element specific styles
1465                                 serializeStyles('*');
1466                                 serializeStyles(element_name);
1467                         } else {
1468                                 // Output the styles in the order they are inside the object
1469                                 for (name in styles) {
1470                                         value = styles[name];
1471
1472                                         if (value !== undef && value.length > 0)
1473                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1474                                 }
1475                         }
1476
1477                         return css;
1478                 }
1479         };
1480 };
1481
1482 (function(tinymce) {
1483         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},
1484                 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1485
1486         function split(str, delim) {
1487                 return str.split(delim || ',');
1488         };
1489
1490         function unpack(lookup, data) {
1491                 var key, elements = {};
1492
1493                 function replace(value) {
1494                         return value.replace(/[A-Z]+/g, function(key) {
1495                                 return replace(lookup[key]);
1496                         });
1497                 };
1498
1499                 // Unpack lookup
1500                 for (key in lookup) {
1501                         if (lookup.hasOwnProperty(key))
1502                                 lookup[key] = replace(lookup[key]);
1503                 }
1504
1505                 // Unpack and parse data into object map
1506                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1507                         attributes = split(attributes, '|');
1508
1509                         elements[name] = {
1510                                 attributes : makeMap(attributes),
1511                                 attributesOrder : attributes,
1512                                 children : makeMap(children, '|', {'#comment' : {}})
1513                         }
1514                 });
1515
1516                 return elements;
1517         };
1518
1519         // Build a lookup table for block elements both lowercase and uppercase
1520         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + 
1521                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + 
1522                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1523         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1524
1525         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1526         transitional = unpack({
1527                 Z : 'H|K|N|O|P',
1528                 Y : 'X|form|R|Q',
1529                 ZG : 'E|span|width|align|char|charoff|valign',
1530                 X : 'p|T|div|U|W|isindex|fieldset|table',
1531                 ZF : 'E|align|char|charoff|valign',
1532                 W : 'pre|hr|blockquote|address|center|noframes',
1533                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1534                 ZD : '[E][S]',
1535                 U : 'ul|ol|dl|menu|dir',
1536                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1537                 T : 'h1|h2|h3|h4|h5|h6',
1538                 ZB : 'X|S|Q',
1539                 S : 'R|P',
1540                 ZA : 'a|G|J|M|O|P',
1541                 R : 'a|H|K|N|O',
1542                 Q : 'noscript|P',
1543                 P : 'ins|del|script',
1544                 O : 'input|select|textarea|label|button',
1545                 N : 'M|L',
1546                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1547                 L : 'sub|sup',
1548                 K : 'J|I',
1549                 J : 'tt|i|b|u|s|strike',
1550                 I : 'big|small|font|basefont',
1551                 H : 'G|F',
1552                 G : 'br|span|bdo',
1553                 F : 'object|applet|img|map|iframe',
1554                 E : 'A|B|C',
1555                 D : 'accesskey|tabindex|onfocus|onblur',
1556                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1557                 B : 'lang|xml:lang|dir',
1558                 A : 'id|class|style|title'
1559         }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
1560                 'style[B|id|type|media|title|xml:space][]' + 
1561                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
1562                 'param[id|name|value|valuetype|type][]' + 
1563                 'p[E|align][#|S]' + 
1564                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
1565                 'br[A|clear][]' + 
1566                 'span[E][#|S]' + 
1567                 'bdo[A|C|B][#|S]' + 
1568                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
1569                 'h1[E|align][#|S]' + 
1570                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
1571                 'map[B|C|A|name][X|form|Q|area]' + 
1572                 'h2[E|align][#|S]' + 
1573                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
1574                 'h3[E|align][#|S]' + 
1575                 'tt[E][#|S]' + 
1576                 'i[E][#|S]' + 
1577                 'b[E][#|S]' + 
1578                 'u[E][#|S]' + 
1579                 's[E][#|S]' + 
1580                 'strike[E][#|S]' + 
1581                 'big[E][#|S]' + 
1582                 'small[E][#|S]' + 
1583                 'font[A|B|size|color|face][#|S]' + 
1584                 'basefont[id|size|color|face][]' + 
1585                 'em[E][#|S]' + 
1586                 'strong[E][#|S]' + 
1587                 'dfn[E][#|S]' + 
1588                 'code[E][#|S]' + 
1589                 'q[E|cite][#|S]' + 
1590                 'samp[E][#|S]' + 
1591                 'kbd[E][#|S]' + 
1592                 'var[E][#|S]' + 
1593                 'cite[E][#|S]' + 
1594                 'abbr[E][#|S]' + 
1595                 'acronym[E][#|S]' + 
1596                 'sub[E][#|S]' + 
1597                 'sup[E][#|S]' + 
1598                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
1599                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
1600                 'optgroup[E|disabled|label][option]' + 
1601                 'option[E|selected|disabled|label|value][]' + 
1602                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
1603                 'label[E|for|accesskey|onfocus|onblur][#|S]' + 
1604                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
1605                 'h4[E|align][#|S]' + 
1606                 'ins[E|cite|datetime][#|Y]' + 
1607                 'h5[E|align][#|S]' + 
1608                 'del[E|cite|datetime][#|Y]' + 
1609                 'h6[E|align][#|S]' + 
1610                 'div[E|align][#|Y]' + 
1611                 'ul[E|type|compact][li]' + 
1612                 'li[E|type|value][#|Y]' + 
1613                 'ol[E|type|compact|start][li]' + 
1614                 'dl[E|compact][dt|dd]' + 
1615                 'dt[E][#|S]' + 
1616                 'dd[E][#|Y]' + 
1617                 'menu[E|compact][li]' + 
1618                 'dir[E|compact][li]' + 
1619                 'pre[E|width|xml:space][#|ZA]' + 
1620                 'hr[E|align|noshade|size|width][]' + 
1621                 'blockquote[E|cite][#|Y]' + 
1622                 'address[E][#|S|p]' + 
1623                 'center[E][#|Y]' + 
1624                 'noframes[E][#|Y]' + 
1625                 'isindex[A|B|prompt][]' + 
1626                 'fieldset[E][#|legend|Y]' + 
1627                 'legend[E|accesskey|align][#|S]' + 
1628                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
1629                 'caption[E|align][#|S]' + 
1630                 'col[ZG][]' + 
1631                 'colgroup[ZG][col]' + 
1632                 'thead[ZF][tr]' + 
1633                 'tr[ZF|bgcolor][th|td]' + 
1634                 'th[E|ZE][#|Y]' + 
1635                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
1636                 'noscript[E][#|Y]' + 
1637                 'td[E|ZE][#|Y]' + 
1638                 'tfoot[ZF][tr]' + 
1639                 'tbody[ZF][tr]' + 
1640                 'area[E|D|shape|coords|href|nohref|alt|target][]' + 
1641                 'base[id|href|target][]' + 
1642                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1643         );
1644
1645         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls');
1646         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1647         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap);
1648         whiteSpaceElementsMap = makeMap('pre,script,style,textarea');
1649         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1650
1651         tinymce.html.Schema = function(settings) {
1652                 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1653
1654                 settings = settings || {};
1655
1656                 // Allow all elements and attributes if verify_html is set to false
1657                 if (settings.verify_html === false)
1658                         settings.valid_elements = '*[*]';
1659
1660                 // Build styles list
1661                 if (settings.valid_styles) {
1662                         validStyles = {};
1663
1664                         // Convert styles into a rule list
1665                         each(settings.valid_styles, function(value, key) {
1666                                 validStyles[key] = tinymce.explode(value);
1667                         });
1668                 }
1669
1670                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1671                 function patternToRegExp(str) {
1672                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1673                 };
1674
1675                 // Parses the specified valid_elements string and adds to the current rules
1676                 // This function is a bit hard to read since it's heavily optimized for speed
1677                 function addValidElements(valid_elements) {
1678                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1679                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1680                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1681                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1682                                 hasPatternsRegExp = /[*?+]/;
1683
1684                         if (valid_elements) {
1685                                 // Split valid elements into an array with rules
1686                                 valid_elements = split(valid_elements);
1687
1688                                 if (elements['@']) {
1689                                         globalAttributes = elements['@'].attributes;
1690                                         globalAttributesOrder = elements['@'].attributesOrder;
1691                                 }
1692
1693                                 // Loop all rules
1694                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1695                                         // Parse element rule
1696                                         matches = elementRuleRegExp.exec(valid_elements[ei]);
1697                                         if (matches) {
1698                                                 // Setup local names for matches
1699                                                 prefix = matches[1];
1700                                                 elementName = matches[2];
1701                                                 outputName = matches[3];
1702                                                 attrData = matches[4];
1703
1704                                                 // Create new attributes and attributesOrder
1705                                                 attributes = {};
1706                                                 attributesOrder = [];
1707
1708                                                 // Create the new element
1709                                                 element = {
1710                                                         attributes : attributes,
1711                                                         attributesOrder : attributesOrder
1712                                                 };
1713
1714                                                 // Padd empty elements prefix
1715                                                 if (prefix === '#')
1716                                                         element.paddEmpty = true;
1717
1718                                                 // Remove empty elements prefix
1719                                                 if (prefix === '-')
1720                                                         element.removeEmpty = true;
1721
1722                                                 // Copy attributes from global rule into current rule
1723                                                 if (globalAttributes) {
1724                                                         for (key in globalAttributes)
1725                                                                 attributes[key] = globalAttributes[key];
1726
1727                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
1728                                                 }
1729
1730                                                 // Attributes defined
1731                                                 if (attrData) {
1732                                                         attrData = split(attrData, '|');
1733                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {
1734                                                                 matches = attrRuleRegExp.exec(attrData[ai]);
1735                                                                 if (matches) {
1736                                                                         attr = {};
1737                                                                         attrType = matches[1];
1738                                                                         attrName = matches[2].replace(/::/g, ':');
1739                                                                         prefix = matches[3];
1740                                                                         value = matches[4];
1741
1742                                                                         // Required
1743                                                                         if (attrType === '!') {
1744                                                                                 element.attributesRequired = element.attributesRequired || [];
1745                                                                                 element.attributesRequired.push(attrName);
1746                                                                                 attr.required = true;
1747                                                                         }
1748
1749                                                                         // Denied from global
1750                                                                         if (attrType === '-') {
1751                                                                                 delete attributes[attrName];
1752                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
1753                                                                                 continue;
1754                                                                         }
1755
1756                                                                         // Default value
1757                                                                         if (prefix) {
1758                                                                                 // Default value
1759                                                                                 if (prefix === '=') {
1760                                                                                         element.attributesDefault = element.attributesDefault || [];
1761                                                                                         element.attributesDefault.push({name: attrName, value: value});
1762                                                                                         attr.defaultValue = value;
1763                                                                                 }
1764
1765                                                                                 // Forced value
1766                                                                                 if (prefix === ':') {
1767                                                                                         element.attributesForced = element.attributesForced || [];
1768                                                                                         element.attributesForced.push({name: attrName, value: value});
1769                                                                                         attr.forcedValue = value;
1770                                                                                 }
1771
1772                                                                                 // Required values
1773                                                                                 if (prefix === '<')
1774                                                                                         attr.validValues = makeMap(value, '?');
1775                                                                         }
1776
1777                                                                         // Check for attribute patterns
1778                                                                         if (hasPatternsRegExp.test(attrName)) {
1779                                                                                 element.attributePatterns = element.attributePatterns || [];
1780                                                                                 attr.pattern = patternToRegExp(attrName);
1781                                                                                 element.attributePatterns.push(attr);
1782                                                                         } else {
1783                                                                                 // Add attribute to order list if it doesn't already exist
1784                                                                                 if (!attributes[attrName])
1785                                                                                         attributesOrder.push(attrName);
1786
1787                                                                                 attributes[attrName] = attr;
1788                                                                         }
1789                                                                 }
1790                                                         }
1791                                                 }
1792
1793                                                 // Global rule, store away these for later usage
1794                                                 if (!globalAttributes && elementName == '@') {
1795                                                         globalAttributes = attributes;
1796                                                         globalAttributesOrder = attributesOrder;
1797                                                 }
1798
1799                                                 // Handle substitute elements such as b/strong
1800                                                 if (outputName) {
1801                                                         element.outputName = elementName;
1802                                                         elements[outputName] = element;
1803                                                 }
1804
1805                                                 // Add pattern or exact element
1806                                                 if (hasPatternsRegExp.test(elementName)) {
1807                                                         element.pattern = patternToRegExp(elementName);
1808                                                         patternElements.push(element);
1809                                                 } else
1810                                                         elements[elementName] = element;
1811                                         }
1812                                 }
1813                         }
1814                 };
1815
1816                 function setValidElements(valid_elements) {
1817                         elements = {};
1818                         patternElements = [];
1819
1820                         addValidElements(valid_elements);
1821
1822                         each(transitional, function(element, name) {
1823                                 children[name] = element.children;
1824                         });
1825                 };
1826
1827                 // Adds custom non HTML elements to the schema
1828                 function addCustomElements(custom_elements) {
1829                         var customElementRegExp = /^(~)?(.+)$/;
1830
1831                         if (custom_elements) {
1832                                 each(split(custom_elements), function(rule) {
1833                                         var matches = customElementRegExp.exec(rule),
1834                                                 inline = matches[1] === '~',
1835                                                 cloneName = inline ? 'span' : 'div',
1836                                                 name = matches[2];
1837
1838                                         children[name] = children[cloneName];
1839                                         customElementsMap[name] = cloneName;
1840
1841                                         // If it's not marked as inline then add it to valid block elements
1842                                         if (!inline)
1843                                                 blockElementsMap[name] = {};
1844
1845                                         // Add custom elements at span/div positions
1846                                         each(children, function(element, child) {
1847                                                 if (element[cloneName])
1848                                                         element[name] = element[cloneName];
1849                                         });
1850                                 });
1851                         }
1852                 };
1853
1854                 // Adds valid children to the schema object
1855                 function addValidChildren(valid_children) {
1856                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
1857
1858                         if (valid_children) {
1859                                 each(split(valid_children), function(rule) {
1860                                         var matches = childRuleRegExp.exec(rule), parent, prefix;
1861
1862                                         if (matches) {
1863                                                 prefix = matches[1];
1864
1865                                                 // Add/remove items from default
1866                                                 if (prefix)
1867                                                         parent = children[matches[2]];
1868                                                 else
1869                                                         parent = children[matches[2]] = {'#comment' : {}};
1870
1871                                                 parent = children[matches[2]];
1872
1873                                                 each(split(matches[3], '|'), function(child) {
1874                                                         if (prefix === '-')
1875                                                                 delete parent[child];
1876                                                         else
1877                                                                 parent[child] = {};
1878                                                 });
1879                                         }
1880                                 });
1881                         }
1882                 };
1883
1884                 function getElementRule(name) {
1885                         var element = elements[name], i;
1886
1887                         // Exact match found
1888                         if (element)
1889                                 return element;
1890
1891                         // No exact match then try the patterns
1892                         i = patternElements.length;
1893                         while (i--) {
1894                                 element = patternElements[i];
1895
1896                                 if (element.pattern.test(name))
1897                                         return element;
1898                         }
1899                 };
1900
1901                 if (!settings.valid_elements) {
1902                         // No valid elements defined then clone the elements from the transitional spec
1903                         each(transitional, function(element, name) {
1904                                 elements[name] = {
1905                                         attributes : element.attributes,
1906                                         attributesOrder : element.attributesOrder
1907                                 };
1908
1909                                 children[name] = element.children;
1910                         });
1911
1912                         // Switch these
1913                         each(split('strong/b,em/i'), function(item) {
1914                                 item = split(item, '/');
1915                                 elements[item[1]].outputName = item[0];
1916                         });
1917
1918                         // Add default alt attribute for images
1919                         elements.img.attributesDefault = [{name: 'alt', value: ''}];
1920
1921                         // Remove these if they are empty by default
1922                         each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {
1923                                 elements[name].removeEmpty = true;
1924                         });
1925
1926                         // Padd these by default
1927                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
1928                                 elements[name].paddEmpty = true;
1929                         });
1930                 } else
1931                         setValidElements(settings.valid_elements);
1932
1933                 addCustomElements(settings.custom_elements);
1934                 addValidChildren(settings.valid_children);
1935                 addValidElements(settings.extended_valid_elements);
1936
1937                 // Todo: Remove this when we fix list handling to be valid
1938                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
1939
1940                 // If the user didn't allow span only allow internal spans
1941                 if (!getElementRule('span'))
1942                         addValidElements('span[!data-mce-type|*]');
1943
1944                 // Delete invalid elements
1945                 if (settings.invalid_elements) {
1946                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
1947                                 if (elements[item])
1948                                         delete elements[item];
1949                         });
1950                 }
1951
1952                 self.children = children;
1953
1954                 self.styles = validStyles;
1955
1956                 self.getBoolAttrs = function() {
1957                         return boolAttrMap;
1958                 };
1959
1960                 self.getBlockElements = function() {
1961                         return blockElementsMap;
1962                 };
1963
1964                 self.getShortEndedElements = function() {
1965                         return shortEndedElementsMap;
1966                 };
1967
1968                 self.getSelfClosingElements = function() {
1969                         return selfClosingElementsMap;
1970                 };
1971
1972                 self.getNonEmptyElements = function() {
1973                         return nonEmptyElementsMap;
1974                 };
1975
1976                 self.getWhiteSpaceElements = function() {
1977                         return whiteSpaceElementsMap;
1978                 };
1979
1980                 self.isValidChild = function(name, child) {
1981                         var parent = children[name];
1982
1983                         return !!(parent && parent[child]);
1984                 };
1985
1986                 self.getElementRule = getElementRule;
1987
1988                 self.getCustomElements = function() {
1989                         return customElementsMap;
1990                 };
1991
1992                 self.addValidElements = addValidElements;
1993
1994                 self.setValidElements = setValidElements;
1995
1996                 self.addCustomElements = addCustomElements;
1997
1998                 self.addValidChildren = addValidChildren;
1999         };
2000
2001         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
2002         tinymce.html.Schema.boolAttrMap = boolAttrMap;
2003         tinymce.html.Schema.blockElementsMap = blockElementsMap;
2004 })(tinymce);
2005
2006 (function(tinymce) {
2007         tinymce.html.SaxParser = function(settings, schema) {
2008                 var self = this, noop = function() {};
2009
2010                 settings = settings || {};
2011                 self.schema = schema = schema || new tinymce.html.Schema();
2012
2013                 if (settings.fix_self_closing !== false)
2014                         settings.fix_self_closing = true;
2015
2016                 // Add handler functions from settings and setup default handlers
2017                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2018                         if (name)
2019                                 self[name] = settings[name] || noop;
2020                 });
2021
2022                 self.parse = function(html) {
2023                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2024                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
2025                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2026                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
2027
2028                         function processEndTag(name) {
2029                                 var pos, i;
2030
2031                                 // Find position of parent of the same type
2032                                 pos = stack.length;
2033                                 while (pos--) {
2034                                         if (stack[pos].name === name)
2035                                                 break;                                          
2036                                 }
2037
2038                                 // Found parent
2039                                 if (pos >= 0) {
2040                                         // Close all the open elements
2041                                         for (i = stack.length - 1; i >= pos; i--) {
2042                                                 name = stack[i];
2043
2044                                                 if (name.valid)
2045                                                         self.end(name.name);
2046                                         }
2047
2048                                         // Remove the open elements from the stack
2049                                         stack.length = pos;
2050                                 }
2051                         };
2052
2053                         // Precompile RegExps and map objects
2054                         tokenRegExp = new RegExp('<(?:' +
2055                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
2056                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2057                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2058                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2059                                 '(?:\\/([^>]+)>)|' + // End element
2060                                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
2061                         ')', 'g');
2062
2063                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2064                         specialElements = {
2065                                 'script' : /<\/script[^>]*>/gi,
2066                                 'style' : /<\/style[^>]*>/gi,
2067                                 'noscript' : /<\/noscript[^>]*>/gi
2068                         };
2069
2070                         // Setup lookup tables for empty elements and boolean attributes
2071                         shortEndedElements = schema.getShortEndedElements();
2072                         selfClosing = schema.getSelfClosingElements();
2073                         fillAttrsMap = schema.getBoolAttrs();
2074                         validate = settings.validate;
2075                         removeInternalElements = settings.remove_internals;
2076                         fixSelfClosing = settings.fix_self_closing;
2077
2078                         while (matches = tokenRegExp.exec(html)) {
2079                                 // Text
2080                                 if (index < matches.index)
2081                                         self.text(decode(html.substr(index, matches.index - index)));
2082
2083                                 if (value = matches[6]) { // End element
2084                                         processEndTag(value.toLowerCase());
2085                                 } else if (value = matches[7]) { // Start element
2086                                         value = value.toLowerCase();
2087                                         isShortEnded = value in shortEndedElements;
2088
2089                                         // Is self closing tag for example an <li> after an open <li>
2090                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2091                                                 processEndTag(value);
2092
2093                                         // Validate element
2094                                         if (!validate || (elementRule = schema.getElementRule(value))) {
2095                                                 isValidElement = true;
2096
2097                                                 // Grab attributes map and patters when validation is enabled
2098                                                 if (validate) {
2099                                                         validAttributesMap = elementRule.attributes;
2100                                                         validAttributePatterns = elementRule.attributePatterns;
2101                                                 }
2102
2103                                                 // Parse attributes
2104                                                 if (attribsValue = matches[8]) {
2105                                                         isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
2106
2107                                                         // If the element has internal attributes then remove it if we are told to do so
2108                                                         if (isInternalElement && removeInternalElements)
2109                                                                 isValidElement = false;
2110
2111                                                         attrList = [];
2112                                                         attrList.map = {};
2113
2114                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2115                                                                 var attrRule, i;
2116
2117                                                                 name = name.toLowerCase();
2118                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2119
2120                                                                 // Validate name and value
2121                                                                 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2122                                                                         attrRule = validAttributesMap[name];
2123
2124                                                                         // Find rule by pattern matching
2125                                                                         if (!attrRule && validAttributePatterns) {
2126                                                                                 i = validAttributePatterns.length;
2127                                                                                 while (i--) {
2128                                                                                         attrRule = validAttributePatterns[i];
2129                                                                                         if (attrRule.pattern.test(name))
2130                                                                                                 break;
2131                                                                                 }
2132
2133                                                                                 // No rule matched
2134                                                                                 if (i === -1)
2135                                                                                         attrRule = null;
2136                                                                         }
2137
2138                                                                         // No attribute rule found
2139                                                                         if (!attrRule)
2140                                                                                 return;
2141
2142                                                                         // Validate value
2143                                                                         if (attrRule.validValues && !(value in attrRule.validValues))
2144                                                                                 return;
2145                                                                 }
2146
2147                                                                 // Add attribute to list and map
2148                                                                 attrList.map[name] = value;
2149                                                                 attrList.push({
2150                                                                         name: name,
2151                                                                         value: value
2152                                                                 });
2153                                                         });
2154                                                 } else {
2155                                                         attrList = [];
2156                                                         attrList.map = {};
2157                                                 }
2158
2159                                                 // Process attributes if validation is enabled
2160                                                 if (validate && !isInternalElement) {
2161                                                         attributesRequired = elementRule.attributesRequired;
2162                                                         attributesDefault = elementRule.attributesDefault;
2163                                                         attributesForced = elementRule.attributesForced;
2164
2165                                                         // Handle forced attributes
2166                                                         if (attributesForced) {
2167                                                                 i = attributesForced.length;
2168                                                                 while (i--) {
2169                                                                         attr = attributesForced[i];
2170                                                                         name = attr.name;
2171                                                                         attrValue = attr.value;
2172
2173                                                                         if (attrValue === '{$uid}')
2174                                                                                 attrValue = 'mce_' + idCount++;
2175
2176                                                                         attrList.map[name] = attrValue;
2177                                                                         attrList.push({name: name, value: attrValue});
2178                                                                 }
2179                                                         }
2180
2181                                                         // Handle default attributes
2182                                                         if (attributesDefault) {
2183                                                                 i = attributesDefault.length;
2184                                                                 while (i--) {
2185                                                                         attr = attributesDefault[i];
2186                                                                         name = attr.name;
2187
2188                                                                         if (!(name in attrList.map)) {
2189                                                                                 attrValue = attr.value;
2190
2191                                                                                 if (attrValue === '{$uid}')
2192                                                                                         attrValue = 'mce_' + idCount++;
2193
2194                                                                                 attrList.map[name] = attrValue;
2195                                                                                 attrList.push({name: name, value: attrValue});
2196                                                                         }
2197                                                                 }
2198                                                         }
2199
2200                                                         // Handle required attributes
2201                                                         if (attributesRequired) {
2202                                                                 i = attributesRequired.length;
2203                                                                 while (i--) {
2204                                                                         if (attributesRequired[i] in attrList.map)
2205                                                                                 break;
2206                                                                 }
2207
2208                                                                 // None of the required attributes where found
2209                                                                 if (i === -1)
2210                                                                         isValidElement = false;
2211                                                         }
2212
2213                                                         // Invalidate element if it's marked as bogus
2214                                                         if (attrList.map['data-mce-bogus'])
2215                                                                 isValidElement = false;
2216                                                 }
2217
2218                                                 if (isValidElement)
2219                                                         self.start(value, attrList, isShortEnded);
2220                                         } else
2221                                                 isValidElement = false;
2222
2223                                         // Treat script, noscript and style a bit different since they may include code that looks like elements
2224                                         if (endRegExp = specialElements[value]) {
2225                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;
2226
2227                                                 if (matches = endRegExp.exec(html)) {
2228                                                         if (isValidElement)
2229                                                                 text = html.substr(index, matches.index - index);
2230
2231                                                         index = matches.index + matches[0].length;
2232                                                 } else {
2233                                                         text = html.substr(index);
2234                                                         index = html.length;
2235                                                 }
2236
2237                                                 if (isValidElement && text.length > 0)
2238                                                         self.text(text, true);
2239
2240                                                 if (isValidElement)
2241                                                         self.end(value);
2242
2243                                                 tokenRegExp.lastIndex = index;
2244                                                 continue;
2245                                         }
2246
2247                                         // Push value on to stack
2248                                         if (!isShortEnded) {
2249                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2250                                                         stack.push({name: value, valid: isValidElement});
2251                                                 else if (isValidElement)
2252                                                         self.end(value);
2253                                         }
2254                                 } else if (value = matches[1]) { // Comment
2255                                         self.comment(value);
2256                                 } else if (value = matches[2]) { // CDATA
2257                                         self.cdata(value);
2258                                 } else if (value = matches[3]) { // DOCTYPE
2259                                         self.doctype(value);
2260                                 } else if (value = matches[4]) { // PI
2261                                         self.pi(value, matches[5]);
2262                                 }
2263
2264                                 index = matches.index + matches[0].length;
2265                         }
2266
2267                         // Text
2268                         if (index < html.length)
2269                                 self.text(decode(html.substr(index)));
2270
2271                         // Close any open elements
2272                         for (i = stack.length - 1; i >= 0; i--) {
2273                                 value = stack[i];
2274
2275                                 if (value.valid)
2276                                         self.end(value.name);
2277                         }
2278                 };
2279         }
2280 })(tinymce);
2281
2282 (function(tinymce) {
2283         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2284                 '#text' : 3,
2285                 '#comment' : 8,
2286                 '#cdata' : 4,
2287                 '#pi' : 7,
2288                 '#doctype' : 10,
2289                 '#document-fragment' : 11
2290         };
2291
2292         // Walks the tree left/right
2293         function walk(node, root_node, prev) {
2294                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2295
2296                 // Walk into nodes if it has a start
2297                 if (node[startName])
2298                         return node[startName];
2299
2300                 // Return the sibling if it has one
2301                 if (node !== root_node) {
2302                         sibling = node[siblingName];
2303
2304                         if (sibling)
2305                                 return sibling;
2306
2307                         // Walk up the parents to look for siblings
2308                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2309                                 sibling = parent[siblingName];
2310
2311                                 if (sibling)
2312                                         return sibling;
2313                         }
2314                 }
2315         };
2316
2317         function Node(name, type) {
2318                 this.name = name;
2319                 this.type = type;
2320
2321                 if (type === 1) {
2322                         this.attributes = [];
2323                         this.attributes.map = {};
2324                 }
2325         }
2326
2327         tinymce.extend(Node.prototype, {
2328                 replace : function(node) {
2329                         var self = this;
2330
2331                         if (node.parent)
2332                                 node.remove();
2333
2334                         self.insert(node, self);
2335                         self.remove();
2336
2337                         return self;
2338                 },
2339
2340                 attr : function(name, value) {
2341                         var self = this, attrs, i, undef;
2342
2343                         if (typeof name !== "string") {
2344                                 for (i in name)
2345                                         self.attr(i, name[i]);
2346
2347                                 return self;
2348                         }
2349
2350                         if (attrs = self.attributes) {
2351                                 if (value !== undef) {
2352                                         // Remove attribute
2353                                         if (value === null) {
2354                                                 if (name in attrs.map) {
2355                                                         delete attrs.map[name];
2356
2357                                                         i = attrs.length;
2358                                                         while (i--) {
2359                                                                 if (attrs[i].name === name) {
2360                                                                         attrs = attrs.splice(i, 1);
2361                                                                         return self;
2362                                                                 }
2363                                                         }
2364                                                 }
2365
2366                                                 return self;
2367                                         }
2368
2369                                         // Set attribute
2370                                         if (name in attrs.map) {
2371                                                 // Set attribute
2372                                                 i = attrs.length;
2373                                                 while (i--) {
2374                                                         if (attrs[i].name === name) {
2375                                                                 attrs[i].value = value;
2376                                                                 break;
2377                                                         }
2378                                                 }
2379                                         } else
2380                                                 attrs.push({name: name, value: value});
2381
2382                                         attrs.map[name] = value;
2383
2384                                         return self;
2385                                 } else {
2386                                         return attrs.map[name];
2387                                 }
2388                         }
2389                 },
2390
2391                 clone : function() {
2392                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2393
2394                         // Clone element attributes
2395                         if (selfAttrs = self.attributes) {
2396                                 cloneAttrs = [];
2397                                 cloneAttrs.map = {};
2398
2399                                 for (i = 0, l = selfAttrs.length; i < l; i++) {
2400                                         selfAttr = selfAttrs[i];
2401
2402                                         // Clone everything except id
2403                                         if (selfAttr.name !== 'id') {
2404                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2405                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2406                                         }
2407                                 }
2408
2409                                 clone.attributes = cloneAttrs;
2410                         }
2411
2412                         clone.value = self.value;
2413                         clone.shortEnded = self.shortEnded;
2414
2415                         return clone;
2416                 },
2417
2418                 wrap : function(wrapper) {
2419                         var self = this;
2420
2421                         self.parent.insert(wrapper, self);
2422                         wrapper.append(self);
2423
2424                         return self;
2425                 },
2426
2427                 unwrap : function() {
2428                         var self = this, node, next;
2429
2430                         for (node = self.firstChild; node; ) {
2431                                 next = node.next;
2432                                 self.insert(node, self, true);
2433                                 node = next;
2434                         }
2435
2436                         self.remove();
2437                 },
2438
2439                 remove : function() {
2440                         var self = this, parent = self.parent, next = self.next, prev = self.prev;
2441
2442                         if (parent) {
2443                                 if (parent.firstChild === self) {
2444                                         parent.firstChild = next;
2445
2446                                         if (next)
2447                                                 next.prev = null;
2448                                 } else {
2449                                         prev.next = next;
2450                                 }
2451
2452                                 if (parent.lastChild === self) {
2453                                         parent.lastChild = prev;
2454
2455                                         if (prev)
2456                                                 prev.next = null;
2457                                 } else {
2458                                         next.prev = prev;
2459                                 }
2460
2461                                 self.parent = self.next = self.prev = null;
2462                         }
2463
2464                         return self;
2465                 },
2466
2467                 append : function(node) {
2468                         var self = this, last;
2469
2470                         if (node.parent)
2471                                 node.remove();
2472
2473                         last = self.lastChild;
2474                         if (last) {
2475                                 last.next = node;
2476                                 node.prev = last;
2477                                 self.lastChild = node;
2478                         } else
2479                                 self.lastChild = self.firstChild = node;
2480
2481                         node.parent = self;
2482
2483                         return node;
2484                 },
2485
2486                 insert : function(node, ref_node, before) {
2487                         var parent;
2488
2489                         if (node.parent)
2490                                 node.remove();
2491
2492                         parent = ref_node.parent || this;
2493
2494                         if (before) {
2495                                 if (ref_node === parent.firstChild)
2496                                         parent.firstChild = node;
2497                                 else
2498                                         ref_node.prev.next = node;
2499
2500                                 node.prev = ref_node.prev;
2501                                 node.next = ref_node;
2502                                 ref_node.prev = node;
2503                         } else {
2504                                 if (ref_node === parent.lastChild)
2505                                         parent.lastChild = node;
2506                                 else
2507                                         ref_node.next.prev = node;
2508
2509                                 node.next = ref_node.next;
2510                                 node.prev = ref_node;
2511                                 ref_node.next = node;
2512                         }
2513
2514                         node.parent = parent;
2515
2516                         return node;
2517                 },
2518
2519                 getAll : function(name) {
2520                         var self = this, node, collection = [];
2521
2522                         for (node = self.firstChild; node; node = walk(node, self)) {
2523                                 if (node.name === name)
2524                                         collection.push(node);
2525                         }
2526
2527                         return collection;
2528                 },
2529
2530                 empty : function() {
2531                         var self = this, nodes, i, node;
2532
2533                         // Remove all children
2534                         if (self.firstChild) {
2535                                 nodes = [];
2536
2537                                 // Collect the children
2538                                 for (node = self.firstChild; node; node = walk(node, self))
2539                                         nodes.push(node);
2540
2541                                 // Remove the children
2542                                 i = nodes.length;
2543                                 while (i--) {
2544                                         node = nodes[i];
2545                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2546                                 }
2547                         }
2548
2549                         self.firstChild = self.lastChild = null;
2550
2551                         return self;
2552                 },
2553
2554                 isEmpty : function(elements) {
2555                         var self = this, node = self.firstChild, i, name;
2556
2557                         if (node) {
2558                                 do {
2559                                         if (node.type === 1) {
2560                                                 // Ignore bogus elements
2561                                                 if (node.attributes.map['data-mce-bogus'])
2562                                                         continue;
2563
2564                                                 // Keep empty elements like <img />
2565                                                 if (elements[node.name])
2566                                                         return false;
2567
2568                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
2569                                                 i = node.attributes.length;
2570                                                 while (i--) {
2571                                                         name = node.attributes[i].name;
2572                                                         if (name === "name" || name.indexOf('data-') === 0)
2573                                                                 return false;
2574                                                 }
2575                                         }
2576
2577                                         // Keep non whitespace text nodes
2578                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2579                                                 return false;
2580                                 } while (node = walk(node, self));
2581                         }
2582
2583                         return true;
2584                 },
2585
2586                 walk : function(prev) {
2587                         return walk(this, null, prev);
2588                 }
2589         });
2590
2591         tinymce.extend(Node, {
2592                 create : function(name, attrs) {
2593                         var node, attrName;
2594
2595                         // Create node
2596                         node = new Node(name, typeLookup[name] || 1);
2597
2598                         // Add attributes if needed
2599                         if (attrs) {
2600                                 for (attrName in attrs)
2601                                         node.attr(attrName, attrs[attrName]);
2602                         }
2603
2604                         return node;
2605                 }
2606         });
2607
2608         tinymce.html.Node = Node;
2609 })(tinymce);
2610
2611 (function(tinymce) {
2612         var Node = tinymce.html.Node;
2613
2614         tinymce.html.DomParser = function(settings, schema) {
2615                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2616
2617                 settings = settings || {};
2618                 settings.validate = "validate" in settings ? settings.validate : true;
2619                 settings.root_name = settings.root_name || 'body';
2620                 self.schema = schema = schema || new tinymce.html.Schema();
2621
2622                 function fixInvalidChildren(nodes) {
2623                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2624                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2625
2626                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2627                         nonEmptyElements = schema.getNonEmptyElements();
2628
2629                         for (ni = 0; ni < nodes.length; ni++) {
2630                                 node = nodes[ni];
2631
2632                                 // Already removed
2633                                 if (!node.parent)
2634                                         continue;
2635
2636                                 // Get list of all parent nodes until we find a valid parent to stick the child into
2637                                 parents = [node];
2638                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2639                                         parents.push(parent);
2640
2641                                 // Found a suitable parent
2642                                 if (parent && parents.length > 1) {
2643                                         // Reverse the array since it makes looping easier
2644                                         parents.reverse();
2645
2646                                         // Clone the related parent and insert that after the moved node
2647                                         newParent = currentNode = self.filterNode(parents[0].clone());
2648
2649                                         // Start cloning and moving children on the left side of the target node
2650                                         for (i = 0; i < parents.length - 1; i++) {
2651                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {
2652                                                         tempNode = self.filterNode(parents[i].clone());
2653                                                         currentNode.append(tempNode);
2654                                                 } else
2655                                                         tempNode = currentNode;
2656
2657                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2658                                                         nextNode = childNode.next;
2659                                                         tempNode.append(childNode);
2660                                                         childNode = nextNode;
2661                                                 }
2662
2663                                                 currentNode = tempNode;
2664                                         }
2665
2666                                         if (!newParent.isEmpty(nonEmptyElements)) {
2667                                                 parent.insert(newParent, parents[0], true);
2668                                                 parent.insert(node, newParent);
2669                                         } else {
2670                                                 parent.insert(node, parents[0], true);
2671                                         }
2672
2673                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2674                                         parent = parents[0];
2675                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2676                                                 parent.empty().remove();
2677                                         }
2678                                 } else if (node.parent) {
2679                                         // If it's an LI try to find a UL/OL for it or wrap it
2680                                         if (node.name === 'li') {
2681                                                 sibling = node.prev;
2682                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2683                                                         sibling.append(node);
2684                                                         continue;
2685                                                 }
2686
2687                                                 sibling = node.next;
2688                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2689                                                         sibling.insert(node, sibling.firstChild, true);
2690                                                         continue;
2691                                                 }
2692
2693                                                 node.wrap(self.filterNode(new Node('ul', 1)));
2694                                                 continue;
2695                                         }
2696
2697                                         // Try wrapping the element in a DIV
2698                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2699                                                 node.wrap(self.filterNode(new Node('div', 1)));
2700                                         } else {
2701                                                 // We failed wrapping it, then remove or unwrap it
2702                                                 if (node.name === 'style' || node.name === 'script')
2703                                                         node.empty().remove();
2704                                                 else
2705                                                         node.unwrap();
2706                                         }
2707                                 }
2708                         }
2709                 };
2710
2711                 self.filterNode = function(node) {
2712                         var i, name, list;
2713
2714                         // Run element filters
2715                         if (name in nodeFilters) {
2716                                 list = matchedNodes[name];
2717
2718                                 if (list)
2719                                         list.push(node);
2720                                 else
2721                                         matchedNodes[name] = [node];
2722                         }
2723
2724                         // Run attribute filters
2725                         i = attributeFilters.length;
2726                         while (i--) {
2727                                 name = attributeFilters[i].name;
2728
2729                                 if (name in node.attributes.map) {
2730                                         list = matchedAttributes[name];
2731
2732                                         if (list)
2733                                                 list.push(node);
2734                                         else
2735                                                 matchedAttributes[name] = [node];
2736                                 }
2737                         }
2738
2739                         return node;
2740                 };
2741
2742                 self.addNodeFilter = function(name, callback) {
2743                         tinymce.each(tinymce.explode(name), function(name) {
2744                                 var list = nodeFilters[name];
2745
2746                                 if (!list)
2747                                         nodeFilters[name] = list = [];
2748
2749                                 list.push(callback);
2750                         });
2751                 };
2752
2753                 self.addAttributeFilter = function(name, callback) {
2754                         tinymce.each(tinymce.explode(name), function(name) {
2755                                 var i;
2756
2757                                 for (i = 0; i < attributeFilters.length; i++) {
2758                                         if (attributeFilters[i].name === name) {
2759                                                 attributeFilters[i].callbacks.push(callback);
2760                                                 return;
2761                                         }
2762                                 }
2763
2764                                 attributeFilters.push({name: name, callbacks: [callback]});
2765                         });
2766                 };
2767
2768                 self.parse = function(html, args) {
2769                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
2770                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
2771                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
2772
2773                         args = args || {};
2774                         matchedNodes = {};
2775                         matchedAttributes = {};
2776                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
2777                         nonEmptyElements = schema.getNonEmptyElements();
2778                         children = schema.children;
2779                         validate = settings.validate;
2780                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
2781
2782                         whiteSpaceElements = schema.getWhiteSpaceElements();
2783                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;
2784                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;
2785                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;
2786
2787                         function addRootBlocks() {
2788                                 var node = rootNode.firstChild, next, rootBlockNode;
2789
2790                                 while (node) {
2791                                         next = node.next;
2792
2793                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
2794                                                 if (!rootBlockNode) {
2795                                                         // Create a new root block element
2796                                                         rootBlockNode = createNode(rootBlockName, 1);
2797                                                         rootNode.insert(rootBlockNode, node);
2798                                                         rootBlockNode.append(node);
2799                                                 } else
2800                                                         rootBlockNode.append(node);
2801                                         } else {
2802                                                 rootBlockNode = null;
2803                                         }
2804
2805                                         node = next;
2806                                 };
2807                         };
2808
2809                         function createNode(name, type) {
2810                                 var node = new Node(name, type), list;
2811
2812                                 if (name in nodeFilters) {
2813                                         list = matchedNodes[name];
2814
2815                                         if (list)
2816                                                 list.push(node);
2817                                         else
2818                                                 matchedNodes[name] = [node];
2819                                 }
2820
2821                                 return node;
2822                         };
2823
2824                         function removeWhitespaceBefore(node) {
2825                                 var textNode, textVal, sibling;
2826
2827                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
2828                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
2829
2830                                         if (textVal.length > 0) {
2831                                                 textNode.value = textVal;
2832                                                 textNode = textNode.prev;
2833                                         } else {
2834                                                 sibling = textNode.prev;
2835                                                 textNode.remove();
2836                                                 textNode = sibling;
2837                                         }
2838                                 }
2839                         };
2840
2841                         parser = new tinymce.html.SaxParser({
2842                                 validate : validate,
2843                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
2844
2845                                 cdata: function(text) {
2846                                         node.append(createNode('#cdata', 4)).value = text;
2847                                 },
2848
2849                                 text: function(text, raw) {
2850                                         var textNode;
2851
2852                                         // Trim all redundant whitespace on non white space elements
2853                                         if (!whiteSpaceElements[node.name]) {
2854                                                 text = text.replace(allWhiteSpaceRegExp, ' ');
2855
2856                                                 if (node.lastChild && blockElements[node.lastChild.name])
2857                                                         text = text.replace(startWhiteSpaceRegExp, '');
2858                                         }
2859
2860                                         // Do we need to create the node
2861                                         if (text.length !== 0) {
2862                                                 textNode = createNode('#text', 3);
2863                                                 textNode.raw = !!raw;
2864                                                 node.append(textNode).value = text;
2865                                         }
2866                                 },
2867
2868                                 comment: function(text) {
2869                                         node.append(createNode('#comment', 8)).value = text;
2870                                 },
2871
2872                                 pi: function(name, text) {
2873                                         node.append(createNode(name, 7)).value = text;
2874                                         removeWhitespaceBefore(node);
2875                                 },
2876
2877                                 doctype: function(text) {
2878                                         var newNode;
2879                 
2880                                         newNode = node.append(createNode('#doctype', 10));
2881                                         newNode.value = text;
2882                                         removeWhitespaceBefore(node);
2883                                 },
2884
2885                                 start: function(name, attrs, empty) {
2886                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
2887
2888                                         elementRule = validate ? schema.getElementRule(name) : {};
2889                                         if (elementRule) {
2890                                                 newNode = createNode(elementRule.outputName || name, 1);
2891                                                 newNode.attributes = attrs;
2892                                                 newNode.shortEnded = empty;
2893
2894                                                 node.append(newNode);
2895
2896                                                 // Check if node is valid child of the parent node is the child is
2897                                                 // unknown we don't collect it since it's probably a custom element
2898                                                 parent = children[node.name];
2899                                                 if (parent && children[newNode.name] && !parent[newNode.name])
2900                                                         invalidChildren.push(newNode);
2901
2902                                                 attrFiltersLen = attributeFilters.length;
2903                                                 while (attrFiltersLen--) {
2904                                                         attrName = attributeFilters[attrFiltersLen].name;
2905
2906                                                         if (attrName in attrs.map) {
2907                                                                 list = matchedAttributes[attrName];
2908
2909                                                                 if (list)
2910                                                                         list.push(newNode);
2911                                                                 else
2912                                                                         matchedAttributes[attrName] = [newNode];
2913                                                         }
2914                                                 }
2915
2916                                                 // Trim whitespace before block
2917                                                 if (blockElements[name])
2918                                                         removeWhitespaceBefore(newNode);
2919
2920                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />
2921                                                 if (!empty)
2922                                                         node = newNode;
2923                                         }
2924                                 },
2925
2926                                 end: function(name) {
2927                                         var textNode, elementRule, text, sibling, tempNode;
2928
2929                                         elementRule = validate ? schema.getElementRule(name) : {};
2930                                         if (elementRule) {
2931                                                 if (blockElements[name]) {
2932                                                         if (!whiteSpaceElements[node.name]) {
2933                                                                 // Trim whitespace at beginning of block
2934                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
2935                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');
2936
2937                                                                         if (text.length > 0) {
2938                                                                                 textNode.value = text;
2939                                                                                 textNode = textNode.next;
2940                                                                         } else {
2941                                                                                 sibling = textNode.next;
2942                                                                                 textNode.remove();
2943                                                                                 textNode = sibling;
2944                                                                         }
2945                                                                 }
2946
2947                                                                 // Trim whitespace at end of block
2948                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
2949                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');
2950
2951                                                                         if (text.length > 0) {
2952                                                                                 textNode.value = text;
2953                                                                                 textNode = textNode.prev;
2954                                                                         } else {
2955                                                                                 sibling = textNode.prev;
2956                                                                                 textNode.remove();
2957                                                                                 textNode = sibling;
2958                                                                         }
2959                                                                 }
2960                                                         }
2961
2962                                                         // Trim start white space
2963                                                         textNode = node.prev;
2964                                                         if (textNode && textNode.type === 3) {
2965                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
2966
2967                                                                 if (text.length > 0)
2968                                                                         textNode.value = text;
2969                                                                 else
2970                                                                         textNode.remove();
2971                                                         }
2972                                                 }
2973
2974                                                 // Handle empty nodes
2975                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {
2976                                                         if (node.isEmpty(nonEmptyElements)) {
2977                                                                 if (elementRule.paddEmpty)
2978                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';
2979                                                                 else {
2980                                                                         // Leave nodes that have a name like <a name="name">
2981                                                                         if (!node.attributes.map.name) {
2982                                                                                 tempNode = node.parent;
2983                                                                                 node.empty().remove();
2984                                                                                 node = tempNode;
2985                                                                                 return;
2986                                                                         }
2987                                                                 }
2988                                                         }
2989                                                 }
2990
2991                                                 node = node.parent;
2992                                         }
2993                                 }
2994                         }, schema);
2995
2996                         rootNode = node = new Node(args.context || settings.root_name, 11);
2997
2998                         parser.parse(html);
2999
3000                         // Fix invalid children or report invalid children in a contextual parsing
3001                         if (validate && invalidChildren.length) {
3002                                 if (!args.context)
3003                                         fixInvalidChildren(invalidChildren);
3004                                 else
3005                                         args.invalid = true;
3006                         }
3007
3008                         // Wrap nodes in the root into block elements if the root is body
3009                         if (rootBlockName && rootNode.name == 'body')
3010                                 addRootBlocks();
3011
3012                         // Run filters only when the contents is valid
3013                         if (!args.invalid) {
3014                                 // Run node filters
3015                                 for (name in matchedNodes) {
3016                                         list = nodeFilters[name];
3017                                         nodes = matchedNodes[name];
3018
3019                                         // Remove already removed children
3020                                         fi = nodes.length;
3021                                         while (fi--) {
3022                                                 if (!nodes[fi].parent)
3023                                                         nodes.splice(fi, 1);
3024                                         }
3025
3026                                         for (i = 0, l = list.length; i < l; i++)
3027                                                 list[i](nodes, name, args);
3028                                 }
3029
3030                                 // Run attribute filters
3031                                 for (i = 0, l = attributeFilters.length; i < l; i++) {
3032                                         list = attributeFilters[i];
3033
3034                                         if (list.name in matchedAttributes) {
3035                                                 nodes = matchedAttributes[list.name];
3036
3037                                                 // Remove already removed children
3038                                                 fi = nodes.length;
3039                                                 while (fi--) {
3040                                                         if (!nodes[fi].parent)
3041                                                                 nodes.splice(fi, 1);
3042                                                 }
3043
3044                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3045                                                         list.callbacks[fi](nodes, list.name, args);
3046                                         }
3047                                 }
3048                         }
3049
3050                         return rootNode;
3051                 };
3052
3053                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
3054                 // make it possible to place the caret inside empty blocks. This logic tries to remove
3055                 // these elements and keep br elements that where intended to be there intact
3056                 if (settings.remove_trailing_brs) {
3057                         self.addNodeFilter('br', function(nodes, name) {
3058                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
3059                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
3060
3061                                 // Remove brs from body element as well
3062                                 blockElements.body = 1;
3063
3064                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
3065                                 for (i = 0; i < l; i++) {
3066                                         node = nodes[i];
3067                                         parent = node.parent;
3068
3069                                         if (blockElements[node.parent.name] && node === parent.lastChild) {
3070                                                 // Loop all nodes to the right of the current node and check for other BR elements
3071                                                 // excluding bookmarks since they are invisible
3072                                                 prev = node.prev;
3073                                                 while (prev) {
3074                                                         prevName = prev.name;
3075
3076                                                         // Ignore bookmarks
3077                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
3078                                                                 // Found a non BR element
3079                                                                 if (prevName !== "br")
3080                                                                         break;
3081         
3082                                                                 // Found another br it's a <br><br> structure then don't remove anything
3083                                                                 if (prevName === 'br') {
3084                                                                         node = null;
3085                                                                         break;
3086                                                                 }
3087                                                         }
3088
3089                                                         prev = prev.prev;
3090                                                 }
3091
3092                                                 if (node) {
3093                                                         node.remove();
3094
3095                                                         // Is the parent to be considered empty after we removed the BR
3096                                                         if (parent.isEmpty(nonEmptyElements)) {
3097                                                                 elementRule = schema.getElementRule(parent.name);
3098
3099                                                                 // Remove or padd the element depending on schema rule
3100                                                                 if (elementRule.removeEmpty)
3101                                                                         parent.remove();
3102                                                                 else if (elementRule.paddEmpty) 
3103                                                                         parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3104                                                         }
3105                                                 }
3106                                         }
3107                                 }
3108                         });
3109                 }
3110         }
3111 })(tinymce);
3112
3113 tinymce.html.Writer = function(settings) {
3114         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3115
3116         settings = settings || {};
3117         indent = settings.indent;
3118         indentBefore = tinymce.makeMap(settings.indent_before || '');
3119         indentAfter = tinymce.makeMap(settings.indent_after || '');
3120         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3121         htmlOutput = settings.element_format == "html";
3122
3123         return {
3124                 start: function(name, attrs, empty) {
3125                         var i, l, attr, value;
3126
3127                         if (indent && indentBefore[name] && html.length > 0) {
3128                                 value = html[html.length - 1];
3129
3130                                 if (value.length > 0 && value !== '\n')
3131                                         html.push('\n');
3132                         }
3133
3134                         html.push('<', name);
3135
3136                         if (attrs) {
3137                                 for (i = 0, l = attrs.length; i < l; i++) {
3138                                         attr = attrs[i];
3139                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3140                                 }
3141                         }
3142
3143                         if (!empty || htmlOutput)
3144                                 html[html.length] = '>';
3145                         else
3146                                 html[html.length] = ' />';
3147
3148                         if (empty && indent && indentAfter[name] && html.length > 0) {
3149                                 value = html[html.length - 1];
3150
3151                                 if (value.length > 0 && value !== '\n')
3152                                         html.push('\n');
3153                         }
3154                 },
3155
3156                 end: function(name) {
3157                         var value;
3158
3159                         /*if (indent && indentBefore[name] && html.length > 0) {
3160                                 value = html[html.length - 1];
3161
3162                                 if (value.length > 0 && value !== '\n')
3163                                         html.push('\n');
3164                         }*/
3165
3166                         html.push('</', name, '>');
3167
3168                         if (indent && indentAfter[name] && html.length > 0) {
3169                                 value = html[html.length - 1];
3170
3171                                 if (value.length > 0 && value !== '\n')
3172                                         html.push('\n');
3173                         }
3174                 },
3175
3176                 text: function(text, raw) {
3177                         if (text.length > 0)
3178                                 html[html.length] = raw ? text : encode(text);
3179                 },
3180
3181                 cdata: function(text) {
3182                         html.push('<![CDATA[', text, ']]>');
3183                 },
3184
3185                 comment: function(text) {
3186                         html.push('<!--', text, '-->');
3187                 },
3188
3189                 pi: function(name, text) {
3190                         if (text)
3191                                 html.push('<?', name, ' ', text, '?>');
3192                         else
3193                                 html.push('<?', name, '?>');
3194
3195                         if (indent)
3196                                 html.push('\n');
3197                 },
3198
3199                 doctype: function(text) {
3200                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3201                 },
3202
3203                 reset: function() {
3204                         html.length = 0;
3205                 },
3206
3207                 getContent: function() {
3208                         return html.join('').replace(/\n$/, '');
3209                 }
3210         };
3211 };
3212
3213 (function(tinymce) {
3214         tinymce.html.Serializer = function(settings, schema) {
3215                 var self = this, writer = new tinymce.html.Writer(settings);
3216
3217                 settings = settings || {};
3218                 settings.validate = "validate" in settings ? settings.validate : true;
3219
3220                 self.schema = schema = schema || new tinymce.html.Schema();
3221                 self.writer = writer;
3222
3223                 self.serialize = function(node) {
3224                         var handlers, validate;
3225
3226                         validate = settings.validate;
3227
3228                         handlers = {
3229                                 // #text
3230                                 3: function(node, raw) {
3231                                         writer.text(node.value, node.raw);
3232                                 },
3233
3234                                 // #comment
3235                                 8: function(node) {
3236                                         writer.comment(node.value);
3237                                 },
3238
3239                                 // Processing instruction
3240                                 7: function(node) {
3241                                         writer.pi(node.name, node.value);
3242                                 },
3243
3244                                 // Doctype
3245                                 10: function(node) {
3246                                         writer.doctype(node.value);
3247                                 },
3248
3249                                 // CDATA
3250                                 4: function(node) {
3251                                         writer.cdata(node.value);
3252                                 },
3253
3254                                 // Document fragment
3255                                 11: function(node) {
3256                                         if ((node = node.firstChild)) {
3257                                                 do {
3258                                                         walk(node);
3259                                                 } while (node = node.next);
3260                                         }
3261                                 }
3262                         };
3263
3264                         writer.reset();
3265
3266                         function walk(node) {
3267                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3268
3269                                 if (!handler) {
3270                                         name = node.name;
3271                                         isEmpty = node.shortEnded;
3272                                         attrs = node.attributes;
3273
3274                                         // Sort attributes
3275                                         if (validate && attrs && attrs.length > 1) {
3276                                                 sortedAttrs = [];
3277                                                 sortedAttrs.map = {};
3278
3279                                                 elementRule = schema.getElementRule(node.name);
3280                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3281                                                         attrName = elementRule.attributesOrder[i];
3282
3283                                                         if (attrName in attrs.map) {
3284                                                                 attrValue = attrs.map[attrName];
3285                                                                 sortedAttrs.map[attrName] = attrValue;
3286                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3287                                                         }
3288                                                 }
3289
3290                                                 for (i = 0, l = attrs.length; i < l; i++) {
3291                                                         attrName = attrs[i].name;
3292
3293                                                         if (!(attrName in sortedAttrs.map)) {
3294                                                                 attrValue = attrs.map[attrName];
3295                                                                 sortedAttrs.map[attrName] = attrValue;
3296                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3297                                                         }
3298                                                 }
3299
3300                                                 attrs = sortedAttrs;
3301                                         }
3302
3303                                         writer.start(node.name, attrs, isEmpty);
3304
3305                                         if (!isEmpty) {
3306                                                 if ((node = node.firstChild)) {
3307                                                         do {
3308                                                                 walk(node);
3309                                                         } while (node = node.next);
3310                                                 }
3311
3312                                                 writer.end(name);
3313                                         }
3314                                 } else
3315                                         handler(node);
3316                         }
3317
3318                         // Serialize element and treat all non elements as fragments
3319                         if (node.type == 1 && !settings.inner)
3320                                 walk(node);
3321                         else
3322                                 handlers[11](node);
3323
3324                         return writer.getContent();
3325                 };
3326         }
3327 })(tinymce);
3328
3329 (function(tinymce) {
3330         // Shorten names
3331         var each = tinymce.each,
3332                 is = tinymce.is,
3333                 isWebKit = tinymce.isWebKit,
3334                 isIE = tinymce.isIE,
3335                 Entities = tinymce.html.Entities,
3336                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
3337                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
3338                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
3339
3340         tinymce.create('tinymce.dom.DOMUtils', {
3341                 doc : null,
3342                 root : null,
3343                 files : null,
3344                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3345                 props : {
3346                         "for" : "htmlFor",
3347                         "class" : "className",
3348                         className : "className",
3349                         checked : "checked",
3350                         disabled : "disabled",
3351                         maxlength : "maxLength",
3352                         readonly : "readOnly",
3353                         selected : "selected",
3354                         value : "value",
3355                         id : "id",
3356                         name : "name",
3357                         type : "type"
3358                 },
3359
3360                 DOMUtils : function(d, s) {
3361                         var t = this, globalStyle, name;
3362
3363                         t.doc = d;
3364                         t.win = window;
3365                         t.files = {};
3366                         t.cssFlicker = false;
3367                         t.counter = 0;
3368                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3369                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3370                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
3371
3372                         t.settings = s = tinymce.extend({
3373                                 keep_values : false,
3374                                 hex_colors : 1
3375                         }, s);
3376                         
3377                         t.schema = s.schema;
3378                         t.styles = new tinymce.html.Styles({
3379                                 url_converter : s.url_converter,
3380                                 url_converter_scope : s.url_converter_scope
3381                         }, s.schema);
3382
3383                         // Fix IE6SP2 flicker and check it failed for pre SP2
3384                         if (tinymce.isIE6) {
3385                                 try {
3386                                         d.execCommand('BackgroundImageCache', false, true);
3387                                 } catch (e) {
3388                                         t.cssFlicker = true;
3389                                 }
3390                         }
3391
3392                         if (isIE && s.schema) {
3393                                 // Add missing HTML 4/5 elements to IE
3394                                 ('abbr article aside audio canvas ' +
3395                                 'details figcaption figure footer ' +
3396                                 'header hgroup mark menu meter nav ' +
3397                                 'output progress section summary ' +
3398                                 'time video').replace(/\w+/g, function(name) {
3399                                         d.createElement(name);
3400                                 });
3401
3402                                 // Create all custom elements
3403                                 for (name in s.schema.getCustomElements()) {
3404                                         d.createElement(name);
3405                                 }
3406                         }
3407
3408                         tinymce.addUnload(t.destroy, t);
3409                 },
3410
3411                 getRoot : function() {
3412                         var t = this, s = t.settings;
3413
3414                         return (s && t.get(s.root_element)) || t.doc.body;
3415                 },
3416
3417                 getViewPort : function(w) {
3418                         var d, b;
3419
3420                         w = !w ? this.win : w;
3421                         d = w.document;
3422                         b = this.boxModel ? d.documentElement : d.body;
3423
3424                         // Returns viewport size excluding scrollbars
3425                         return {
3426                                 x : w.pageXOffset || b.scrollLeft,
3427                                 y : w.pageYOffset || b.scrollTop,
3428                                 w : w.innerWidth || b.clientWidth,
3429                                 h : w.innerHeight || b.clientHeight
3430                         };
3431                 },
3432
3433                 getRect : function(e) {
3434                         var p, t = this, sr;
3435
3436                         e = t.get(e);
3437                         p = t.getPos(e);
3438                         sr = t.getSize(e);
3439
3440                         return {
3441                                 x : p.x,
3442                                 y : p.y,
3443                                 w : sr.w,
3444                                 h : sr.h
3445                         };
3446                 },
3447
3448                 getSize : function(e) {
3449                         var t = this, w, h;
3450
3451                         e = t.get(e);
3452                         w = t.getStyle(e, 'width');
3453                         h = t.getStyle(e, 'height');
3454
3455                         // Non pixel value, then force offset/clientWidth
3456                         if (w.indexOf('px') === -1)
3457                                 w = 0;
3458
3459                         // Non pixel value, then force offset/clientWidth
3460                         if (h.indexOf('px') === -1)
3461                                 h = 0;
3462
3463                         return {
3464                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3465                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
3466                         };
3467                 },
3468
3469                 getParent : function(n, f, r) {
3470                         return this.getParents(n, f, r, false);
3471                 },
3472
3473                 getParents : function(n, f, r, c) {
3474                         var t = this, na, se = t.settings, o = [];
3475
3476                         n = t.get(n);
3477                         c = c === undefined;
3478
3479                         if (se.strict_root)
3480                                 r = r || t.getRoot();
3481
3482                         // Wrap node name as func
3483                         if (is(f, 'string')) {
3484                                 na = f;
3485
3486                                 if (f === '*') {
3487                                         f = function(n) {return n.nodeType == 1;};
3488                                 } else {
3489                                         f = function(n) {
3490                                                 return t.is(n, na);
3491                                         };
3492                                 }
3493                         }
3494
3495                         while (n) {
3496                                 if (n == r || !n.nodeType || n.nodeType === 9)
3497                                         break;
3498
3499                                 if (!f || f(n)) {
3500                                         if (c)
3501                                                 o.push(n);
3502                                         else
3503                                                 return n;
3504                                 }
3505
3506                                 n = n.parentNode;
3507                         }
3508
3509                         return c ? o : null;
3510                 },
3511
3512                 get : function(e) {
3513                         var n;
3514
3515                         if (e && this.doc && typeof(e) == 'string') {
3516                                 n = e;
3517                                 e = this.doc.getElementById(e);
3518
3519                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3520                                 if (e && e.id !== n)
3521                                         return this.doc.getElementsByName(n)[1];
3522                         }
3523
3524                         return e;
3525                 },
3526
3527                 getNext : function(node, selector) {
3528                         return this._findSib(node, selector, 'nextSibling');
3529                 },
3530
3531                 getPrev : function(node, selector) {
3532                         return this._findSib(node, selector, 'previousSibling');
3533                 },
3534
3535
3536                 select : function(pa, s) {
3537                         var t = this;
3538
3539                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
3540                 },
3541
3542                 is : function(n, selector) {
3543                         var i;
3544
3545                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
3546                         if (n.length === undefined) {
3547                                 // Simple all selector
3548                                 if (selector === '*')
3549                                         return n.nodeType == 1;
3550
3551                                 // Simple selector just elements
3552                                 if (simpleSelectorRe.test(selector)) {
3553                                         selector = selector.toLowerCase().split(/,/);
3554                                         n = n.nodeName.toLowerCase();
3555
3556                                         for (i = selector.length - 1; i >= 0; i--) {
3557                                                 if (selector[i] == n)
3558                                                         return true;
3559                                         }
3560
3561                                         return false;
3562                                 }
3563                         }
3564
3565                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
3566                 },
3567
3568
3569                 add : function(p, n, a, h, c) {
3570                         var t = this;
3571
3572                         return this.run(p, function(p) {
3573                                 var e, k;
3574
3575                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
3576                                 t.setAttribs(e, a);
3577
3578                                 if (h) {
3579                                         if (h.nodeType)
3580                                                 e.appendChild(h);
3581                                         else
3582                                                 t.setHTML(e, h);
3583                                 }
3584
3585                                 return !c ? p.appendChild(e) : e;
3586                         });
3587                 },
3588
3589                 create : function(n, a, h) {
3590                         return this.add(this.doc.createElement(n), n, a, h, 1);
3591                 },
3592
3593                 createHTML : function(n, a, h) {
3594                         var o = '', t = this, k;
3595
3596                         o += '<' + n;
3597
3598                         for (k in a) {
3599                                 if (a.hasOwnProperty(k))
3600                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
3601                         }
3602
3603                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3604                         if (typeof(h) != "undefined")
3605                                 return o + '>' + h + '</' + n + '>';
3606
3607                         return o + ' />';
3608                 },
3609
3610                 remove : function(node, keep_children) {
3611                         return this.run(node, function(node) {
3612                                 var child, parent = node.parentNode;
3613
3614                                 if (!parent)
3615                                         return null;
3616
3617                                 if (keep_children) {
3618                                         while (child = node.firstChild) {
3619                                                 // IE 8 will crash if you don't remove completely empty text nodes
3620                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3621                                                         parent.insertBefore(child, node);
3622                                                 else
3623                                                         node.removeChild(child);
3624                                         }
3625                                 }
3626
3627                                 return parent.removeChild(node);
3628                         });
3629                 },
3630
3631                 setStyle : function(n, na, v) {
3632                         var t = this;
3633
3634                         return t.run(n, function(e) {
3635                                 var s, i;
3636
3637                                 s = e.style;
3638
3639                                 // Camelcase it, if needed
3640                                 na = na.replace(/-(\D)/g, function(a, b){
3641                                         return b.toUpperCase();
3642                                 });
3643
3644                                 // Default px suffix on these
3645                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3646                                         v += 'px';
3647
3648                                 switch (na) {
3649                                         case 'opacity':
3650                                                 // IE specific opacity
3651                                                 if (isIE) {
3652                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3653
3654                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
3655                                                                 s.display = 'inline-block';
3656                                                 }
3657
3658                                                 // Fix for older browsers
3659                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3660                                                 break;
3661
3662                                         case 'float':
3663                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
3664                                                 break;
3665                                         
3666                                         default:
3667                                                 s[na] = v || '';
3668                                 }
3669
3670                                 // Force update of the style data
3671                                 if (t.settings.update_styles)
3672                                         t.setAttrib(e, 'data-mce-style');
3673                         });
3674                 },
3675
3676                 getStyle : function(n, na, c) {
3677                         n = this.get(n);
3678
3679                         if (!n)
3680                                 return;
3681
3682                         // Gecko
3683                         if (this.doc.defaultView && c) {
3684                                 // Remove camelcase
3685                                 na = na.replace(/[A-Z]/g, function(a){
3686                                         return '-' + a;
3687                                 });
3688
3689                                 try {
3690                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3691                                 } catch (ex) {
3692                                         // Old safari might fail
3693                                         return null;
3694                                 }
3695                         }
3696
3697                         // Camelcase it, if needed
3698                         na = na.replace(/-(\D)/g, function(a, b){
3699                                 return b.toUpperCase();
3700                         });
3701
3702                         if (na == 'float')
3703                                 na = isIE ? 'styleFloat' : 'cssFloat';
3704
3705                         // IE & Opera
3706                         if (n.currentStyle && c)
3707                                 return n.currentStyle[na];
3708
3709                         return n.style ? n.style[na] : undefined;
3710                 },
3711
3712                 setStyles : function(e, o) {
3713                         var t = this, s = t.settings, ol;
3714
3715                         ol = s.update_styles;
3716                         s.update_styles = 0;
3717
3718                         each(o, function(v, n) {
3719                                 t.setStyle(e, n, v);
3720                         });
3721
3722                         // Update style info
3723                         s.update_styles = ol;
3724                         if (s.update_styles)
3725                                 t.setAttrib(e, s.cssText);
3726                 },
3727
3728                 removeAllAttribs: function(e) {
3729                         return this.run(e, function(e) {
3730                                 var i, attrs = e.attributes;
3731                                 for (i = attrs.length - 1; i >= 0; i--) {
3732                                         e.removeAttributeNode(attrs.item(i));
3733                                 }
3734                         });
3735                 },
3736
3737                 setAttrib : function(e, n, v) {
3738                         var t = this;
3739
3740                         // Whats the point
3741                         if (!e || !n)
3742                                 return;
3743
3744                         // Strict XML mode
3745                         if (t.settings.strict)
3746                                 n = n.toLowerCase();
3747
3748                         return this.run(e, function(e) {
3749                                 var s = t.settings;
3750
3751                                 switch (n) {
3752                                         case "style":
3753                                                 if (!is(v, 'string')) {
3754                                                         each(v, function(v, n) {
3755                                                                 t.setStyle(e, n, v);
3756                                                         });
3757
3758                                                         return;
3759                                                 }
3760
3761                                                 // No mce_style for elements with these since they might get resized by the user
3762                                                 if (s.keep_values) {
3763                                                         if (v && !t._isRes(v))
3764                                                                 e.setAttribute('data-mce-style', v, 2);
3765                                                         else
3766                                                                 e.removeAttribute('data-mce-style', 2);
3767                                                 }
3768
3769                                                 e.style.cssText = v;
3770                                                 break;
3771
3772                                         case "class":
3773                                                 e.className = v || ''; // Fix IE null bug
3774                                                 break;
3775
3776                                         case "src":
3777                                         case "href":
3778                                                 if (s.keep_values) {
3779                                                         if (s.url_converter)
3780                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3781
3782                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
3783                                                 }
3784
3785                                                 break;
3786
3787                                         case "shape":
3788                                                 e.setAttribute('data-mce-style', v);
3789                                                 break;
3790                                 }
3791
3792                                 if (is(v) && v !== null && v.length !== 0)
3793                                         e.setAttribute(n, '' + v, 2);
3794                                 else
3795                                         e.removeAttribute(n, 2);
3796                         });
3797                 },
3798
3799                 setAttribs : function(e, o) {
3800                         var t = this;
3801
3802                         return this.run(e, function(e) {
3803                                 each(o, function(v, n) {
3804                                         t.setAttrib(e, n, v);
3805                                 });
3806                         });
3807                 },
3808
3809                 getAttrib : function(e, n, dv) {
3810                         var v, t = this, undef;
3811
3812                         e = t.get(e);
3813
3814                         if (!e || e.nodeType !== 1)
3815                                 return dv === undef ? false : dv;
3816
3817                         if (!is(dv))
3818                                 dv = '';
3819
3820                         // Try the mce variant for these
3821                         if (/^(src|href|style|coords|shape)$/.test(n)) {
3822                                 v = e.getAttribute("data-mce-" + n);
3823
3824                                 if (v)
3825                                         return v;
3826                         }
3827
3828                         if (isIE && t.props[n]) {
3829                                 v = e[t.props[n]];
3830                                 v = v && v.nodeValue ? v.nodeValue : v;
3831                         }
3832
3833                         if (!v)
3834                                 v = e.getAttribute(n, 2);
3835
3836                         // Check boolean attribs
3837                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
3838                                 if (e[t.props[n]] === true && v === '')
3839                                         return n;
3840
3841                                 return v ? n : '';
3842                         }
3843
3844                         // Inner input elements will override attributes on form elements
3845                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
3846                                 return e.getAttributeNode(n).nodeValue;
3847
3848                         if (n === 'style') {
3849                                 v = v || e.style.cssText;
3850
3851                                 if (v) {
3852                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
3853
3854                                         if (t.settings.keep_values && !t._isRes(v))
3855                                                 e.setAttribute('data-mce-style', v);
3856                                 }
3857                         }
3858
3859                         // Remove Apple and WebKit stuff
3860                         if (isWebKit && n === "class" && v)
3861                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3862
3863                         // Handle IE issues
3864                         if (isIE) {
3865                                 switch (n) {
3866                                         case 'rowspan':
3867                                         case 'colspan':
3868                                                 // IE returns 1 as default value
3869                                                 if (v === 1)
3870                                                         v = '';
3871
3872                                                 break;
3873
3874                                         case 'size':
3875                                                 // IE returns +0 as default value for size
3876                                                 if (v === '+0' || v === 20 || v === 0)
3877                                                         v = '';
3878
3879                                                 break;
3880
3881                                         case 'width':
3882                                         case 'height':
3883                                         case 'vspace':
3884                                         case 'checked':
3885                                         case 'disabled':
3886                                         case 'readonly':
3887                                                 if (v === 0)
3888                                                         v = '';
3889
3890                                                 break;
3891
3892                                         case 'hspace':
3893                                                 // IE returns -1 as default value
3894                                                 if (v === -1)
3895                                                         v = '';
3896
3897                                                 break;
3898
3899                                         case 'maxlength':
3900                                         case 'tabindex':
3901                                                 // IE returns default value
3902                                                 if (v === 32768 || v === 2147483647 || v === '32768')
3903                                                         v = '';
3904
3905                                                 break;
3906
3907                                         case 'multiple':
3908                                         case 'compact':
3909                                         case 'noshade':
3910                                         case 'nowrap':
3911                                                 if (v === 65535)
3912                                                         return n;
3913
3914                                                 return dv;
3915
3916                                         case 'shape':
3917                                                 v = v.toLowerCase();
3918                                                 break;
3919
3920                                         default:
3921                                                 // IE has odd anonymous function for event attributes
3922                                                 if (n.indexOf('on') === 0 && v)
3923                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
3924                                 }
3925                         }
3926
3927                         return (v !== undef && v !== null && v !== '') ? '' + v : dv;
3928                 },
3929
3930                 getPos : function(n, ro) {
3931                         var t = this, x = 0, y = 0, e, d = t.doc, r;
3932
3933                         n = t.get(n);
3934                         ro = ro || d.body;
3935
3936                         if (n) {
3937                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
3938                                 if (n.getBoundingClientRect) {
3939                                         n = n.getBoundingClientRect();
3940                                         e = t.boxModel ? d.documentElement : d.body;
3941
3942                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
3943                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
3944                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
3945                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
3946
3947                                         return {x : x, y : y};
3948                                 }
3949
3950                                 r = n;
3951                                 while (r && r != ro && r.nodeType) {
3952                                         x += r.offsetLeft || 0;
3953                                         y += r.offsetTop || 0;
3954                                         r = r.offsetParent;
3955                                 }
3956
3957                                 r = n.parentNode;
3958                                 while (r && r != ro && r.nodeType) {
3959                                         x -= r.scrollLeft || 0;
3960                                         y -= r.scrollTop || 0;
3961                                         r = r.parentNode;
3962                                 }
3963                         }
3964
3965                         return {x : x, y : y};
3966                 },
3967
3968                 parseStyle : function(st) {
3969                         return this.styles.parse(st);
3970                 },
3971
3972                 serializeStyle : function(o, name) {
3973                         return this.styles.serialize(o, name);
3974                 },
3975
3976                 loadCSS : function(u) {
3977                         var t = this, d = t.doc, head;
3978
3979                         if (!u)
3980                                 u = '';
3981
3982                         head = t.select('head')[0];
3983
3984                         each(u.split(','), function(u) {
3985                                 var link;
3986
3987                                 if (t.files[u])
3988                                         return;
3989
3990                                 t.files[u] = true;
3991                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
3992
3993                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
3994                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
3995                                 // It's ugly but it seems to work fine.
3996                                 if (isIE && d.documentMode && d.recalc) {
3997                                         link.onload = function() {
3998                                                 if (d.recalc)
3999                                                         d.recalc();
4000
4001                                                 link.onload = null;
4002                                         };
4003                                 }
4004
4005                                 head.appendChild(link);
4006                         });
4007                 },
4008
4009                 addClass : function(e, c) {
4010                         return this.run(e, function(e) {
4011                                 var o;
4012
4013                                 if (!c)
4014                                         return 0;
4015
4016                                 if (this.hasClass(e, c))
4017                                         return e.className;
4018
4019                                 o = this.removeClass(e, c);
4020
4021                                 return e.className = (o != '' ? (o + ' ') : '') + c;
4022                         });
4023                 },
4024
4025                 removeClass : function(e, c) {
4026                         var t = this, re;
4027
4028                         return t.run(e, function(e) {
4029                                 var v;
4030
4031                                 if (t.hasClass(e, c)) {
4032                                         if (!re)
4033                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
4034
4035                                         v = e.className.replace(re, ' ');
4036                                         v = tinymce.trim(v != ' ' ? v : '');
4037
4038                                         e.className = v;
4039
4040                                         // Empty class attr
4041                                         if (!v) {
4042                                                 e.removeAttribute('class');
4043                                                 e.removeAttribute('className');
4044                                         }
4045
4046                                         return v;
4047                                 }
4048
4049                                 return e.className;
4050                         });
4051                 },
4052
4053                 hasClass : function(n, c) {
4054                         n = this.get(n);
4055
4056                         if (!n || !c)
4057                                 return false;
4058
4059                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
4060                 },
4061
4062                 show : function(e) {
4063                         return this.setStyle(e, 'display', 'block');
4064                 },
4065
4066                 hide : function(e) {
4067                         return this.setStyle(e, 'display', 'none');
4068                 },
4069
4070                 isHidden : function(e) {
4071                         e = this.get(e);
4072
4073                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
4074                 },
4075
4076                 uniqueId : function(p) {
4077                         return (!p ? 'mce_' : p) + (this.counter++);
4078                 },
4079
4080                 setHTML : function(element, html) {
4081                         var self = this;
4082
4083                         return self.run(element, function(element) {
4084                                 if (isIE) {
4085                                         // Remove all child nodes, IE keeps empty text nodes in DOM
4086                                         while (element.firstChild)
4087                                                 element.removeChild(element.firstChild);
4088
4089                                         try {
4090                                                 // IE will remove comments from the beginning
4091                                                 // unless you padd the contents with something
4092                                                 element.innerHTML = '<br />' + html;
4093                                                 element.removeChild(element.firstChild);
4094                                         } catch (ex) {
4095                                                 // 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
4096                                                 // This seems to fix this problem
4097
4098                                                 // Create new div with HTML contents and a BR infront to keep comments
4099                                                 element = self.create('div');
4100                                                 element.innerHTML = '<br />' + html;
4101
4102                                                 // Add all children from div to target
4103                                                 each (element.childNodes, function(node, i) {
4104                                                         // Skip br element
4105                                                         if (i)
4106                                                                 element.appendChild(node);
4107                                                 });
4108                                         }
4109                                 } else
4110                                         element.innerHTML = html;
4111
4112                                 return html;
4113                         });
4114                 },
4115
4116                 getOuterHTML : function(elm) {
4117                         var doc, self = this;
4118
4119                         elm = self.get(elm);
4120
4121                         if (!elm)
4122                                 return null;
4123
4124                         if (elm.nodeType === 1 && self.hasOuterHTML)
4125                                 return elm.outerHTML;
4126
4127                         doc = (elm.ownerDocument || self.doc).createElement("body");
4128                         doc.appendChild(elm.cloneNode(true));
4129
4130                         return doc.innerHTML;
4131                 },
4132
4133                 setOuterHTML : function(e, h, d) {
4134                         var t = this;
4135
4136                         function setHTML(e, h, d) {
4137                                 var n, tp;
4138
4139                                 tp = d.createElement("body");
4140                                 tp.innerHTML = h;
4141
4142                                 n = tp.lastChild;
4143                                 while (n) {
4144                                         t.insertAfter(n.cloneNode(true), e);
4145                                         n = n.previousSibling;
4146                                 }
4147
4148                                 t.remove(e);
4149                         };
4150
4151                         return this.run(e, function(e) {
4152                                 e = t.get(e);
4153
4154                                 // Only set HTML on elements
4155                                 if (e.nodeType == 1) {
4156                                         d = d || e.ownerDocument || t.doc;
4157
4158                                         if (isIE) {
4159                                                 try {
4160                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
4161                                                         if (isIE && e.nodeType == 1)
4162                                                                 e.outerHTML = h;
4163                                                         else
4164                                                                 setHTML(e, h, d);
4165                                                 } catch (ex) {
4166                                                         // Fix for unknown runtime error
4167                                                         setHTML(e, h, d);
4168                                                 }
4169                                         } else
4170                                                 setHTML(e, h, d);
4171                                 }
4172                         });
4173                 },
4174
4175                 decode : Entities.decode,
4176
4177                 encode : Entities.encodeAllRaw,
4178
4179                 insertAfter : function(node, reference_node) {
4180                         reference_node = this.get(reference_node);
4181
4182                         return this.run(node, function(node) {
4183                                 var parent, nextSibling;
4184
4185                                 parent = reference_node.parentNode;
4186                                 nextSibling = reference_node.nextSibling;
4187
4188                                 if (nextSibling)
4189                                         parent.insertBefore(node, nextSibling);
4190                                 else
4191                                         parent.appendChild(node);
4192
4193                                 return node;
4194                         });
4195                 },
4196
4197                 isBlock : function(node) {
4198                         var type = node.nodeType;
4199
4200                         // If it's a node then check the type and use the nodeName
4201                         if (type)
4202                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
4203
4204                         return !!blockElementsMap[node];
4205                 },
4206
4207                 replace : function(n, o, k) {
4208                         var t = this;
4209
4210                         if (is(o, 'array'))
4211                                 n = n.cloneNode(true);
4212
4213                         return t.run(o, function(o) {
4214                                 if (k) {
4215                                         each(tinymce.grep(o.childNodes), function(c) {
4216                                                 n.appendChild(c);
4217                                         });
4218                                 }
4219
4220                                 return o.parentNode.replaceChild(n, o);
4221                         });
4222                 },
4223
4224                 rename : function(elm, name) {
4225                         var t = this, newElm;
4226
4227                         if (elm.nodeName != name.toUpperCase()) {
4228                                 // Rename block element
4229                                 newElm = t.create(name);
4230
4231                                 // Copy attribs to new block
4232                                 each(t.getAttribs(elm), function(attr_node) {
4233                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4234                                 });
4235
4236                                 // Replace block
4237                                 t.replace(newElm, elm, 1);
4238                         }
4239
4240                         return newElm || elm;
4241                 },
4242
4243                 findCommonAncestor : function(a, b) {
4244                         var ps = a, pe;
4245
4246                         while (ps) {
4247                                 pe = b;
4248
4249                                 while (pe && ps != pe)
4250                                         pe = pe.parentNode;
4251
4252                                 if (ps == pe)
4253                                         break;
4254
4255                                 ps = ps.parentNode;
4256                         }
4257
4258                         if (!ps && a.ownerDocument)
4259                                 return a.ownerDocument.documentElement;
4260
4261                         return ps;
4262                 },
4263
4264                 toHex : function(s) {
4265                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4266
4267                         function hex(s) {
4268                                 s = parseInt(s).toString(16);
4269
4270                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
4271                         };
4272
4273                         if (c) {
4274                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4275
4276                                 return s;
4277                         }
4278
4279                         return s;
4280                 },
4281
4282                 getClasses : function() {
4283                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4284
4285                         if (t.classes)
4286                                 return t.classes;
4287
4288                         function addClasses(s) {
4289                                 // IE style imports
4290                                 each(s.imports, function(r) {
4291                                         addClasses(r);
4292                                 });
4293
4294                                 each(s.cssRules || s.rules, function(r) {
4295                                         // Real type or fake it on IE
4296                                         switch (r.type || 1) {
4297                                                 // Rule
4298                                                 case 1:
4299                                                         if (r.selectorText) {
4300                                                                 each(r.selectorText.split(','), function(v) {
4301                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
4302
4303                                                                         // Is internal or it doesn't contain a class
4304                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4305                                                                                 return;
4306
4307                                                                         // Remove everything but class name
4308                                                                         ov = v;
4309                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
4310
4311                                                                         // Filter classes
4312                                                                         if (f && !(v = f(v, ov)))
4313                                                                                 return;
4314
4315                                                                         if (!lo[v]) {
4316                                                                                 cl.push({'class' : v});
4317                                                                                 lo[v] = 1;
4318                                                                         }
4319                                                                 });
4320                                                         }
4321                                                         break;
4322
4323                                                 // Import
4324                                                 case 3:
4325                                                         addClasses(r.styleSheet);
4326                                                         break;
4327                                         }
4328                                 });
4329                         };
4330
4331                         try {
4332                                 each(t.doc.styleSheets, addClasses);
4333                         } catch (ex) {
4334                                 // Ignore
4335                         }
4336
4337                         if (cl.length > 0)
4338                                 t.classes = cl;
4339
4340                         return cl;
4341                 },
4342
4343                 run : function(e, f, s) {
4344                         var t = this, o;
4345
4346                         if (t.doc && typeof(e) === 'string')
4347                                 e = t.get(e);
4348
4349                         if (!e)
4350                                 return false;
4351
4352                         s = s || this;
4353                         if (!e.nodeType && (e.length || e.length === 0)) {
4354                                 o = [];
4355
4356                                 each(e, function(e, i) {
4357                                         if (e) {
4358                                                 if (typeof(e) == 'string')
4359                                                         e = t.doc.getElementById(e);
4360
4361                                                 o.push(f.call(s, e, i));
4362                                         }
4363                                 });
4364
4365                                 return o;
4366                         }
4367
4368                         return f.call(s, e);
4369                 },
4370
4371                 getAttribs : function(n) {
4372                         var o;
4373
4374                         n = this.get(n);
4375
4376                         if (!n)
4377                                 return [];
4378
4379                         if (isIE) {
4380                                 o = [];
4381
4382                                 // Object will throw exception in IE
4383                                 if (n.nodeName == 'OBJECT')
4384                                         return n.attributes;
4385
4386                                 // IE doesn't keep the selected attribute if you clone option elements
4387                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4388                                         o.push({specified : 1, nodeName : 'selected'});
4389
4390                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
4391                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
4392                                         o.push({specified : 1, nodeName : a});
4393                                 });
4394
4395                                 return o;
4396                         }
4397
4398                         return n.attributes;
4399                 },
4400
4401                 isEmpty : function(node, elements) {
4402                         var self = this, i, attributes, type, walker, name;
4403
4404                         node = node.firstChild;
4405                         if (node) {
4406                                 walker = new tinymce.dom.TreeWalker(node);
4407                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4408
4409                                 do {
4410                                         type = node.nodeType;
4411
4412                                         if (type === 1) {
4413                                                 // Ignore bogus elements
4414                                                 if (node.getAttribute('data-mce-bogus'))
4415                                                         continue;
4416
4417                                                 // Keep empty elements like <img />
4418                                                 if (elements && elements[node.nodeName.toLowerCase()])
4419                                                         return false;
4420
4421                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
4422                                                 attributes = self.getAttribs(node);
4423                                                 i = node.attributes.length;
4424                                                 while (i--) {
4425                                                         name = node.attributes[i].nodeName;
4426                                                         if (name === "name" || name === 'data-mce-bookmark')
4427                                                                 return false;
4428                                                 }
4429                                         }
4430
4431                                         // Keep non whitespace text nodes
4432                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4433                                                 return false;
4434                                 } while (node = walker.next());
4435                         }
4436
4437                         return true;
4438                 },
4439
4440                 destroy : function(s) {
4441                         var t = this;
4442
4443                         if (t.events)
4444                                 t.events.destroy();
4445
4446                         t.win = t.doc = t.root = t.events = null;
4447
4448                         // Manual destroy then remove unload handler
4449                         if (!s)
4450                                 tinymce.removeUnload(t.destroy);
4451                 },
4452
4453                 createRng : function() {
4454                         var d = this.doc;
4455
4456                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4457                 },
4458
4459                 nodeIndex : function(node, normalized) {
4460                         var idx = 0, lastNodeType, lastNode, nodeType;
4461
4462                         if (node) {
4463                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4464                                         nodeType = node.nodeType;
4465
4466                                         // Normalize text nodes
4467                                         if (normalized && nodeType == 3) {
4468                                                 if (nodeType == lastNodeType || !node.nodeValue.length)
4469                                                         continue;
4470                                         }
4471                                         idx++;
4472                                         lastNodeType = nodeType;
4473                                 }
4474                         }
4475
4476                         return idx;
4477                 },
4478
4479                 split : function(pe, e, re) {
4480                         var t = this, r = t.createRng(), bef, aft, pa;
4481
4482                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
4483                         // but we don't want that in our code since it serves no purpose for the end user
4484                         // For example if this is chopped:
4485                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4486                         // would produce:
4487                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4488                         // this function will then trim of empty edges and produce:
4489                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4490                         function trim(node) {
4491                                 var i, children = node.childNodes, type = node.nodeType;
4492
4493                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4494                                         return;
4495
4496                                 for (i = children.length - 1; i >= 0; i--)
4497                                         trim(children[i]);
4498
4499                                 if (type != 9) {
4500                                         // Keep non whitespace text nodes
4501                                         if (type == 3 && node.nodeValue.length > 0) {
4502                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4503                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4504                                                         return;
4505                                         } else if (type == 1) {
4506                                                 // If the only child is a bookmark then move it up
4507                                                 children = node.childNodes;
4508                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
4509                                                         node.parentNode.insertBefore(children[0], node);
4510
4511                                                 // Keep non empty elements or img, hr etc
4512                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4513                                                         return;
4514                                         }
4515
4516                                         t.remove(node);
4517                                 }
4518
4519                                 return node;
4520                         };
4521
4522                         if (pe && e) {
4523                                 // Get before chunk
4524                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
4525                                 r.setEnd(e.parentNode, t.nodeIndex(e));
4526                                 bef = r.extractContents();
4527
4528                                 // Get after chunk
4529                                 r = t.createRng();
4530                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
4531                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
4532                                 aft = r.extractContents();
4533
4534                                 // Insert before chunk
4535                                 pa = pe.parentNode;
4536                                 pa.insertBefore(trim(bef), pe);
4537
4538                                 // Insert middle chunk
4539                                 if (re)
4540                                         pa.replaceChild(re, e);
4541                                 else
4542                                         pa.insertBefore(e, pe);
4543
4544                                 // Insert after chunk
4545                                 pa.insertBefore(trim(aft), pe);
4546                                 t.remove(pe);
4547
4548                                 return re || e;
4549                         }
4550                 },
4551
4552                 bind : function(target, name, func, scope) {
4553                         var t = this;
4554
4555                         if (!t.events)
4556                                 t.events = new tinymce.dom.EventUtils();
4557
4558                         return t.events.add(target, name, func, scope || this);
4559                 },
4560
4561                 unbind : function(target, name, func) {
4562                         var t = this;
4563
4564                         if (!t.events)
4565                                 t.events = new tinymce.dom.EventUtils();
4566
4567                         return t.events.remove(target, name, func);
4568                 },
4569
4570
4571                 _findSib : function(node, selector, name) {
4572                         var t = this, f = selector;
4573
4574                         if (node) {
4575                                 // If expression make a function of it using is
4576                                 if (is(f, 'string')) {
4577                                         f = function(node) {
4578                                                 return t.is(node, selector);
4579                                         };
4580                                 }
4581
4582                                 // Loop all siblings
4583                                 for (node = node[name]; node; node = node[name]) {
4584                                         if (f(node))
4585                                                 return node;
4586                                 }
4587                         }
4588
4589                         return null;
4590                 },
4591
4592                 _isRes : function(c) {
4593                         // Is live resizble element
4594                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
4595                 }
4596
4597                 /*
4598                 walk : function(n, f, s) {
4599                         var d = this.doc, w;
4600
4601                         if (d.createTreeWalker) {
4602                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4603
4604                                 while ((n = w.nextNode()) != null)
4605                                         f.call(s || this, n);
4606                         } else
4607                                 tinymce.walk(n, f, 'childNodes', s);
4608                 }
4609                 */
4610
4611                 /*
4612                 toRGB : function(s) {
4613                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4614
4615                         if (c) {
4616                                 // #FFF -> #FFFFFF
4617                                 if (!is(c[3]))
4618                                         c[3] = c[2] = c[1];
4619
4620                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4621                         }
4622
4623                         return s;
4624                 }
4625                 */
4626         });
4627
4628         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
4629 })(tinymce);
4630
4631 (function(ns) {
4632         // Range constructor
4633         function Range(dom) {
4634                 var t = this,
4635                         doc = dom.doc,
4636                         EXTRACT = 0,
4637                         CLONE = 1,
4638                         DELETE = 2,
4639                         TRUE = true,
4640                         FALSE = false,
4641                         START_OFFSET = 'startOffset',
4642                         START_CONTAINER = 'startContainer',
4643                         END_CONTAINER = 'endContainer',
4644                         END_OFFSET = 'endOffset',
4645                         extend = tinymce.extend,
4646                         nodeIndex = dom.nodeIndex;
4647
4648                 extend(t, {
4649                         // Inital states
4650                         startContainer : doc,
4651                         startOffset : 0,
4652                         endContainer : doc,
4653                         endOffset : 0,
4654                         collapsed : TRUE,
4655                         commonAncestorContainer : doc,
4656
4657                         // Range constants
4658                         START_TO_START : 0,
4659                         START_TO_END : 1,
4660                         END_TO_END : 2,
4661                         END_TO_START : 3,
4662
4663                         // Public methods
4664                         setStart : setStart,
4665                         setEnd : setEnd,
4666                         setStartBefore : setStartBefore,
4667                         setStartAfter : setStartAfter,
4668                         setEndBefore : setEndBefore,
4669                         setEndAfter : setEndAfter,
4670                         collapse : collapse,
4671                         selectNode : selectNode,
4672                         selectNodeContents : selectNodeContents,
4673                         compareBoundaryPoints : compareBoundaryPoints,
4674                         deleteContents : deleteContents,
4675                         extractContents : extractContents,
4676                         cloneContents : cloneContents,
4677                         insertNode : insertNode,
4678                         surroundContents : surroundContents,
4679                         cloneRange : cloneRange
4680                 });
4681
4682                 function setStart(n, o) {
4683                         _setEndPoint(TRUE, n, o);
4684                 };
4685
4686                 function setEnd(n, o) {
4687                         _setEndPoint(FALSE, n, o);
4688                 };
4689
4690                 function setStartBefore(n) {
4691                         setStart(n.parentNode, nodeIndex(n));
4692                 };
4693
4694                 function setStartAfter(n) {
4695                         setStart(n.parentNode, nodeIndex(n) + 1);
4696                 };
4697
4698                 function setEndBefore(n) {
4699                         setEnd(n.parentNode, nodeIndex(n));
4700                 };
4701
4702                 function setEndAfter(n) {
4703                         setEnd(n.parentNode, nodeIndex(n) + 1);
4704                 };
4705
4706                 function collapse(ts) {
4707                         if (ts) {
4708                                 t[END_CONTAINER] = t[START_CONTAINER];
4709                                 t[END_OFFSET] = t[START_OFFSET];
4710                         } else {
4711                                 t[START_CONTAINER] = t[END_CONTAINER];
4712                                 t[START_OFFSET] = t[END_OFFSET];
4713                         }
4714
4715                         t.collapsed = TRUE;
4716                 };
4717
4718                 function selectNode(n) {
4719                         setStartBefore(n);
4720                         setEndAfter(n);
4721                 };
4722
4723                 function selectNodeContents(n) {
4724                         setStart(n, 0);
4725                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
4726                 };
4727
4728                 function compareBoundaryPoints(h, r) {
4729                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
4730                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
4731
4732                         // Check START_TO_START
4733                         if (h === 0)
4734                                 return _compareBoundaryPoints(sc, so, rsc, rso);
4735         
4736                         // Check START_TO_END
4737                         if (h === 1)
4738                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
4739         
4740                         // Check END_TO_END
4741                         if (h === 2)
4742                                 return _compareBoundaryPoints(ec, eo, rec, reo);
4743         
4744                         // Check END_TO_START
4745                         if (h === 3) 
4746                                 return _compareBoundaryPoints(sc, so, rec, reo);
4747                 };
4748
4749                 function deleteContents() {
4750                         _traverse(DELETE);
4751                 };
4752
4753                 function extractContents() {
4754                         return _traverse(EXTRACT);
4755                 };
4756
4757                 function cloneContents() {
4758                         return _traverse(CLONE);
4759                 };
4760
4761                 function insertNode(n) {
4762                         var startContainer = this[START_CONTAINER],
4763                                 startOffset = this[START_OFFSET], nn, o;
4764
4765                         // Node is TEXT_NODE or CDATA
4766                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
4767                                 if (!startOffset) {
4768                                         // At the start of text
4769                                         startContainer.parentNode.insertBefore(n, startContainer);
4770                                 } else if (startOffset >= startContainer.nodeValue.length) {
4771                                         // At the end of text
4772                                         dom.insertAfter(n, startContainer);
4773                                 } else {
4774                                         // Middle, need to split
4775                                         nn = startContainer.splitText(startOffset);
4776                                         startContainer.parentNode.insertBefore(n, nn);
4777                                 }
4778                         } else {
4779                                 // Insert element node
4780                                 if (startContainer.childNodes.length > 0)
4781                                         o = startContainer.childNodes[startOffset];
4782
4783                                 if (o)
4784                                         startContainer.insertBefore(n, o);
4785                                 else
4786                                         startContainer.appendChild(n);
4787                         }
4788                 };
4789
4790                 function surroundContents(n) {
4791                         var f = t.extractContents();
4792
4793                         t.insertNode(n);
4794                         n.appendChild(f);
4795                         t.selectNode(n);
4796                 };
4797
4798                 function cloneRange() {
4799                         return extend(new Range(dom), {
4800                                 startContainer : t[START_CONTAINER],
4801                                 startOffset : t[START_OFFSET],
4802                                 endContainer : t[END_CONTAINER],
4803                                 endOffset : t[END_OFFSET],
4804                                 collapsed : t.collapsed,
4805                                 commonAncestorContainer : t.commonAncestorContainer
4806                         });
4807                 };
4808
4809                 // Private methods
4810
4811                 function _getSelectedNode(container, offset) {
4812                         var child;
4813
4814                         if (container.nodeType == 3 /* TEXT_NODE */)
4815                                 return container;
4816
4817                         if (offset < 0)
4818                                 return container;
4819
4820                         child = container.firstChild;
4821                         while (child && offset > 0) {
4822                                 --offset;
4823                                 child = child.nextSibling;
4824                         }
4825
4826                         if (child)
4827                                 return child;
4828
4829                         return container;
4830                 };
4831
4832                 function _isCollapsed() {
4833                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
4834                 };
4835
4836                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
4837                         var c, offsetC, n, cmnRoot, childA, childB;
4838                         
4839                         // In the first case the boundary-points have the same container. A is before B
4840                         // if its offset is less than the offset of B, A is equal to B if its offset is
4841                         // equal to the offset of B, and A is after B if its offset is greater than the
4842                         // offset of B.
4843                         if (containerA == containerB) {
4844                                 if (offsetA == offsetB)
4845                                         return 0; // equal
4846
4847                                 if (offsetA < offsetB)
4848                                         return -1; // before
4849
4850                                 return 1; // after
4851                         }
4852
4853                         // In the second case a child node C of the container of A is an ancestor
4854                         // container of B. In this case, A is before B if the offset of A is less than or
4855                         // equal to the index of the child node C and A is after B otherwise.
4856                         c = containerB;
4857                         while (c && c.parentNode != containerA)
4858                                 c = c.parentNode;
4859
4860                         if (c) {
4861                                 offsetC = 0;
4862                                 n = containerA.firstChild;
4863
4864                                 while (n != c && offsetC < offsetA) {
4865                                         offsetC++;
4866                                         n = n.nextSibling;
4867                                 }
4868
4869                                 if (offsetA <= offsetC)
4870                                         return -1; // before
4871
4872                                 return 1; // after
4873                         }
4874
4875                         // In the third case a child node C of the container of B is an ancestor container
4876                         // of A. In this case, A is before B if the index of the child node C is less than
4877                         // the offset of B and A is after B otherwise.
4878                         c = containerA;
4879                         while (c && c.parentNode != containerB) {
4880                                 c = c.parentNode;
4881                         }
4882
4883                         if (c) {
4884                                 offsetC = 0;
4885                                 n = containerB.firstChild;
4886
4887                                 while (n != c && offsetC < offsetB) {
4888                                         offsetC++;
4889                                         n = n.nextSibling;
4890                                 }
4891
4892                                 if (offsetC < offsetB)
4893                                         return -1; // before
4894
4895                                 return 1; // after
4896                         }
4897
4898                         // In the fourth case, none of three other cases hold: the containers of A and B
4899                         // are siblings or descendants of sibling nodes. In this case, A is before B if
4900                         // the container of A is before the container of B in a pre-order traversal of the
4901                         // Ranges' context tree and A is after B otherwise.
4902                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
4903                         childA = containerA;
4904
4905                         while (childA && childA.parentNode != cmnRoot)
4906                                 childA = childA.parentNode;
4907
4908                         if (!childA)
4909                                 childA = cmnRoot;
4910
4911                         childB = containerB;
4912                         while (childB && childB.parentNode != cmnRoot)
4913                                 childB = childB.parentNode;
4914
4915                         if (!childB)
4916                                 childB = cmnRoot;
4917
4918                         if (childA == childB)
4919                                 return 0; // equal
4920
4921                         n = cmnRoot.firstChild;
4922                         while (n) {
4923                                 if (n == childA)
4924                                         return -1; // before
4925
4926                                 if (n == childB)
4927                                         return 1; // after
4928
4929                                 n = n.nextSibling;
4930                         }
4931                 };
4932
4933                 function _setEndPoint(st, n, o) {
4934                         var ec, sc;
4935
4936                         if (st) {
4937                                 t[START_CONTAINER] = n;
4938                                 t[START_OFFSET] = o;
4939                         } else {
4940                                 t[END_CONTAINER] = n;
4941                                 t[END_OFFSET] = o;
4942                         }
4943
4944                         // If one boundary-point of a Range is set to have a root container
4945                         // other than the current one for the Range, the Range is collapsed to
4946                         // the new position. This enforces the restriction that both boundary-
4947                         // points of a Range must have the same root container.
4948                         ec = t[END_CONTAINER];
4949                         while (ec.parentNode)
4950                                 ec = ec.parentNode;
4951
4952                         sc = t[START_CONTAINER];
4953                         while (sc.parentNode)
4954                                 sc = sc.parentNode;
4955
4956                         if (sc == ec) {
4957                                 // The start position of a Range is guaranteed to never be after the
4958                                 // end position. To enforce this restriction, if the start is set to
4959                                 // be at a position after the end, the Range is collapsed to that
4960                                 // position.
4961                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
4962                                         t.collapse(st);
4963                         } else
4964                                 t.collapse(st);
4965
4966                         t.collapsed = _isCollapsed();
4967                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
4968                 };
4969
4970                 function _traverse(how) {
4971                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
4972
4973                         if (t[START_CONTAINER] == t[END_CONTAINER])
4974                                 return _traverseSameContainer(how);
4975
4976                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
4977                                 if (p == t[START_CONTAINER])
4978                                         return _traverseCommonStartContainer(c, how);
4979
4980                                 ++endContainerDepth;
4981                         }
4982
4983                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
4984                                 if (p == t[END_CONTAINER])
4985                                         return _traverseCommonEndContainer(c, how);
4986
4987                                 ++startContainerDepth;
4988                         }
4989
4990                         depthDiff = startContainerDepth - endContainerDepth;
4991
4992                         startNode = t[START_CONTAINER];
4993                         while (depthDiff > 0) {
4994                                 startNode = startNode.parentNode;
4995                                 depthDiff--;
4996                         }
4997
4998                         endNode = t[END_CONTAINER];
4999                         while (depthDiff < 0) {
5000                                 endNode = endNode.parentNode;
5001                                 depthDiff++;
5002                         }
5003
5004                         // ascend the ancestor hierarchy until we have a common parent.
5005                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
5006                                 startNode = sp;
5007                                 endNode = ep;
5008                         }
5009
5010                         return _traverseCommonAncestors(startNode, endNode, how);
5011                 };
5012
5013                  function _traverseSameContainer(how) {
5014                         var frag, s, sub, n, cnt, sibling, xferNode;
5015
5016                         if (how != DELETE)
5017                                 frag = doc.createDocumentFragment();
5018
5019                         // If selection is empty, just return the fragment
5020                         if (t[START_OFFSET] == t[END_OFFSET])
5021                                 return frag;
5022
5023                         // Text node needs special case handling
5024                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
5025                                 // get the substring
5026                                 s = t[START_CONTAINER].nodeValue;
5027                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
5028
5029                                 // set the original text node to its new value
5030                                 if (how != CLONE) {
5031                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
5032
5033                                         // Nothing is partially selected, so collapse to start point
5034                                         t.collapse(TRUE);
5035                                 }
5036
5037                                 if (how == DELETE)
5038                                         return;
5039
5040                                 frag.appendChild(doc.createTextNode(sub));
5041                                 return frag;
5042                         }
5043
5044                         // Copy nodes between the start/end offsets.
5045                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
5046                         cnt = t[END_OFFSET] - t[START_OFFSET];
5047
5048                         while (cnt > 0) {
5049                                 sibling = n.nextSibling;
5050                                 xferNode = _traverseFullySelected(n, how);
5051
5052                                 if (frag)
5053                                         frag.appendChild( xferNode );
5054
5055                                 --cnt;
5056                                 n = sibling;
5057                         }
5058
5059                         // Nothing is partially selected, so collapse to start point
5060                         if (how != CLONE)
5061                                 t.collapse(TRUE);
5062
5063                         return frag;
5064                 };
5065
5066                 function _traverseCommonStartContainer(endAncestor, how) {
5067                         var frag, n, endIdx, cnt, sibling, xferNode;
5068
5069                         if (how != DELETE)
5070                                 frag = doc.createDocumentFragment();
5071
5072                         n = _traverseRightBoundary(endAncestor, how);
5073
5074                         if (frag)
5075                                 frag.appendChild(n);
5076
5077                         endIdx = nodeIndex(endAncestor);
5078                         cnt = endIdx - t[START_OFFSET];
5079
5080                         if (cnt <= 0) {
5081                                 // Collapse to just before the endAncestor, which
5082                                 // is partially selected.
5083                                 if (how != CLONE) {
5084                                         t.setEndBefore(endAncestor);
5085                                         t.collapse(FALSE);
5086                                 }
5087
5088                                 return frag;
5089                         }
5090
5091                         n = endAncestor.previousSibling;
5092                         while (cnt > 0) {
5093                                 sibling = n.previousSibling;
5094                                 xferNode = _traverseFullySelected(n, how);
5095
5096                                 if (frag)
5097                                         frag.insertBefore(xferNode, frag.firstChild);
5098
5099                                 --cnt;
5100                                 n = sibling;
5101                         }
5102
5103                         // Collapse to just before the endAncestor, which
5104                         // is partially selected.
5105                         if (how != CLONE) {
5106                                 t.setEndBefore(endAncestor);
5107                                 t.collapse(FALSE);
5108                         }
5109
5110                         return frag;
5111                 };
5112
5113                 function _traverseCommonEndContainer(startAncestor, how) {
5114                         var frag, startIdx, n, cnt, sibling, xferNode;
5115
5116                         if (how != DELETE)
5117                                 frag = doc.createDocumentFragment();
5118
5119                         n = _traverseLeftBoundary(startAncestor, how);
5120                         if (frag)
5121                                 frag.appendChild(n);
5122
5123                         startIdx = nodeIndex(startAncestor);
5124                         ++startIdx; // Because we already traversed it
5125
5126                         cnt = t[END_OFFSET] - startIdx;
5127                         n = startAncestor.nextSibling;
5128                         while (cnt > 0) {
5129                                 sibling = n.nextSibling;
5130                                 xferNode = _traverseFullySelected(n, how);
5131
5132                                 if (frag)
5133                                         frag.appendChild(xferNode);
5134
5135                                 --cnt;
5136                                 n = sibling;
5137                         }
5138
5139                         if (how != CLONE) {
5140                                 t.setStartAfter(startAncestor);
5141                                 t.collapse(TRUE);
5142                         }
5143
5144                         return frag;
5145                 };
5146
5147                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
5148                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
5149
5150                         if (how != DELETE)
5151                                 frag = doc.createDocumentFragment();
5152
5153                         n = _traverseLeftBoundary(startAncestor, how);
5154                         if (frag)
5155                                 frag.appendChild(n);
5156
5157                         commonParent = startAncestor.parentNode;
5158                         startOffset = nodeIndex(startAncestor);
5159                         endOffset = nodeIndex(endAncestor);
5160                         ++startOffset;
5161
5162                         cnt = endOffset - startOffset;
5163                         sibling = startAncestor.nextSibling;
5164
5165                         while (cnt > 0) {
5166                                 nextSibling = sibling.nextSibling;
5167                                 n = _traverseFullySelected(sibling, how);
5168
5169                                 if (frag)
5170                                         frag.appendChild(n);
5171
5172                                 sibling = nextSibling;
5173                                 --cnt;
5174                         }
5175
5176                         n = _traverseRightBoundary(endAncestor, how);
5177
5178                         if (frag)
5179                                 frag.appendChild(n);
5180
5181                         if (how != CLONE) {
5182                                 t.setStartAfter(startAncestor);
5183                                 t.collapse(TRUE);
5184                         }
5185
5186                         return frag;
5187                 };
5188
5189                 function _traverseRightBoundary(root, how) {
5190                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
5191
5192                         if (next == root)
5193                                 return _traverseNode(next, isFullySelected, FALSE, how);
5194
5195                         parent = next.parentNode;
5196                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
5197
5198                         while (parent) {
5199                                 while (next) {
5200                                         prevSibling = next.previousSibling;
5201                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
5202
5203                                         if (how != DELETE)
5204                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5205
5206                                         isFullySelected = TRUE;
5207                                         next = prevSibling;
5208                                 }
5209
5210                                 if (parent == root)
5211                                         return clonedParent;
5212
5213                                 next = parent.previousSibling;
5214                                 parent = parent.parentNode;
5215
5216                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
5217
5218                                 if (how != DELETE)
5219                                         clonedGrandParent.appendChild(clonedParent);
5220
5221                                 clonedParent = clonedGrandParent;
5222                         }
5223                 };
5224
5225                 function _traverseLeftBoundary(root, how) {
5226                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
5227
5228                         if (next == root)
5229                                 return _traverseNode(next, isFullySelected, TRUE, how);
5230
5231                         parent = next.parentNode;
5232                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
5233
5234                         while (parent) {
5235                                 while (next) {
5236                                         nextSibling = next.nextSibling;
5237                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
5238
5239                                         if (how != DELETE)
5240                                                 clonedParent.appendChild(clonedChild);
5241
5242                                         isFullySelected = TRUE;
5243                                         next = nextSibling;
5244                                 }
5245
5246                                 if (parent == root)
5247                                         return clonedParent;
5248
5249                                 next = parent.nextSibling;
5250                                 parent = parent.parentNode;
5251
5252                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
5253
5254                                 if (how != DELETE)
5255                                         clonedGrandParent.appendChild(clonedParent);
5256
5257                                 clonedParent = clonedGrandParent;
5258                         }
5259                 };
5260
5261                 function _traverseNode(n, isFullySelected, isLeft, how) {
5262                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
5263
5264                         if (isFullySelected)
5265                                 return _traverseFullySelected(n, how);
5266
5267                         if (n.nodeType == 3 /* TEXT_NODE */) {
5268                                 txtValue = n.nodeValue;
5269
5270                                 if (isLeft) {
5271                                         offset = t[START_OFFSET];
5272                                         newNodeValue = txtValue.substring(offset);
5273                                         oldNodeValue = txtValue.substring(0, offset);
5274                                 } else {
5275                                         offset = t[END_OFFSET];
5276                                         newNodeValue = txtValue.substring(0, offset);
5277                                         oldNodeValue = txtValue.substring(offset);
5278                                 }
5279
5280                                 if (how != CLONE)
5281                                         n.nodeValue = oldNodeValue;
5282
5283                                 if (how == DELETE)
5284                                         return;
5285
5286                                 newNode = n.cloneNode(FALSE);
5287                                 newNode.nodeValue = newNodeValue;
5288
5289                                 return newNode;
5290                         }
5291
5292                         if (how == DELETE)
5293                                 return;
5294
5295                         return n.cloneNode(FALSE);
5296                 };
5297
5298                 function _traverseFullySelected(n, how) {
5299                         if (how != DELETE)
5300                                 return how == CLONE ? n.cloneNode(TRUE) : n;
5301
5302                         n.parentNode.removeChild(n);
5303                 };
5304         };
5305
5306         ns.Range = Range;
5307 })(tinymce.dom);
5308
5309 (function() {
5310         function Selection(selection) {
5311                 var self = this, dom = selection.dom, TRUE = true, FALSE = false;
5312
5313                 function getPosition(rng, start) {
5314                         var checkRng, startIndex = 0, endIndex, inside,
5315                                 children, child, offset, index, position = -1, parent;
5316
5317                         // Setup test range, collapse it and get the parent
5318                         checkRng = rng.duplicate();
5319                         checkRng.collapse(start);
5320                         parent = checkRng.parentElement();
5321
5322                         // Check if the selection is within the right document
5323                         if (parent.ownerDocument !== selection.dom.doc)
5324                                 return;
5325
5326                         // IE will report non editable elements as it's parent so look for an editable one
5327                         while (parent.contentEditable === "false") {
5328                                 parent = parent.parentNode;
5329                         }
5330
5331                         // If parent doesn't have any children then return that we are inside the element
5332                         if (!parent.hasChildNodes()) {
5333                                 return {node : parent, inside : 1};
5334                         }
5335
5336                         // Setup node list and endIndex
5337                         children = parent.children;
5338                         endIndex = children.length - 1;
5339
5340                         // Perform a binary search for the position
5341                         while (startIndex <= endIndex) {
5342                                 index = Math.floor((startIndex + endIndex) / 2);
5343
5344                                 // Move selection to node and compare the ranges
5345                                 child = children[index];
5346                                 checkRng.moveToElementText(child);
5347                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
5348
5349                                 // Before/after or an exact match
5350                                 if (position > 0) {
5351                                         endIndex = index - 1;
5352                                 } else if (position < 0) {
5353                                         startIndex = index + 1;
5354                                 } else {
5355                                         return {node : child};
5356                                 }
5357                         }
5358
5359                         // Check if child position is before or we didn't find a position
5360                         if (position < 0) {
5361                                 // No element child was found use the parent element and the offset inside that
5362                                 if (!child) {
5363                                         checkRng.moveToElementText(parent);
5364                                         checkRng.collapse(true);
5365                                         child = parent;
5366                                         inside = true;
5367                                 } else
5368                                         checkRng.collapse(false);
5369
5370                                 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
5371
5372                                 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
5373                                 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
5374                                         checkRng = rng.duplicate();
5375                                         checkRng.collapse(start);
5376
5377                                         offset = -1;
5378                                         while (parent == checkRng.parentElement()) {
5379                                                 if (checkRng.move('character', -1) == 0)
5380                                                         break;
5381
5382                                                 offset++;
5383                                         }
5384                                 }
5385
5386                                 offset = offset || checkRng.text.replace('\r\n', ' ').length;
5387                         } else {
5388                                 // Child position is after the selection endpoint
5389                                 checkRng.collapse(true);
5390                                 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
5391
5392                                 // Get the length of the text to find where the endpoint is relative to it's container
5393                                 offset = checkRng.text.replace('\r\n', ' ').length;
5394                         }
5395
5396                         return {node : child, position : position, offset : offset, inside : inside};
5397                 };
5398
5399                 // Returns a W3C DOM compatible range object by using the IE Range API
5400                 function getRange() {
5401                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
5402
5403                         // If selection is outside the current document just return an empty range
5404                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5405                         if (element.ownerDocument != dom.doc)
5406                                 return domRange;
5407
5408                         collapsed = selection.isCollapsed();
5409
5410                         // Handle control selection
5411                         if (ieRange.item) {
5412                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));
5413                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5414
5415                                 return domRange;
5416                         }
5417
5418                         function findEndPoint(start) {
5419                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
5420
5421                                 container = endPoint.node;
5422                                 offset = endPoint.offset;
5423
5424                                 if (endPoint.inside && !container.hasChildNodes()) {
5425                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);
5426                                         return;
5427                                 }
5428
5429                                 if (offset === undef) {
5430                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
5431                                         return;
5432                                 }
5433
5434                                 if (endPoint.position < 0) {
5435                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;
5436
5437                                         if (!sibling) {
5438                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
5439                                                 return;
5440                                         }
5441
5442                                         if (!offset) {
5443                                                 if (sibling.nodeType == 3)
5444                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
5445                                                 else
5446                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
5447
5448                                                 return;
5449                                         }
5450
5451                                         // Find the text node and offset
5452                                         while (sibling) {
5453                                                 nodeValue = sibling.nodeValue;
5454                                                 textNodeOffset += nodeValue.length;
5455
5456                                                 // We are at or passed the position we where looking for
5457                                                 if (textNodeOffset >= offset) {
5458                                                         container = sibling;
5459                                                         textNodeOffset -= offset;
5460                                                         textNodeOffset = nodeValue.length - textNodeOffset;
5461                                                         break;
5462                                                 }
5463
5464                                                 sibling = sibling.nextSibling;
5465                                         }
5466                                 } else {
5467                                         // Find the text node and offset
5468                                         sibling = container.previousSibling;
5469
5470                                         if (!sibling)
5471                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
5472
5473                                         // If there isn't any text to loop then use the first position
5474                                         if (!offset) {
5475                                                 if (container.nodeType == 3)
5476                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
5477                                                 else
5478                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
5479
5480                                                 return;
5481                                         }
5482
5483                                         while (sibling) {
5484                                                 textNodeOffset += sibling.nodeValue.length;
5485
5486                                                 // We are at or passed the position we where looking for
5487                                                 if (textNodeOffset >= offset) {
5488                                                         container = sibling;
5489                                                         textNodeOffset -= offset;
5490                                                         break;
5491                                                 }
5492
5493                                                 sibling = sibling.previousSibling;
5494                                         }
5495                                 }
5496
5497                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
5498                         };
5499
5500                         try {
5501                                 // Find start point
5502                                 findEndPoint(true);
5503
5504                                 // Find end point if needed
5505                                 if (!collapsed)
5506                                         findEndPoint();
5507                         } catch (ex) {
5508                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you
5509                                 // access the nodeValue or other properties of text nodes. This seems to happend when
5510                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
5511                                 if (ex.number == -2147024809) {
5512                                         // Get the current selection
5513                                         bookmark = self.getBookmark(2);
5514
5515                                         // Get start element
5516                                         tmpRange = ieRange.duplicate();
5517                                         tmpRange.collapse(true);
5518                                         element = tmpRange.parentElement();
5519
5520                                         // Get end element
5521                                         if (!collapsed) {
5522                                                 tmpRange = ieRange.duplicate();
5523                                                 tmpRange.collapse(false);
5524                                                 element2 = tmpRange.parentElement();
5525                                                 element2.innerHTML = element2.innerHTML;
5526                                         }
5527
5528                                         // Remove the broken elements
5529                                         element.innerHTML = element.innerHTML;
5530
5531                                         // Restore the selection
5532                                         self.moveToBookmark(bookmark);
5533
5534                                         // Since the range has moved we need to re-get it
5535                                         ieRange = selection.getRng();
5536
5537                                         // Find start point
5538                                         findEndPoint(true);
5539
5540                                         // Find end point if needed
5541                                         if (!collapsed)
5542                                                 findEndPoint();
5543                                 } else
5544                                         throw ex; // Throw other errors
5545                         }
5546
5547                         return domRange;
5548                 };
5549
5550                 this.getBookmark = function(type) {
5551                         var rng = selection.getRng(), start, end, bookmark = {};
5552
5553                         function getIndexes(node) {
5554                                 var node, parent, root, children, i, indexes = [];
5555
5556                                 parent = node.parentNode;
5557                                 root = dom.getRoot().parentNode;
5558
5559                                 while (parent != root) {
5560                                         children = parent.children;
5561
5562                                         i = children.length;
5563                                         while (i--) {
5564                                                 if (node === children[i]) {
5565                                                         indexes.push(i);
5566                                                         break;
5567                                                 }
5568                                         }
5569
5570                                         node = parent;
5571                                         parent = parent.parentNode;
5572                                 }
5573
5574                                 return indexes;
5575                         };
5576
5577                         function getBookmarkEndPoint(start) {
5578                                 var position;
5579
5580                                 position = getPosition(rng, start);
5581                                 if (position) {
5582                                         return {
5583                                                 position : position.position,
5584                                                 offset : position.offset,
5585                                                 indexes : getIndexes(position.node),
5586                                                 inside : position.inside
5587                                         };
5588                                 }
5589                         };
5590
5591                         // Non ubstructive bookmark
5592                         if (type === 2) {
5593                                 // Handle text selection
5594                                 if (!rng.item) {
5595                                         bookmark.start = getBookmarkEndPoint(true);
5596
5597                                         if (!selection.isCollapsed())
5598                                                 bookmark.end = getBookmarkEndPoint();
5599                                 } else
5600                                         bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
5601                         }
5602
5603                         return bookmark;
5604                 };
5605
5606                 this.moveToBookmark = function(bookmark) {
5607                         var rng, body = dom.doc.body;
5608
5609                         function resolveIndexes(indexes) {
5610                                 var node, i, idx, children;
5611
5612                                 node = dom.getRoot();
5613                                 for (i = indexes.length - 1; i >= 0; i--) {
5614                                         children = node.children;
5615                                         idx = indexes[i];
5616
5617                                         if (idx <= children.length - 1) {
5618                                                 node = children[idx];
5619                                         }
5620                                 }
5621
5622                                 return node;
5623                         };
5624                         
5625                         function setBookmarkEndPoint(start) {
5626                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
5627
5628                                 if (endPoint) {
5629                                         moveLeft = endPoint.position > 0;
5630
5631                                         moveRng = body.createTextRange();
5632                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
5633
5634                                         offset = endPoint.offset;
5635                                         if (offset !== undef) {
5636                                                 moveRng.collapse(endPoint.inside || moveLeft);
5637                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);
5638                                         } else
5639                                                 moveRng.collapse(start);
5640
5641                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
5642
5643                                         if (start)
5644                                                 rng.collapse(true);
5645                                 }
5646                         };
5647
5648                         if (bookmark.start) {
5649                                 if (bookmark.start.ctrl) {
5650                                         rng = body.createControlRange();
5651                                         rng.addElement(resolveIndexes(bookmark.start.indexes));
5652                                         rng.select();
5653                                 } else {
5654                                         rng = body.createTextRange();
5655                                         setBookmarkEndPoint(true);
5656                                         setBookmarkEndPoint();
5657                                         rng.select();
5658                                 }
5659                         }
5660                 };
5661
5662                 this.addRange = function(rng) {
5663                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
5664
5665                         function setEndPoint(start) {
5666                                 var container, offset, marker, tmpRng, nodes;
5667
5668                                 marker = dom.create('a');
5669                                 container = start ? startContainer : endContainer;
5670                                 offset = start ? startOffset : endOffset;
5671                                 tmpRng = ieRng.duplicate();
5672
5673                                 if (container == doc || container == doc.documentElement) {
5674                                         container = body;
5675                                         offset = 0;
5676                                 }
5677
5678                                 if (container.nodeType == 3) {
5679                                         container.parentNode.insertBefore(marker, container);
5680                                         tmpRng.moveToElementText(marker);
5681                                         tmpRng.moveStart('character', offset);
5682                                         dom.remove(marker);
5683                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5684                                 } else {
5685                                         nodes = container.childNodes;
5686
5687                                         if (nodes.length) {
5688                                                 if (offset >= nodes.length) {
5689                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
5690                                                 } else {
5691                                                         container.insertBefore(marker, nodes[offset]);
5692                                                 }
5693
5694                                                 tmpRng.moveToElementText(marker);
5695                                         } else {
5696                                                 // Empty node selection for example <div>|</div>
5697                                                 marker = doc.createTextNode('\uFEFF');
5698                                                 container.appendChild(marker);
5699                                                 tmpRng.moveToElementText(marker.parentNode);
5700                                                 tmpRng.collapse(TRUE);
5701                                         }
5702
5703                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5704                                         dom.remove(marker);
5705                                 }
5706                         }
5707
5708                         // Setup some shorter versions
5709                         startContainer = rng.startContainer;
5710                         startOffset = rng.startOffset;
5711                         endContainer = rng.endContainer;
5712                         endOffset = rng.endOffset;
5713                         ieRng = body.createTextRange();
5714
5715                         // If single element selection then try making a control selection out of it
5716                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
5717                                 if (startOffset == endOffset - 1) {
5718                                         try {
5719                                                 ctrlRng = body.createControlRange();
5720                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);
5721                                                 ctrlRng.select();
5722                                                 return;
5723                                         } catch (ex) {
5724                                                 // Ignore
5725                                         }
5726                                 }
5727                         }
5728
5729                         // Set start/end point of selection
5730                         setEndPoint(true);
5731                         setEndPoint();
5732
5733                         // Select the new range and scroll it into view
5734                         ieRng.select();
5735                 };
5736
5737                 // Expose range method
5738                 this.getRangeAt = getRange;
5739         };
5740
5741         // Expose the selection object
5742         tinymce.dom.TridentSelection = Selection;
5743 })();
5744
5745
5746 /*
5747  * Sizzle CSS Selector Engine - v1.0
5748  *  Copyright 2009, The Dojo Foundation
5749  *  Released under the MIT, BSD, and GPL Licenses.
5750  *  More information: http://sizzlejs.com/
5751  */
5752 (function(){
5753
5754 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
5755         done = 0,
5756         toString = Object.prototype.toString,
5757         hasDuplicate = false,
5758         baseHasDuplicate = true;
5759
5760 // Here we check if the JavaScript engine is using some sort of
5761 // optimization where it does not always call our comparision
5762 // function. If that is the case, discard the hasDuplicate value.
5763 //   Thus far that includes Google Chrome.
5764 [0, 0].sort(function(){
5765         baseHasDuplicate = false;
5766         return 0;
5767 });
5768
5769 var Sizzle = function(selector, context, results, seed) {
5770         results = results || [];
5771         context = context || document;
5772
5773         var origContext = context;
5774
5775         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
5776                 return [];
5777         }
5778         
5779         if ( !selector || typeof selector !== "string" ) {
5780                 return results;
5781         }
5782
5783         var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
5784                 soFar = selector, ret, cur, pop, i;
5785         
5786         // Reset the position of the chunker regexp (start from head)
5787         do {
5788                 chunker.exec("");
5789                 m = chunker.exec(soFar);
5790
5791                 if ( m ) {
5792                         soFar = m[3];
5793                 
5794                         parts.push( m[1] );
5795                 
5796                         if ( m[2] ) {
5797                                 extra = m[3];
5798                                 break;
5799                         }
5800                 }
5801         } while ( m );
5802
5803         if ( parts.length > 1 && origPOS.exec( selector ) ) {
5804                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
5805                         set = posProcess( parts[0] + parts[1], context );
5806                 } else {
5807                         set = Expr.relative[ parts[0] ] ?
5808                                 [ context ] :
5809                                 Sizzle( parts.shift(), context );
5810
5811                         while ( parts.length ) {
5812                                 selector = parts.shift();
5813
5814                                 if ( Expr.relative[ selector ] ) {
5815                                         selector += parts.shift();
5816                                 }
5817                                 
5818                                 set = posProcess( selector, set );
5819                         }
5820                 }
5821         } else {
5822                 // Take a shortcut and set the context if the root selector is an ID
5823                 // (but not if it'll be faster if the inner selector is an ID)
5824                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
5825                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
5826                         ret = Sizzle.find( parts.shift(), context, contextXML );
5827                         context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
5828                 }
5829
5830                 if ( context ) {
5831                         ret = seed ?
5832                                 { expr: parts.pop(), set: makeArray(seed) } :
5833                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
5834                         set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
5835
5836                         if ( parts.length > 0 ) {
5837                                 checkSet = makeArray(set);
5838                         } else {
5839                                 prune = false;
5840                         }
5841
5842                         while ( parts.length ) {
5843                                 cur = parts.pop();
5844                                 pop = cur;
5845
5846                                 if ( !Expr.relative[ cur ] ) {
5847                                         cur = "";
5848                                 } else {
5849                                         pop = parts.pop();
5850                                 }
5851
5852                                 if ( pop == null ) {
5853                                         pop = context;
5854                                 }
5855
5856                                 Expr.relative[ cur ]( checkSet, pop, contextXML );
5857                         }
5858                 } else {
5859                         checkSet = parts = [];
5860                 }
5861         }
5862
5863         if ( !checkSet ) {
5864                 checkSet = set;
5865         }
5866
5867         if ( !checkSet ) {
5868                 Sizzle.error( cur || selector );
5869         }
5870
5871         if ( toString.call(checkSet) === "[object Array]" ) {
5872                 if ( !prune ) {
5873                         results.push.apply( results, checkSet );
5874                 } else if ( context && context.nodeType === 1 ) {
5875                         for ( i = 0; checkSet[i] != null; i++ ) {
5876                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
5877                                         results.push( set[i] );
5878                                 }
5879                         }
5880                 } else {
5881                         for ( i = 0; checkSet[i] != null; i++ ) {
5882                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
5883                                         results.push( set[i] );
5884                                 }
5885                         }
5886                 }
5887         } else {
5888                 makeArray( checkSet, results );
5889         }
5890
5891         if ( extra ) {
5892                 Sizzle( extra, origContext, results, seed );
5893                 Sizzle.uniqueSort( results );
5894         }
5895
5896         return results;
5897 };
5898
5899 Sizzle.uniqueSort = function(results){
5900         if ( sortOrder ) {
5901                 hasDuplicate = baseHasDuplicate;
5902                 results.sort(sortOrder);
5903
5904                 if ( hasDuplicate ) {
5905                         for ( var i = 1; i < results.length; i++ ) {
5906                                 if ( results[i] === results[i-1] ) {
5907                                         results.splice(i--, 1);
5908                                 }
5909                         }
5910                 }
5911         }
5912
5913         return results;
5914 };
5915
5916 Sizzle.matches = function(expr, set){
5917         return Sizzle(expr, null, null, set);
5918 };
5919
5920 Sizzle.find = function(expr, context, isXML){
5921         var set;
5922
5923         if ( !expr ) {
5924                 return [];
5925         }
5926
5927         for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
5928                 var type = Expr.order[i], match;
5929                 
5930                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
5931                         var left = match[1];
5932                         match.splice(1,1);
5933
5934                         if ( left.substr( left.length - 1 ) !== "\\" ) {
5935                                 match[1] = (match[1] || "").replace(/\\/g, "");
5936                                 set = Expr.find[ type ]( match, context, isXML );
5937                                 if ( set != null ) {
5938                                         expr = expr.replace( Expr.match[ type ], "" );
5939                                         break;
5940                                 }
5941                         }
5942                 }
5943         }
5944
5945         if ( !set ) {
5946                 set = context.getElementsByTagName("*");
5947         }
5948
5949         return {set: set, expr: expr};
5950 };
5951
5952 Sizzle.filter = function(expr, set, inplace, not){
5953         var old = expr, result = [], curLoop = set, match, anyFound,
5954                 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
5955
5956         while ( expr && set.length ) {
5957                 for ( var type in Expr.filter ) {
5958                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
5959                                 var filter = Expr.filter[ type ], found, item, left = match[1];
5960                                 anyFound = false;
5961
5962                                 match.splice(1,1);
5963
5964                                 if ( left.substr( left.length - 1 ) === "\\" ) {
5965                                         continue;
5966                                 }
5967
5968                                 if ( curLoop === result ) {
5969                                         result = [];
5970                                 }
5971
5972                                 if ( Expr.preFilter[ type ] ) {
5973                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
5974
5975                                         if ( !match ) {
5976                                                 anyFound = found = true;
5977                                         } else if ( match === true ) {
5978                                                 continue;
5979                                         }
5980                                 }
5981
5982                                 if ( match ) {
5983                                         for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
5984                                                 if ( item ) {
5985                                                         found = filter( item, match, i, curLoop );
5986                                                         var pass = not ^ !!found;
5987
5988                                                         if ( inplace && found != null ) {
5989                                                                 if ( pass ) {
5990                                                                         anyFound = true;
5991                                                                 } else {
5992                                                                         curLoop[i] = false;
5993                                                                 }
5994                                                         } else if ( pass ) {
5995                                                                 result.push( item );
5996                                                                 anyFound = true;
5997                                                         }
5998                                                 }
5999                                         }
6000                                 }
6001
6002                                 if ( found !== undefined ) {
6003                                         if ( !inplace ) {
6004                                                 curLoop = result;
6005                                         }
6006
6007                                         expr = expr.replace( Expr.match[ type ], "" );
6008
6009                                         if ( !anyFound ) {
6010                                                 return [];
6011                                         }
6012
6013                                         break;
6014                                 }
6015                         }
6016                 }
6017
6018                 // Improper expression
6019                 if ( expr === old ) {
6020                         if ( anyFound == null ) {
6021                                 Sizzle.error( expr );
6022                         } else {
6023                                 break;
6024                         }
6025                 }
6026
6027                 old = expr;
6028         }
6029
6030         return curLoop;
6031 };
6032
6033 Sizzle.error = function( msg ) {
6034         throw "Syntax error, unrecognized expression: " + msg;
6035 };
6036
6037 var Expr = Sizzle.selectors = {
6038         order: [ "ID", "NAME", "TAG" ],
6039         match: {
6040                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
6041                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
6042                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
6043                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
6044                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
6045                 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
6046                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
6047                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
6048         },
6049         leftMatch: {},
6050         attrMap: {
6051                 "class": "className",
6052                 "for": "htmlFor"
6053         },
6054         attrHandle: {
6055                 href: function(elem){
6056                         return elem.getAttribute("href");
6057                 }
6058         },
6059         relative: {
6060                 "+": function(checkSet, part){
6061                         var isPartStr = typeof part === "string",
6062                                 isTag = isPartStr && !/\W/.test(part),
6063                                 isPartStrNotTag = isPartStr && !isTag;
6064
6065                         if ( isTag ) {
6066                                 part = part.toLowerCase();
6067                         }
6068
6069                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
6070                                 if ( (elem = checkSet[i]) ) {
6071                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
6072
6073                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
6074                                                 elem || false :
6075                                                 elem === part;
6076                                 }
6077                         }
6078
6079                         if ( isPartStrNotTag ) {
6080                                 Sizzle.filter( part, checkSet, true );
6081                         }
6082                 },
6083                 ">": function(checkSet, part){
6084                         var isPartStr = typeof part === "string",
6085                                 elem, i = 0, l = checkSet.length;
6086
6087                         if ( isPartStr && !/\W/.test(part) ) {
6088                                 part = part.toLowerCase();
6089
6090                                 for ( ; i < l; i++ ) {
6091                                         elem = checkSet[i];
6092                                         if ( elem ) {
6093                                                 var parent = elem.parentNode;
6094                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
6095                                         }
6096                                 }
6097                         } else {
6098                                 for ( ; i < l; i++ ) {
6099                                         elem = checkSet[i];
6100                                         if ( elem ) {
6101                                                 checkSet[i] = isPartStr ?
6102                                                         elem.parentNode :
6103                                                         elem.parentNode === part;
6104                                         }
6105                                 }
6106
6107                                 if ( isPartStr ) {
6108                                         Sizzle.filter( part, checkSet, true );
6109                                 }
6110                         }
6111                 },
6112                 "": function(checkSet, part, isXML){
6113                         var doneName = done++, checkFn = dirCheck, nodeCheck;
6114
6115                         if ( typeof part === "string" && !/\W/.test(part) ) {
6116                                 part = part.toLowerCase();
6117                                 nodeCheck = part;
6118                                 checkFn = dirNodeCheck;
6119                         }
6120
6121                         checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
6122                 },
6123                 "~": function(checkSet, part, isXML){
6124                         var doneName = done++, checkFn = dirCheck, nodeCheck;
6125
6126                         if ( typeof part === "string" && !/\W/.test(part) ) {
6127                                 part = part.toLowerCase();
6128                                 nodeCheck = part;
6129                                 checkFn = dirNodeCheck;
6130                         }
6131
6132                         checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
6133                 }
6134         },
6135         find: {
6136                 ID: function(match, context, isXML){
6137                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
6138                                 var m = context.getElementById(match[1]);
6139                                 return m ? [m] : [];
6140                         }
6141                 },
6142                 NAME: function(match, context){
6143                         if ( typeof context.getElementsByName !== "undefined" ) {
6144                                 var ret = [], results = context.getElementsByName(match[1]);
6145
6146                                 for ( var i = 0, l = results.length; i < l; i++ ) {
6147                                         if ( results[i].getAttribute("name") === match[1] ) {
6148                                                 ret.push( results[i] );
6149                                         }
6150                                 }
6151
6152                                 return ret.length === 0 ? null : ret;
6153                         }
6154                 },
6155                 TAG: function(match, context){
6156                         return context.getElementsByTagName(match[1]);
6157                 }
6158         },
6159         preFilter: {
6160                 CLASS: function(match, curLoop, inplace, result, not, isXML){
6161                         match = " " + match[1].replace(/\\/g, "") + " ";
6162
6163                         if ( isXML ) {
6164                                 return match;
6165                         }
6166
6167                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
6168                                 if ( elem ) {
6169                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
6170                                                 if ( !inplace ) {
6171                                                         result.push( elem );
6172                                                 }
6173                                         } else if ( inplace ) {
6174                                                 curLoop[i] = false;
6175                                         }
6176                                 }
6177                         }
6178
6179                         return false;
6180                 },
6181                 ID: function(match){
6182                         return match[1].replace(/\\/g, "");
6183                 },
6184                 TAG: function(match, curLoop){
6185                         return match[1].toLowerCase();
6186                 },
6187                 CHILD: function(match){
6188                         if ( match[1] === "nth" ) {
6189                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
6190                                 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
6191                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
6192                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
6193
6194                                 // calculate the numbers (first)n+(last) including if they are negative
6195                                 match[2] = (test[1] + (test[2] || 1)) - 0;
6196                                 match[3] = test[3] - 0;
6197                         }
6198
6199                         // TODO: Move to normal caching system
6200                         match[0] = done++;
6201
6202                         return match;
6203                 },
6204                 ATTR: function(match, curLoop, inplace, result, not, isXML){
6205                         var name = match[1].replace(/\\/g, "");
6206                         
6207                         if ( !isXML && Expr.attrMap[name] ) {
6208                                 match[1] = Expr.attrMap[name];
6209                         }
6210
6211                         if ( match[2] === "~=" ) {
6212                                 match[4] = " " + match[4] + " ";
6213                         }
6214
6215                         return match;
6216                 },
6217                 PSEUDO: function(match, curLoop, inplace, result, not){
6218                         if ( match[1] === "not" ) {
6219                                 // If we're dealing with a complex expression, or a simple one
6220                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
6221                                         match[3] = Sizzle(match[3], null, null, curLoop);
6222                                 } else {
6223                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
6224                                         if ( !inplace ) {
6225                                                 result.push.apply( result, ret );
6226                                         }
6227                                         return false;
6228                                 }
6229                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
6230                                 return true;
6231                         }
6232                         
6233                         return match;
6234                 },
6235                 POS: function(match){
6236                         match.unshift( true );
6237                         return match;
6238                 }
6239         },
6240         filters: {
6241                 enabled: function(elem){
6242                         return elem.disabled === false && elem.type !== "hidden";
6243                 },
6244                 disabled: function(elem){
6245                         return elem.disabled === true;
6246                 },
6247                 checked: function(elem){
6248                         return elem.checked === true;
6249                 },
6250                 selected: function(elem){
6251                         // Accessing this property makes selected-by-default
6252                         // options in Safari work properly
6253                         elem.parentNode.selectedIndex;
6254                         return elem.selected === true;
6255                 },
6256                 parent: function(elem){
6257                         return !!elem.firstChild;
6258                 },
6259                 empty: function(elem){
6260                         return !elem.firstChild;
6261                 },
6262                 has: function(elem, i, match){
6263                         return !!Sizzle( match[3], elem ).length;
6264                 },
6265                 header: function(elem){
6266                         return (/h\d/i).test( elem.nodeName );
6267                 },
6268                 text: function(elem){
6269                         return "text" === elem.type;
6270                 },
6271                 radio: function(elem){
6272                         return "radio" === elem.type;
6273                 },
6274                 checkbox: function(elem){
6275                         return "checkbox" === elem.type;
6276                 },
6277                 file: function(elem){
6278                         return "file" === elem.type;
6279                 },
6280                 password: function(elem){
6281                         return "password" === elem.type;
6282                 },
6283                 submit: function(elem){
6284                         return "submit" === elem.type;
6285                 },
6286                 image: function(elem){
6287                         return "image" === elem.type;
6288                 },
6289                 reset: function(elem){
6290                         return "reset" === elem.type;
6291                 },
6292                 button: function(elem){
6293                         return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
6294                 },
6295                 input: function(elem){
6296                         return (/input|select|textarea|button/i).test(elem.nodeName);
6297                 }
6298         },
6299         setFilters: {
6300                 first: function(elem, i){
6301                         return i === 0;
6302                 },
6303                 last: function(elem, i, match, array){
6304                         return i === array.length - 1;
6305                 },
6306                 even: function(elem, i){
6307                         return i % 2 === 0;
6308                 },
6309                 odd: function(elem, i){
6310                         return i % 2 === 1;
6311                 },
6312                 lt: function(elem, i, match){
6313                         return i < match[3] - 0;
6314                 },
6315                 gt: function(elem, i, match){
6316                         return i > match[3] - 0;
6317                 },
6318                 nth: function(elem, i, match){
6319                         return match[3] - 0 === i;
6320                 },
6321                 eq: function(elem, i, match){
6322                         return match[3] - 0 === i;
6323                 }
6324         },
6325         filter: {
6326                 PSEUDO: function(elem, match, i, array){
6327                         var name = match[1], filter = Expr.filters[ name ];
6328
6329                         if ( filter ) {
6330                                 return filter( elem, i, match, array );
6331                         } else if ( name === "contains" ) {
6332                                 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
6333                         } else if ( name === "not" ) {
6334                                 var not = match[3];
6335
6336                                 for ( var j = 0, l = not.length; j < l; j++ ) {
6337                                         if ( not[j] === elem ) {
6338                                                 return false;
6339                                         }
6340                                 }
6341
6342                                 return true;
6343                         } else {
6344                                 Sizzle.error( "Syntax error, unrecognized expression: " + name );
6345                         }
6346                 },
6347                 CHILD: function(elem, match){
6348                         var type = match[1], node = elem;
6349                         switch (type) {
6350                                 case 'only':
6351                                 case 'first':
6352                                         while ( (node = node.previousSibling) )  {
6353                                                 if ( node.nodeType === 1 ) { 
6354                                                         return false; 
6355                                                 }
6356                                         }
6357                                         if ( type === "first" ) { 
6358                                                 return true; 
6359                                         }
6360                                         node = elem;
6361                                 case 'last':
6362                                         while ( (node = node.nextSibling) )      {
6363                                                 if ( node.nodeType === 1 ) { 
6364                                                         return false; 
6365                                                 }
6366                                         }
6367                                         return true;
6368                                 case 'nth':
6369                                         var first = match[2], last = match[3];
6370
6371                                         if ( first === 1 && last === 0 ) {
6372                                                 return true;
6373                                         }
6374                                         
6375                                         var doneName = match[0],
6376                                                 parent = elem.parentNode;
6377         
6378                                         if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
6379                                                 var count = 0;
6380                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {
6381                                                         if ( node.nodeType === 1 ) {
6382                                                                 node.nodeIndex = ++count;
6383                                                         }
6384                                                 } 
6385                                                 parent.sizcache = doneName;
6386                                         }
6387                                         
6388                                         var diff = elem.nodeIndex - last;
6389                                         if ( first === 0 ) {
6390                                                 return diff === 0;
6391                                         } else {
6392                                                 return ( diff % first === 0 && diff / first >= 0 );
6393                                         }
6394                         }
6395                 },
6396                 ID: function(elem, match){
6397                         return elem.nodeType === 1 && elem.getAttribute("id") === match;
6398                 },
6399                 TAG: function(elem, match){
6400                         return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
6401                 },
6402                 CLASS: function(elem, match){
6403                         return (" " + (elem.className || elem.getAttribute("class")) + " ")
6404                                 .indexOf( match ) > -1;
6405                 },
6406                 ATTR: function(elem, match){
6407                         var name = match[1],
6408                                 result = Expr.attrHandle[ name ] ?
6409                                         Expr.attrHandle[ name ]( elem ) :
6410                                         elem[ name ] != null ?
6411                                                 elem[ name ] :
6412                                                 elem.getAttribute( name ),
6413                                 value = result + "",
6414                                 type = match[2],
6415                                 check = match[4];
6416
6417                         return result == null ?
6418                                 type === "!=" :
6419                                 type === "=" ?
6420                                 value === check :
6421                                 type === "*=" ?
6422                                 value.indexOf(check) >= 0 :
6423                                 type === "~=" ?
6424                                 (" " + value + " ").indexOf(check) >= 0 :
6425                                 !check ?
6426                                 value && result !== false :
6427                                 type === "!=" ?
6428                                 value !== check :
6429                                 type === "^=" ?
6430                                 value.indexOf(check) === 0 :
6431                                 type === "$=" ?
6432                                 value.substr(value.length - check.length) === check :
6433                                 type === "|=" ?
6434                                 value === check || value.substr(0, check.length + 1) === check + "-" :
6435                                 false;
6436                 },
6437                 POS: function(elem, match, i, array){
6438                         var name = match[2], filter = Expr.setFilters[ name ];
6439
6440                         if ( filter ) {
6441                                 return filter( elem, i, match, array );
6442                         }
6443                 }
6444         }
6445 };
6446
6447 var origPOS = Expr.match.POS,
6448         fescape = function(all, num){
6449                 return "\\" + (num - 0 + 1);
6450         };
6451
6452 for ( var type in Expr.match ) {
6453         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
6454         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
6455 }
6456
6457 var makeArray = function(array, results) {
6458         array = Array.prototype.slice.call( array, 0 );
6459
6460         if ( results ) {
6461                 results.push.apply( results, array );
6462                 return results;
6463         }
6464         
6465         return array;
6466 };
6467
6468 // Perform a simple check to determine if the browser is capable of
6469 // converting a NodeList to an array using builtin methods.
6470 // Also verifies that the returned array holds DOM nodes
6471 // (which is not the case in the Blackberry browser)
6472 try {
6473         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
6474
6475 // Provide a fallback method if it does not work
6476 } catch(e){
6477         makeArray = function(array, results) {
6478                 var ret = results || [], i = 0;
6479
6480                 if ( toString.call(array) === "[object Array]" ) {
6481                         Array.prototype.push.apply( ret, array );
6482                 } else {
6483                         if ( typeof array.length === "number" ) {
6484                                 for ( var l = array.length; i < l; i++ ) {
6485                                         ret.push( array[i] );
6486                                 }
6487                         } else {
6488                                 for ( ; array[i]; i++ ) {
6489                                         ret.push( array[i] );
6490                                 }
6491                         }
6492                 }
6493
6494                 return ret;
6495         };
6496 }
6497
6498 var sortOrder;
6499
6500 if ( document.documentElement.compareDocumentPosition ) {
6501         sortOrder = function( a, b ) {
6502                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
6503                         if ( a == b ) {
6504                                 hasDuplicate = true;
6505                         }
6506                         return a.compareDocumentPosition ? -1 : 1;
6507                 }
6508
6509                 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
6510                 if ( ret === 0 ) {
6511                         hasDuplicate = true;
6512                 }
6513                 return ret;
6514         };
6515 } else if ( "sourceIndex" in document.documentElement ) {
6516         sortOrder = function( a, b ) {
6517                 if ( !a.sourceIndex || !b.sourceIndex ) {
6518                         if ( a == b ) {
6519                                 hasDuplicate = true;
6520                         }
6521                         return a.sourceIndex ? -1 : 1;
6522                 }
6523
6524                 var ret = a.sourceIndex - b.sourceIndex;
6525                 if ( ret === 0 ) {
6526                         hasDuplicate = true;
6527                 }
6528                 return ret;
6529         };
6530 } else if ( document.createRange ) {
6531         sortOrder = function( a, b ) {
6532                 if ( !a.ownerDocument || !b.ownerDocument ) {
6533                         if ( a == b ) {
6534                                 hasDuplicate = true;
6535                         }
6536                         return a.ownerDocument ? -1 : 1;
6537                 }
6538
6539                 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
6540                 aRange.setStart(a, 0);
6541                 aRange.setEnd(a, 0);
6542                 bRange.setStart(b, 0);
6543                 bRange.setEnd(b, 0);
6544                 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
6545                 if ( ret === 0 ) {
6546                         hasDuplicate = true;
6547                 }
6548                 return ret;
6549         };
6550 }
6551
6552 // Utility function for retreiving the text value of an array of DOM nodes
6553 Sizzle.getText = function( elems ) {
6554         var ret = "", elem;
6555
6556         for ( var i = 0; elems[i]; i++ ) {
6557                 elem = elems[i];
6558
6559                 // Get the text from text nodes and CDATA nodes
6560                 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
6561                         ret += elem.nodeValue;
6562
6563                 // Traverse everything else, except comment nodes
6564                 } else if ( elem.nodeType !== 8 ) {
6565                         ret += Sizzle.getText( elem.childNodes );
6566                 }
6567         }
6568
6569         return ret;
6570 };
6571
6572 // Check to see if the browser returns elements by name when
6573 // querying by getElementById (and provide a workaround)
6574 (function(){
6575         // We're going to inject a fake input element with a specified name
6576         var form = document.createElement("div"),
6577                 id = "script" + (new Date()).getTime();
6578         form.innerHTML = "<a name='" + id + "'/>";
6579
6580         // Inject it into the root element, check its status, and remove it quickly
6581         var root = document.documentElement;
6582         root.insertBefore( form, root.firstChild );
6583
6584         // The workaround has to do additional checks after a getElementById
6585         // Which slows things down for other browsers (hence the branching)
6586         if ( document.getElementById( id ) ) {
6587                 Expr.find.ID = function(match, context, isXML){
6588                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
6589                                 var m = context.getElementById(match[1]);
6590                                 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
6591                         }
6592                 };
6593
6594                 Expr.filter.ID = function(elem, match){
6595                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
6596                         return elem.nodeType === 1 && node && node.nodeValue === match;
6597                 };
6598         }
6599
6600         root.removeChild( form );
6601         root = form = null; // release memory in IE
6602 })();
6603
6604 (function(){
6605         // Check to see if the browser returns only elements
6606         // when doing getElementsByTagName("*")
6607
6608         // Create a fake element
6609         var div = document.createElement("div");
6610         div.appendChild( document.createComment("") );
6611
6612         // Make sure no comments are found
6613         if ( div.getElementsByTagName("*").length > 0 ) {
6614                 Expr.find.TAG = function(match, context){
6615                         var results = context.getElementsByTagName(match[1]);
6616
6617                         // Filter out possible comments
6618                         if ( match[1] === "*" ) {
6619                                 var tmp = [];
6620
6621                                 for ( var i = 0; results[i]; i++ ) {
6622                                         if ( results[i].nodeType === 1 ) {
6623                                                 tmp.push( results[i] );
6624                                         }
6625                                 }
6626
6627                                 results = tmp;
6628                         }
6629
6630                         return results;
6631                 };
6632         }
6633
6634         // Check to see if an attribute returns normalized href attributes
6635         div.innerHTML = "<a href='#'></a>";
6636         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
6637                         div.firstChild.getAttribute("href") !== "#" ) {
6638                 Expr.attrHandle.href = function(elem){
6639                         return elem.getAttribute("href", 2);
6640                 };
6641         }
6642
6643         div = null; // release memory in IE
6644 })();
6645
6646 if ( document.querySelectorAll ) {
6647         (function(){
6648                 var oldSizzle = Sizzle, div = document.createElement("div");
6649                 div.innerHTML = "<p class='TEST'></p>";
6650
6651                 // Safari can't handle uppercase or unicode characters when
6652                 // in quirks mode.
6653                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
6654                         return;
6655                 }
6656         
6657                 Sizzle = function(query, context, extra, seed){
6658                         context = context || document;
6659
6660                         // Only use querySelectorAll on non-XML documents
6661                         // (ID selectors don't work in non-HTML documents)
6662                         if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
6663                                 try {
6664                                         return makeArray( context.querySelectorAll(query), extra );
6665                                 } catch(e){}
6666                         }
6667                 
6668                         return oldSizzle(query, context, extra, seed);
6669                 };
6670
6671                 for ( var prop in oldSizzle ) {
6672                         Sizzle[ prop ] = oldSizzle[ prop ];
6673                 }
6674
6675                 div = null; // release memory in IE
6676         })();
6677 }
6678
6679 (function(){
6680         var div = document.createElement("div");
6681
6682         div.innerHTML = "<div class='test e'></div><div class='test'></div>";
6683
6684         // Opera can't find a second classname (in 9.6)
6685         // Also, make sure that getElementsByClassName actually exists
6686         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
6687                 return;
6688         }
6689
6690         // Safari caches class attributes, doesn't catch changes (in 3.2)
6691         div.lastChild.className = "e";
6692
6693         if ( div.getElementsByClassName("e").length === 1 ) {
6694                 return;
6695         }
6696         
6697         Expr.order.splice(1, 0, "CLASS");
6698         Expr.find.CLASS = function(match, context, isXML) {
6699                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
6700                         return context.getElementsByClassName(match[1]);
6701                 }
6702         };
6703
6704         div = null; // release memory in IE
6705 })();
6706
6707 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6708         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6709                 var elem = checkSet[i];
6710                 if ( elem ) {
6711                         elem = elem[dir];
6712                         var match = false;
6713
6714                         while ( elem ) {
6715                                 if ( elem.sizcache === doneName ) {
6716                                         match = checkSet[elem.sizset];
6717                                         break;
6718                                 }
6719
6720                                 if ( elem.nodeType === 1 && !isXML ){
6721                                         elem.sizcache = doneName;
6722                                         elem.sizset = i;
6723                                 }
6724
6725                                 if ( elem.nodeName.toLowerCase() === cur ) {
6726                                         match = elem;
6727                                         break;
6728                                 }
6729
6730                                 elem = elem[dir];
6731                         }
6732
6733                         checkSet[i] = match;
6734                 }
6735         }
6736 }
6737
6738 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6739         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6740                 var elem = checkSet[i];
6741                 if ( elem ) {
6742                         elem = elem[dir];
6743                         var match = false;
6744
6745                         while ( elem ) {
6746                                 if ( elem.sizcache === doneName ) {
6747                                         match = checkSet[elem.sizset];
6748                                         break;
6749                                 }
6750
6751                                 if ( elem.nodeType === 1 ) {
6752                                         if ( !isXML ) {
6753                                                 elem.sizcache = doneName;
6754                                                 elem.sizset = i;
6755                                         }
6756                                         if ( typeof cur !== "string" ) {
6757                                                 if ( elem === cur ) {
6758                                                         match = true;
6759                                                         break;
6760                                                 }
6761
6762                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
6763                                                 match = elem;
6764                                                 break;
6765                                         }
6766                                 }
6767
6768                                 elem = elem[dir];
6769                         }
6770
6771                         checkSet[i] = match;
6772                 }
6773         }
6774 }
6775
6776 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
6777         return !!(a.compareDocumentPosition(b) & 16);
6778 } : function(a, b){
6779         return a !== b && (a.contains ? a.contains(b) : true);
6780 };
6781
6782 Sizzle.isXML = function(elem){
6783         // documentElement is verified for cases where it doesn't yet exist
6784         // (such as loading iframes in IE - #4833) 
6785         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
6786         return documentElement ? documentElement.nodeName !== "HTML" : false;
6787 };
6788
6789 var posProcess = function(selector, context){
6790         var tmpSet = [], later = "", match,
6791                 root = context.nodeType ? [context] : context;
6792
6793         // Position selectors must be done after the filter
6794         // And so must :not(positional) so we move all PSEUDOs to the end
6795         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
6796                 later += match[0];
6797                 selector = selector.replace( Expr.match.PSEUDO, "" );
6798         }
6799
6800         selector = Expr.relative[selector] ? selector + "*" : selector;
6801
6802         for ( var i = 0, l = root.length; i < l; i++ ) {
6803                 Sizzle( selector, root[i], tmpSet );
6804         }
6805
6806         return Sizzle.filter( later, tmpSet );
6807 };
6808
6809 // EXPOSE
6810
6811 window.tinymce.dom.Sizzle = Sizzle;
6812
6813 })();
6814
6815
6816 (function(tinymce) {
6817         // Shorten names
6818         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
6819
6820         tinymce.create('tinymce.dom.EventUtils', {
6821                 EventUtils : function() {
6822                         this.inits = [];
6823                         this.events = [];
6824                 },
6825
6826                 add : function(o, n, f, s) {
6827                         var cb, t = this, el = t.events, r;
6828
6829                         if (n instanceof Array) {
6830                                 r = [];
6831
6832                                 each(n, function(n) {
6833                                         r.push(t.add(o, n, f, s));
6834                                 });
6835
6836                                 return r;
6837                         }
6838
6839                         // Handle array
6840                         if (o && o.hasOwnProperty && o instanceof Array) {
6841                                 r = [];
6842
6843                                 each(o, function(o) {
6844                                         o = DOM.get(o);
6845                                         r.push(t.add(o, n, f, s));
6846                                 });
6847
6848                                 return r;
6849                         }
6850
6851                         o = DOM.get(o);
6852
6853                         if (!o)
6854                                 return;
6855
6856                         // Setup event callback
6857                         cb = function(e) {
6858                                 // Is all events disabled
6859                                 if (t.disabled)
6860                                         return;
6861
6862                                 e = e || window.event;
6863
6864                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
6865                                 if (e && isIE) {
6866                                         if (!e.target)
6867                                                 e.target = e.srcElement;
6868
6869                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility
6870                                         tinymce.extend(e, t._stoppers);
6871                                 }
6872
6873                                 if (!s)
6874                                         return f(e);
6875
6876                                 return f.call(s, e);
6877                         };
6878
6879                         if (n == 'unload') {
6880                                 tinymce.unloads.unshift({func : cb});
6881                                 return cb;
6882                         }
6883
6884                         if (n == 'init') {
6885                                 if (t.domLoaded)
6886                                         cb();
6887                                 else
6888                                         t.inits.push(cb);
6889
6890                                 return cb;
6891                         }
6892
6893                         // Store away listener reference
6894                         el.push({
6895                                 obj : o,
6896                                 name : n,
6897                                 func : f,
6898                                 cfunc : cb,
6899                                 scope : s
6900                         });
6901
6902                         t._add(o, n, cb);
6903
6904                         return f;
6905                 },
6906
6907                 remove : function(o, n, f) {
6908                         var t = this, a = t.events, s = false, r;
6909
6910                         // Handle array
6911                         if (o && o.hasOwnProperty && o instanceof Array) {
6912                                 r = [];
6913
6914                                 each(o, function(o) {
6915                                         o = DOM.get(o);
6916                                         r.push(t.remove(o, n, f));
6917                                 });
6918
6919                                 return r;
6920                         }
6921
6922                         o = DOM.get(o);
6923
6924                         each(a, function(e, i) {
6925                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
6926                                         a.splice(i, 1);
6927                                         t._remove(o, n, e.cfunc);
6928                                         s = true;
6929                                         return false;
6930                                 }
6931                         });
6932
6933                         return s;
6934                 },
6935
6936                 clear : function(o) {
6937                         var t = this, a = t.events, i, e;
6938
6939                         if (o) {
6940                                 o = DOM.get(o);
6941
6942                                 for (i = a.length - 1; i >= 0; i--) {
6943                                         e = a[i];
6944
6945                                         if (e.obj === o) {
6946                                                 t._remove(e.obj, e.name, e.cfunc);
6947                                                 e.obj = e.cfunc = null;
6948                                                 a.splice(i, 1);
6949                                         }
6950                                 }
6951                         }
6952                 },
6953
6954                 cancel : function(e) {
6955                         if (!e)
6956                                 return false;
6957
6958                         this.stop(e);
6959
6960                         return this.prevent(e);
6961                 },
6962
6963                 stop : function(e) {
6964                         if (e.stopPropagation)
6965                                 e.stopPropagation();
6966                         else
6967                                 e.cancelBubble = true;
6968
6969                         return false;
6970                 },
6971
6972                 prevent : function(e) {
6973                         if (e.preventDefault)
6974                                 e.preventDefault();
6975                         else
6976                                 e.returnValue = false;
6977
6978                         return false;
6979                 },
6980
6981                 destroy : function() {
6982                         var t = this;
6983
6984                         each(t.events, function(e, i) {
6985                                 t._remove(e.obj, e.name, e.cfunc);
6986                                 e.obj = e.cfunc = null;
6987                         });
6988
6989                         t.events = [];
6990                         t = null;
6991                 },
6992
6993                 _add : function(o, n, f) {
6994                         if (o.attachEvent)
6995                                 o.attachEvent('on' + n, f);
6996                         else if (o.addEventListener)
6997                                 o.addEventListener(n, f, false);
6998                         else
6999                                 o['on' + n] = f;
7000                 },
7001
7002                 _remove : function(o, n, f) {
7003                         if (o) {
7004                                 try {
7005                                         if (o.detachEvent)
7006                                                 o.detachEvent('on' + n, f);
7007                                         else if (o.removeEventListener)
7008                                                 o.removeEventListener(n, f, false);
7009                                         else
7010                                                 o['on' + n] = null;
7011                                 } catch (ex) {
7012                                         // Might fail with permission denined on IE so we just ignore that
7013                                 }
7014                         }
7015                 },
7016
7017                 _pageInit : function(win) {
7018                         var t = this;
7019
7020                         // Keep it from running more than once
7021                         if (t.domLoaded)
7022                                 return;
7023
7024                         t.domLoaded = true;
7025
7026                         each(t.inits, function(c) {
7027                                 c();
7028                         });
7029
7030                         t.inits = [];
7031                 },
7032
7033                 _wait : function(win) {
7034                         var t = this, doc = win.document;
7035
7036                         // No need since the document is already loaded
7037                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
7038                                 t.domLoaded = 1;
7039                                 return;
7040                         }
7041
7042                         // Use IE method
7043                         if (doc.attachEvent) {
7044                                 doc.attachEvent("onreadystatechange", function() {
7045                                         if (doc.readyState === "complete") {
7046                                                 doc.detachEvent("onreadystatechange", arguments.callee);
7047                                                 t._pageInit(win);
7048                                         }
7049                                 });
7050
7051                                 if (doc.documentElement.doScroll && win == win.top) {
7052                                         (function() {
7053                                                 if (t.domLoaded)
7054                                                         return;
7055
7056                                                 try {
7057                                                         // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
7058                                                         // http://javascript.nwbox.com/IEContentLoaded/
7059                                                         doc.documentElement.doScroll("left");
7060                                                 } catch (ex) {
7061                                                         setTimeout(arguments.callee, 0);
7062                                                         return;
7063                                                 }
7064
7065                                                 t._pageInit(win);
7066                                         })();
7067                                 }
7068                         } else if (doc.addEventListener) {
7069                                 t._add(win, 'DOMContentLoaded', function() {
7070                                         t._pageInit(win);
7071                                 });
7072                         }
7073
7074                         t._add(win, 'load', function() {
7075                                 t._pageInit(win);
7076                         });
7077                 },
7078
7079                 _stoppers : {
7080                         preventDefault : function() {
7081                                 this.returnValue = false;
7082                         },
7083
7084                         stopPropagation : function() {
7085                                 this.cancelBubble = true;
7086                         }
7087                 }
7088         });
7089
7090         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
7091
7092         // Dispatch DOM content loaded event for IE and Safari
7093         Event._wait(window);
7094
7095         tinymce.addUnload(function() {
7096                 Event.destroy();
7097         });
7098 })(tinymce);
7099
7100 (function(tinymce) {
7101         tinymce.dom.Element = function(id, settings) {
7102                 var t = this, dom, el;
7103
7104                 t.settings = settings = settings || {};
7105                 t.id = id;
7106                 t.dom = dom = settings.dom || tinymce.DOM;
7107
7108                 // Only IE leaks DOM references, this is a lot faster
7109                 if (!tinymce.isIE)
7110                         el = dom.get(t.id);
7111
7112                 tinymce.each(
7113                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
7114                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
7115                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
7116                                 'isHidden,setHTML,get').split(/,/)
7117                         , function(k) {
7118                                 t[k] = function() {
7119                                         var a = [id], i;
7120
7121                                         for (i = 0; i < arguments.length; i++)
7122                                                 a.push(arguments[i]);
7123
7124                                         a = dom[k].apply(dom, a);
7125                                         t.update(k);
7126
7127                                         return a;
7128                                 };
7129                 });
7130
7131                 tinymce.extend(t, {
7132                         on : function(n, f, s) {
7133                                 return tinymce.dom.Event.add(t.id, n, f, s);
7134                         },
7135
7136                         getXY : function() {
7137                                 return {
7138                                         x : parseInt(t.getStyle('left')),
7139                                         y : parseInt(t.getStyle('top'))
7140                                 };
7141                         },
7142
7143                         getSize : function() {
7144                                 var n = dom.get(t.id);
7145
7146                                 return {
7147                                         w : parseInt(t.getStyle('width') || n.clientWidth),
7148                                         h : parseInt(t.getStyle('height') || n.clientHeight)
7149                                 };
7150                         },
7151
7152                         moveTo : function(x, y) {
7153                                 t.setStyles({left : x, top : y});
7154                         },
7155
7156                         moveBy : function(x, y) {
7157                                 var p = t.getXY();
7158
7159                                 t.moveTo(p.x + x, p.y + y);
7160                         },
7161
7162                         resizeTo : function(w, h) {
7163                                 t.setStyles({width : w, height : h});
7164                         },
7165
7166                         resizeBy : function(w, h) {
7167                                 var s = t.getSize();
7168
7169                                 t.resizeTo(s.w + w, s.h + h);
7170                         },
7171
7172                         update : function(k) {
7173                                 var b;
7174
7175                                 if (tinymce.isIE6 && settings.blocker) {
7176                                         k = k || '';
7177
7178                                         // Ignore getters
7179                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
7180                                                 return;
7181
7182                                         // Remove blocker on remove
7183                                         if (k == 'remove') {
7184                                                 dom.remove(t.blocker);
7185                                                 return;
7186                                         }
7187
7188                                         if (!t.blocker) {
7189                                                 t.blocker = dom.uniqueId();
7190                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
7191                                                 dom.setStyle(b, 'opacity', 0);
7192                                         } else
7193                                                 b = dom.get(t.blocker);
7194
7195                                         dom.setStyles(b, {
7196                                                 left : t.getStyle('left', 1),
7197                                                 top : t.getStyle('top', 1),
7198                                                 width : t.getStyle('width', 1),
7199                                                 height : t.getStyle('height', 1),
7200                                                 display : t.getStyle('display', 1),
7201                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
7202                                         });
7203                                 }
7204                         }
7205                 });
7206         };
7207 })(tinymce);
7208
7209 (function(tinymce) {
7210         function trimNl(s) {
7211                 return s.replace(/[\n\r]+/g, '');
7212         };
7213
7214         // Shorten names
7215         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
7216
7217         tinymce.create('tinymce.dom.Selection', {
7218                 Selection : function(dom, win, serializer) {
7219                         var t = this;
7220
7221                         t.dom = dom;
7222                         t.win = win;
7223                         t.serializer = serializer;
7224
7225                         // Add events
7226                         each([
7227                                 'onBeforeSetContent',
7228
7229                                 'onBeforeGetContent',
7230
7231                                 'onSetContent',
7232
7233                                 'onGetContent'
7234                         ], function(e) {
7235                                 t[e] = new tinymce.util.Dispatcher(t);
7236                         });
7237
7238                         // No W3C Range support
7239                         if (!t.win.getSelection)
7240                                 t.tridentSel = new tinymce.dom.TridentSelection(t);
7241
7242                         if (tinymce.isIE && dom.boxModel)
7243                                 this._fixIESelection();
7244
7245                         // Prevent leaks
7246                         tinymce.addUnload(t.destroy, t);
7247                 },
7248
7249                 setCursorLocation: function(node, offset) {
7250                         var t = this; var r = t.dom.createRng();
7251                         r.setStart(node, offset);
7252                         r.setEnd(node, offset);
7253                         t.setRng(r);
7254                         t.collapse(false);
7255                 },
7256                 getContent : function(s) {
7257                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
7258
7259                         s = s || {};
7260                         wb = wa = '';
7261                         s.get = true;
7262                         s.format = s.format || 'html';
7263                         s.forced_root_block = '';
7264                         t.onBeforeGetContent.dispatch(t, s);
7265
7266                         if (s.format == 'text')
7267                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
7268
7269                         if (r.cloneContents) {
7270                                 n = r.cloneContents();
7271
7272                                 if (n)
7273                                         e.appendChild(n);
7274                         } else if (is(r.item) || is(r.htmlText))
7275                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
7276                         else
7277                                 e.innerHTML = r.toString();
7278
7279                         // Keep whitespace before and after
7280                         if (/^\s/.test(e.innerHTML))
7281                                 wb = ' ';
7282
7283                         if (/\s+$/.test(e.innerHTML))
7284                                 wa = ' ';
7285
7286                         s.getInner = true;
7287
7288                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
7289                         t.onGetContent.dispatch(t, s);
7290
7291                         return s.content;
7292                 },
7293
7294                 setContent : function(content, args) {
7295                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
7296
7297                         args = args || {format : 'html'};
7298                         args.set = true;
7299                         content = args.content = content;
7300
7301                         // Dispatch before set content event
7302                         if (!args.no_events)
7303                                 self.onBeforeSetContent.dispatch(self, args);
7304
7305                         content = args.content;
7306
7307                         if (rng.insertNode) {
7308                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
7309                                 content += '<span id="__caret">_</span>';
7310
7311                                 // Delete and insert new node
7312                                 if (rng.startContainer == doc && rng.endContainer == doc) {
7313                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
7314                                         doc.body.innerHTML = content;
7315                                 } else {
7316                                         rng.deleteContents();
7317
7318                                         if (doc.body.childNodes.length == 0) {
7319                                                 doc.body.innerHTML = content;
7320                                         } else {
7321                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
7322                                                 if (rng.createContextualFragment) {
7323                                                         rng.insertNode(rng.createContextualFragment(content));
7324                                                 } else {
7325                                                         // Fake createContextualFragment call in IE 9
7326                                                         frag = doc.createDocumentFragment();
7327                                                         temp = doc.createElement('div');
7328
7329                                                         frag.appendChild(temp);
7330                                                         temp.outerHTML = content;
7331
7332                                                         rng.insertNode(frag);
7333                                                 }
7334                                         }
7335                                 }
7336
7337                                 // Move to caret marker
7338                                 caretNode = self.dom.get('__caret');
7339
7340                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
7341                                 rng = doc.createRange();
7342                                 rng.setStartBefore(caretNode);
7343                                 rng.setEndBefore(caretNode);
7344                                 self.setRng(rng);
7345
7346                                 // Remove the caret position
7347                                 self.dom.remove('__caret');
7348
7349                                 try {
7350                                         self.setRng(rng);
7351                                 } catch (ex) {
7352                                         // Might fail on Opera for some odd reason
7353                                 }
7354                         } else {
7355                                 if (rng.item) {
7356                                         // Delete content and get caret text selection
7357                                         doc.execCommand('Delete', false, null);
7358                                         rng = self.getRng();
7359                                 }
7360
7361                                 rng.pasteHTML(content);
7362                         }
7363
7364                         // Dispatch set content event
7365                         if (!args.no_events)
7366                                 self.onSetContent.dispatch(self, args);
7367                 },
7368
7369                 getStart : function() {
7370                         var rng = this.getRng(), startElement, parentElement, checkRng, node;
7371
7372                         if (rng.duplicate || rng.item) {
7373                                 // Control selection, return first item
7374                                 if (rng.item)
7375                                         return rng.item(0);
7376
7377                                 // Get start element
7378                                 checkRng = rng.duplicate();
7379                                 checkRng.collapse(1);
7380                                 startElement = checkRng.parentElement();
7381
7382                                 // Check if range parent is inside the start element, then return the inner parent element
7383                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7384                                 parentElement = node = rng.parentElement();
7385                                 while (node = node.parentNode) {
7386                                         if (node == startElement) {
7387                                                 startElement = parentElement;
7388                                                 break;
7389                                         }
7390                                 }
7391
7392                                 return startElement;
7393                         } else {
7394                                 startElement = rng.startContainer;
7395
7396                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
7397                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
7398
7399                                 if (startElement && startElement.nodeType == 3)
7400                                         return startElement.parentNode;
7401
7402                                 return startElement;
7403                         }
7404                 },
7405
7406                 getEnd : function() {
7407                         var t = this, r = t.getRng(), e, eo;
7408
7409                         if (r.duplicate || r.item) {
7410                                 if (r.item)
7411                                         return r.item(0);
7412
7413                                 r = r.duplicate();
7414                                 r.collapse(0);
7415                                 e = r.parentElement();
7416
7417                                 if (e && e.nodeName == 'BODY')
7418                                         return e.lastChild || e;
7419
7420                                 return e;
7421                         } else {
7422                                 e = r.endContainer;
7423                                 eo = r.endOffset;
7424
7425                                 if (e.nodeType == 1 && e.hasChildNodes())
7426                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];
7427
7428                                 if (e && e.nodeType == 3)
7429                                         return e.parentNode;
7430
7431                                 return e;
7432                         }
7433                 },
7434
7435                 getBookmark : function(type, normalized) {
7436                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
7437
7438                         function findIndex(name, element) {
7439                                 var index = 0;
7440
7441                                 each(dom.select(name), function(node, i) {
7442                                         if (node == element)
7443                                                 index = i;
7444                                 });
7445
7446                                 return index;
7447                         };
7448
7449                         if (type == 2) {
7450                                 function getLocation() {
7451                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
7452
7453                                         function getPoint(rng, start) {
7454                                                 var container = rng[start ? 'startContainer' : 'endContainer'],
7455                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
7456
7457                                                 if (container.nodeType == 3) {
7458                                                         if (normalized) {
7459                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
7460                                                                         offset += node.nodeValue.length;
7461                                                         }
7462
7463                                                         point.push(offset);
7464                                                 } else {
7465                                                         childNodes = container.childNodes;
7466
7467                                                         if (offset >= childNodes.length && childNodes.length) {
7468                                                                 after = 1;
7469                                                                 offset = Math.max(0, childNodes.length - 1);
7470                                                         }
7471
7472                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
7473                                                 }
7474
7475                                                 for (; container && container != root; container = container.parentNode)
7476                                                         point.push(t.dom.nodeIndex(container, normalized));
7477
7478                                                 return point;
7479                                         };
7480
7481                                         bookmark.start = getPoint(rng, true);
7482
7483                                         if (!t.isCollapsed())
7484                                                 bookmark.end = getPoint(rng);
7485
7486                                         return bookmark;
7487                                 };
7488
7489                                 if (t.tridentSel)
7490                                         return t.tridentSel.getBookmark(type);
7491
7492                                 return getLocation();
7493                         }
7494
7495                         // Handle simple range
7496                         if (type)
7497                                 return {rng : t.getRng()};
7498
7499                         rng = t.getRng();
7500                         id = dom.uniqueId();
7501                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();
7502                         styles = 'overflow:hidden;line-height:0px';
7503
7504                         // Explorer method
7505                         if (rng.duplicate || rng.item) {
7506                                 // Text selection
7507                                 if (!rng.item) {
7508                                         rng2 = rng.duplicate();
7509
7510                                         try {
7511                                                 // Insert start marker
7512                                                 rng.collapse();
7513                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
7514
7515                                                 // Insert end marker
7516                                                 if (!collapsed) {
7517                                                         rng2.collapse(false);
7518
7519                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
7520                                                         rng.moveToElementText(rng2.parentElement());
7521                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)
7522                                                                 rng2.move('character', -1);
7523
7524                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
7525                                                 }
7526                                         } catch (ex) {
7527                                                 // IE might throw unspecified error so lets ignore it
7528                                                 return null;
7529                                         }
7530                                 } else {
7531                                         // Control selection
7532                                         element = rng.item(0);
7533                                         name = element.nodeName;
7534
7535                                         return {name : name, index : findIndex(name, element)};
7536                                 }
7537                         } else {
7538                                 element = t.getNode();
7539                                 name = element.nodeName;
7540                                 if (name == 'IMG')
7541                                         return {name : name, index : findIndex(name, element)};
7542
7543                                 // W3C method
7544                                 rng2 = rng.cloneRange();
7545
7546                                 // Insert end marker
7547                                 if (!collapsed) {
7548                                         rng2.collapse(false);
7549                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
7550                                 }
7551
7552                                 rng.collapse(true);
7553                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
7554                         }
7555
7556                         t.moveToBookmark({id : id, keep : 1});
7557
7558                         return {id : id};
7559                 },
7560
7561                 moveToBookmark : function(bookmark) {
7562                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
7563
7564                         if (bookmark) {
7565                                 if (bookmark.start) {
7566                                         rng = dom.createRng();
7567                                         root = dom.getRoot();
7568
7569                                         function setEndPoint(start) {
7570                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
7571
7572                                                 if (point) {
7573                                                         offset = point[0];
7574
7575                                                         // Find container node
7576                                                         for (node = root, i = point.length - 1; i >= 1; i--) {
7577                                                                 children = node.childNodes;
7578
7579                                                                 if (point[i] > children.length - 1)
7580                                                                         return;
7581
7582                                                                 node = children[point[i]];
7583                                                         }
7584
7585                                                         // Move text offset to best suitable location
7586                                                         if (node.nodeType === 3)
7587                                                                 offset = Math.min(point[0], node.nodeValue.length);
7588
7589                                                         // Move element offset to best suitable location
7590                                                         if (node.nodeType === 1)
7591                                                                 offset = Math.min(point[0], node.childNodes.length);
7592
7593                                                         // Set offset within container node
7594                                                         if (start)
7595                                                                 rng.setStart(node, offset);
7596                                                         else
7597                                                                 rng.setEnd(node, offset);
7598                                                 }
7599
7600                                                 return true;
7601                                         };
7602
7603                                         if (t.tridentSel)
7604                                                 return t.tridentSel.moveToBookmark(bookmark);
7605
7606                                         if (setEndPoint(true) && setEndPoint()) {
7607                                                 t.setRng(rng);
7608                                         }
7609                                 } else if (bookmark.id) {
7610                                         function restoreEndPoint(suffix) {
7611                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
7612
7613                                                 if (marker) {
7614                                                         node = marker.parentNode;
7615
7616                                                         if (suffix == 'start') {
7617                                                                 if (!keep) {
7618                                                                         idx = dom.nodeIndex(marker);
7619                                                                 } else {
7620                                                                         node = marker.firstChild;
7621                                                                         idx = 1;
7622                                                                 }
7623
7624                                                                 startContainer = endContainer = node;
7625                                                                 startOffset = endOffset = idx;
7626                                                         } else {
7627                                                                 if (!keep) {
7628                                                                         idx = dom.nodeIndex(marker);
7629                                                                 } else {
7630                                                                         node = marker.firstChild;
7631                                                                         idx = 1;
7632                                                                 }
7633
7634                                                                 endContainer = node;
7635                                                                 endOffset = idx;
7636                                                         }
7637
7638                                                         if (!keep) {
7639                                                                 prev = marker.previousSibling;
7640                                                                 next = marker.nextSibling;
7641
7642                                                                 // Remove all marker text nodes
7643                                                                 each(tinymce.grep(marker.childNodes), function(node) {
7644                                                                         if (node.nodeType == 3)
7645                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
7646                                                                 });
7647
7648                                                                 // Remove marker but keep children if for example contents where inserted into the marker
7649                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
7650                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))
7651                                                                         dom.remove(marker, 1);
7652
7653                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
7654                                                                 // 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
7655                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
7656                                                                         idx = prev.nodeValue.length;
7657                                                                         prev.appendData(next.nodeValue);
7658                                                                         dom.remove(next);
7659
7660                                                                         if (suffix == 'start') {
7661                                                                                 startContainer = endContainer = prev;
7662                                                                                 startOffset = endOffset = idx;
7663                                                                         } else {
7664                                                                                 endContainer = prev;
7665                                                                                 endOffset = idx;
7666                                                                         }
7667                                                                 }
7668                                                         }
7669                                                 }
7670                                         };
7671
7672                                         function addBogus(node) {
7673                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
7674                                                 if (dom.isBlock(node) && !node.innerHTML)
7675                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
7676
7677                                                 return node;
7678                                         };
7679
7680                                         // Restore start/end points
7681                                         restoreEndPoint('start');
7682                                         restoreEndPoint('end');
7683
7684                                         if (startContainer) {
7685                                                 rng = dom.createRng();
7686                                                 rng.setStart(addBogus(startContainer), startOffset);
7687                                                 rng.setEnd(addBogus(endContainer), endOffset);
7688                                                 t.setRng(rng);
7689                                         }
7690                                 } else if (bookmark.name) {
7691                                         t.select(dom.select(bookmark.name)[bookmark.index]);
7692                                 } else if (bookmark.rng)
7693                                         t.setRng(bookmark.rng);
7694                         }
7695                 },
7696
7697                 select : function(node, content) {
7698                         var t = this, dom = t.dom, rng = dom.createRng(), idx;
7699
7700                         if (node) {
7701                                 idx = dom.nodeIndex(node);
7702                                 rng.setStart(node.parentNode, idx);
7703                                 rng.setEnd(node.parentNode, idx + 1);
7704
7705                                 // Find first/last text node or BR element
7706                                 if (content) {
7707                                         function setPoint(node, start) {
7708                                                 var walker = new tinymce.dom.TreeWalker(node, node);
7709
7710                                                 do {
7711                                                         // Text node
7712                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
7713                                                                 if (start)
7714                                                                         rng.setStart(node, 0);
7715                                                                 else
7716                                                                         rng.setEnd(node, node.nodeValue.length);
7717
7718                                                                 return;
7719                                                         }
7720
7721                                                         // BR element
7722                                                         if (node.nodeName == 'BR') {
7723                                                                 if (start)
7724                                                                         rng.setStartBefore(node);
7725                                                                 else
7726                                                                         rng.setEndBefore(node);
7727
7728                                                                 return;
7729                                                         }
7730                                                 } while (node = (start ? walker.next() : walker.prev()));
7731                                         };
7732
7733                                         setPoint(node, 1);
7734                                         setPoint(node);
7735                                 }
7736
7737                                 t.setRng(rng);
7738                         }
7739
7740                         return node;
7741                 },
7742
7743                 isCollapsed : function() {
7744                         var t = this, r = t.getRng(), s = t.getSel();
7745
7746                         if (!r || r.item)
7747                                 return false;
7748
7749                         if (r.compareEndPoints)
7750                                 return r.compareEndPoints('StartToEnd', r) === 0;
7751
7752                         return !s || r.collapsed;
7753                 },
7754
7755                 collapse : function(to_start) {
7756                         var self = this, rng = self.getRng(), node;
7757
7758                         // Control range on IE
7759                         if (rng.item) {
7760                                 node = rng.item(0);
7761                                 rng = self.win.document.body.createTextRange();
7762                                 rng.moveToElementText(node);
7763                         }
7764
7765                         rng.collapse(!!to_start);
7766                         self.setRng(rng);
7767                 },
7768
7769                 getSel : function() {
7770                         var t = this, w = this.win;
7771
7772                         return w.getSelection ? w.getSelection() : w.document.selection;
7773                 },
7774
7775                 getRng : function(w3c) {
7776                         var t = this, s, r, elm, doc = t.win.document;
7777
7778                         // Found tridentSel object then we need to use that one
7779                         if (w3c && t.tridentSel)
7780                                 return t.tridentSel.getRangeAt(0);
7781
7782                         try {
7783                                 if (s = t.getSel())
7784                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
7785                         } catch (ex) {
7786                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
7787                         }
7788
7789                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
7790                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
7791                                 elm = doc.selection.createRange().item(0);
7792                                 r = doc.createRange();
7793                                 r.setStartBefore(elm);
7794                                 r.setEndAfter(elm);
7795                         }
7796
7797                         // No range found then create an empty one
7798                         // This can occur when the editor is placed in a hidden container element on Gecko
7799                         // Or on IE when there was an exception
7800                         if (!r)
7801                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
7802
7803                         if (t.selectedRange && t.explicitRange) {
7804                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
7805                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
7806                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
7807                                         r = t.explicitRange;
7808                                 } else {
7809                                         t.selectedRange = null;
7810                                         t.explicitRange = null;
7811                                 }
7812                         }
7813
7814                         return r;
7815                 },
7816
7817                 setRng : function(r) {
7818                         var s, t = this;
7819                         
7820                         if (!t.tridentSel) {
7821                                 s = t.getSel();
7822
7823                                 if (s) {
7824                                         t.explicitRange = r;
7825
7826                                         try {
7827                                                 s.removeAllRanges();
7828                                         } catch (ex) {
7829                                                 // IE9 might throw errors here don't know why
7830                                         }
7831
7832                                         s.addRange(r);
7833                                         t.selectedRange = s.getRangeAt(0);
7834                                 }
7835                         } else {
7836                                 // Is W3C Range
7837                                 if (r.cloneRange) {
7838                                         t.tridentSel.addRange(r);
7839                                         return;
7840                                 }
7841
7842                                 // Is IE specific range
7843                                 try {
7844                                         r.select();
7845                                 } catch (ex) {
7846                                         // Needed for some odd IE bug #1843306
7847                                 }
7848                         }
7849                 },
7850
7851                 setNode : function(n) {
7852                         var t = this;
7853
7854                         t.setContent(t.dom.getOuterHTML(n));
7855
7856                         return n;
7857                 },
7858
7859                 getNode : function() {
7860                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
7861
7862                         // Range maybe lost after the editor is made visible again
7863                         if (!rng)
7864                                 return t.dom.getRoot();
7865
7866                         if (rng.setStart) {
7867                                 elm = rng.commonAncestorContainer;
7868
7869                                 // Handle selection a image or other control like element such as anchors
7870                                 if (!rng.collapsed) {
7871                                         if (rng.startContainer == rng.endContainer) {
7872                                                 if (rng.endOffset - rng.startOffset < 2) {
7873                                                         if (rng.startContainer.hasChildNodes())
7874                                                                 elm = rng.startContainer.childNodes[rng.startOffset];
7875                                                 }
7876                                         }
7877
7878                                         // If the anchor node is a element instead of a text node then return this element
7879                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
7880                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
7881
7882                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
7883                                         // This happens when you double click an underlined word in FireFox.
7884                                         if (start.nodeType === 3 && end.nodeType === 3) {
7885                                                 function skipEmptyTextNodes(n, forwards) {
7886                                                         var orig = n;
7887                                                         while (n && n.nodeType === 3 && n.length === 0) {
7888                                                                 n = forwards ? n.nextSibling : n.previousSibling;
7889                                                         }
7890                                                         return n || orig;
7891                                                 }
7892                                                 if (start.length === rng.startOffset) {
7893                                                         start = skipEmptyTextNodes(start.nextSibling, true);
7894                                                 } else {
7895                                                         start = start.parentNode;
7896                                                 }
7897                                                 if (rng.endOffset === 0) {
7898                                                         end = skipEmptyTextNodes(end.previousSibling, false);
7899                                                 } else {
7900                                                         end = end.parentNode;
7901                                                 }
7902
7903                                                 if (start && start === end)
7904                                                         return start;
7905                                         }
7906                                 }
7907
7908                                 if (elm && elm.nodeType == 3)
7909                                         return elm.parentNode;
7910
7911                                 return elm;
7912                         }
7913
7914                         return rng.item ? rng.item(0) : rng.parentElement();
7915                 },
7916
7917                 getSelectedBlocks : function(st, en) {
7918                         var t = this, dom = t.dom, sb, eb, n, bl = [];
7919
7920                         sb = dom.getParent(st || t.getStart(), dom.isBlock);
7921                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);
7922
7923                         if (sb)
7924                                 bl.push(sb);
7925
7926                         if (sb && eb && sb != eb) {
7927                                 n = sb;
7928
7929                                 while ((n = n.nextSibling) && n != eb) {
7930                                         if (dom.isBlock(n))
7931                                                 bl.push(n);
7932                                 }
7933                         }
7934
7935                         if (eb && sb != eb)
7936                                 bl.push(eb);
7937
7938                         return bl;
7939                 },
7940
7941                 normalize : function() {
7942                         var self = this, rng, normalized;
7943
7944                         // Normalize only on non IE browsers for now
7945                         if (tinymce.isIE)
7946                                 return;
7947
7948                         function normalizeEndPoint(start) {
7949                                 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;
7950
7951                                 container = rng[(start ? 'start' : 'end') + 'Container'];
7952                                 offset = rng[(start ? 'start' : 'end') + 'Offset'];
7953
7954                                 // If the container is a document move it to the body element
7955                                 if (container.nodeType === 9) {
7956                                         container = container.body;
7957                                         offset = 0;
7958                                 }
7959
7960                                 // If the container is body try move it into the closest text node or position
7961                                 // TODO: Add more logic here to handle element selection cases
7962                                 if (container === body) {
7963                                         // Resolve the index
7964                                         if (container.hasChildNodes()) {
7965                                                 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
7966                                                 offset = 0;
7967
7968                                                 // Walk the DOM to find a text node to place the caret at or a BR
7969                                                 node = container;
7970                                                 walker = new tinymce.dom.TreeWalker(container, body);
7971                                                 do {
7972                                                         // Found a text node use that position
7973                                                         if (node.nodeType === 3) {
7974                                                                 offset = start ? 0 : node.nodeValue.length - 1;
7975                                                                 container = node;
7976                                                                 break;
7977                                                         }
7978
7979                                                         // Found a BR element that we can place the caret before
7980                                                         if (node.nodeName === 'BR') {
7981                                                                 offset = dom.nodeIndex(node);
7982                                                                 container = node.parentNode;
7983                                                                 break;
7984                                                         }
7985                                                 } while (node = (start ? walker.next() : walker.prev()));
7986
7987                                                 normalized = true;
7988                                         }
7989                                 }
7990
7991                                 // Set endpoint if it was normalized
7992                                 if (normalized)
7993                                         rng['set' + (start ? 'Start' : 'End')](container, offset);
7994                         };
7995
7996                         rng = self.getRng();
7997
7998                         // Normalize the end points
7999                         normalizeEndPoint(true);
8000                         
8001                         if (rng.collapsed)
8002                                 normalizeEndPoint();
8003
8004                         // Set the selection if it was normalized
8005                         if (normalized) {
8006                                 //console.log(self.dom.dumpRng(rng));
8007                                 self.setRng(rng);
8008                         }
8009                 },
8010
8011                 destroy : function(s) {
8012                         var t = this;
8013
8014                         t.win = null;
8015
8016                         // Manual destroy then remove unload handler
8017                         if (!s)
8018                                 tinymce.removeUnload(t.destroy);
8019                 },
8020
8021                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
8022                 _fixIESelection : function() {
8023                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
8024
8025                         // Make HTML element unselectable since we are going to handle selection by hand
8026                         doc.documentElement.unselectable = true;
8027
8028                         // Return range from point or null if it failed
8029                         function rngFromPoint(x, y) {
8030                                 var rng = body.createTextRange();
8031
8032                                 try {
8033                                         rng.moveToPoint(x, y);
8034                                 } catch (ex) {
8035                                         // IE sometimes throws and exception, so lets just ignore it
8036                                         rng = null;
8037                                 }
8038
8039                                 return rng;
8040                         };
8041
8042                         // Fires while the selection is changing
8043                         function selectionChange(e) {
8044                                 var pointRng;
8045
8046                                 // Check if the button is down or not
8047                                 if (e.button) {
8048                                         // Create range from mouse position
8049                                         pointRng = rngFromPoint(e.x, e.y);
8050
8051                                         if (pointRng) {
8052                                                 // Check if pointRange is before/after selection then change the endPoint
8053                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
8054                                                         pointRng.setEndPoint('StartToStart', startRng);
8055                                                 else
8056                                                         pointRng.setEndPoint('EndToEnd', startRng);
8057
8058                                                 pointRng.select();
8059                                         }
8060                                 } else
8061                                         endSelection();
8062                         }
8063
8064                         // Removes listeners
8065                         function endSelection() {
8066                                 var rng = doc.selection.createRange();
8067
8068                                 // If the range is collapsed then use the last start range
8069                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
8070                                         startRng.select();
8071
8072                                 dom.unbind(doc, 'mouseup', endSelection);
8073                                 dom.unbind(doc, 'mousemove', selectionChange);
8074                                 startRng = started = 0;
8075                         };
8076
8077                         // Detect when user selects outside BODY
8078                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
8079                                 if (e.target.nodeName === 'HTML') {
8080                                         if (started)
8081                                                 endSelection();
8082
8083                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
8084                                         htmlElm = doc.documentElement;
8085                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)
8086                                                 return;
8087
8088                                         started = 1;
8089                                         // Setup start position
8090                                         startRng = rngFromPoint(e.x, e.y);
8091                                         if (startRng) {
8092                                                 // Listen for selection change events
8093                                                 dom.bind(doc, 'mouseup', endSelection);
8094                                                 dom.bind(doc, 'mousemove', selectionChange);
8095
8096                                                 dom.win.focus();
8097                                                 startRng.select();
8098                                         }
8099                                 }
8100                         });
8101                 }
8102         });
8103 })(tinymce);
8104
8105 (function(tinymce) {
8106         tinymce.dom.Serializer = function(settings, dom, schema) {
8107                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
8108
8109                 // Support the old apply_source_formatting option
8110                 if (!settings.apply_source_formatting)
8111                         settings.indent = false;
8112
8113                 settings.remove_trailing_brs = true;
8114
8115                 // Default DOM and Schema if they are undefined
8116                 dom = dom || tinymce.DOM;
8117                 schema = schema || new tinymce.html.Schema(settings);
8118                 settings.entity_encoding = settings.entity_encoding || 'named';
8119
8120                 onPreProcess = new tinymce.util.Dispatcher(self);
8121
8122                 onPostProcess = new tinymce.util.Dispatcher(self);
8123
8124                 htmlParser = new tinymce.html.DomParser(settings, schema);
8125
8126                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
8127                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
8128                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
8129
8130                         while (i--) {
8131                                 node = nodes[i];
8132
8133                                 value = node.attributes.map[internalName];
8134                                 if (value !== undef) {
8135                                         // Set external name to internal value and remove internal
8136                                         node.attr(name, value.length > 0 ? value : null);
8137                                         node.attr(internalName, null);
8138                                 } else {
8139                                         // No internal attribute found then convert the value we have in the DOM
8140                                         value = node.attributes.map[name];
8141
8142                                         if (name === "style")
8143                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
8144                                         else if (urlConverter)
8145                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
8146
8147                                         node.attr(name, value.length > 0 ? value : null);
8148                                 }
8149                         }
8150                 });
8151
8152                 // Remove internal classes mceItem<..>
8153                 htmlParser.addAttributeFilter('class', function(nodes, name) {
8154                         var i = nodes.length, node, value;
8155
8156                         while (i--) {
8157                                 node = nodes[i];
8158                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
8159                                 node.attr('class', value.length > 0 ? value : null);
8160                         }
8161                 });
8162
8163                 // Remove bookmark elements
8164                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
8165                         var i = nodes.length, node;
8166
8167                         while (i--) {
8168                                 node = nodes[i];
8169
8170                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
8171                                         node.remove();
8172                         }
8173                 });
8174
8175                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
8176                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
8177                         var i = nodes.length, node, value;
8178
8179                         function trim(value) {
8180                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
8181                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
8182                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
8183                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
8184                         };
8185
8186                         while (i--) {
8187                                 node = nodes[i];
8188                                 value = node.firstChild ? node.firstChild.value : '';
8189
8190                                 if (name === "script") {
8191                                         // Remove mce- prefix from script elements
8192                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
8193
8194                                         if (value.length > 0)
8195                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
8196                                 } else {
8197                                         if (value.length > 0)
8198                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
8199                                 }
8200                         }
8201                 });
8202
8203                 // Convert comments to cdata and handle protected comments
8204                 htmlParser.addNodeFilter('#comment', function(nodes, name) {
8205                         var i = nodes.length, node;
8206
8207                         while (i--) {
8208                                 node = nodes[i];
8209
8210                                 if (node.value.indexOf('[CDATA[') === 0) {
8211                                         node.name = '#cdata';
8212                                         node.type = 4;
8213                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
8214                                 } else if (node.value.indexOf('mce:protected ') === 0) {
8215                                         node.name = "#text";
8216                                         node.type = 3;
8217                                         node.raw = true;
8218                                         node.value = unescape(node.value).substr(14);
8219                                 }
8220                         }
8221                 });
8222
8223                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
8224                         var i = nodes.length, node;
8225
8226                         while (i--) {
8227                                 node = nodes[i];
8228                                 if (node.type === 7)
8229                                         node.remove();
8230                                 else if (node.type === 1) {
8231                                         if (name === "input" && !("type" in node.attributes.map))
8232                                                 node.attr('type', 'text');
8233                                 }
8234                         }
8235                 });
8236
8237                 // Fix list elements, TODO: Replace this later
8238                 if (settings.fix_list_elements) {
8239                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
8240                                 var i = nodes.length, node, parentNode;
8241
8242                                 while (i--) {
8243                                         node = nodes[i];
8244                                         parentNode = node.parent;
8245
8246                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
8247                                                 if (node.prev && node.prev.name === 'li') {
8248                                                         node.prev.append(node);
8249                                                 }
8250                                         }
8251                                 }
8252                         });
8253                 }
8254
8255                 // Remove internal data attributes
8256                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
8257                         var i = nodes.length;
8258
8259                         while (i--) {
8260                                 nodes[i].attr(name, null);
8261                         }
8262                 });
8263
8264                 // Return public methods
8265                 return {
8266                         schema : schema,
8267
8268                         addNodeFilter : htmlParser.addNodeFilter,
8269
8270                         addAttributeFilter : htmlParser.addAttributeFilter,
8271
8272                         onPreProcess : onPreProcess,
8273
8274                         onPostProcess : onPostProcess,
8275
8276                         serialize : function(node, args) {
8277                                 var impl, doc, oldDoc, htmlSerializer, content;
8278
8279                                 // Explorer won't clone contents of script and style and the
8280                                 // selected index of select elements are cleared on a clone operation.
8281                                 if (isIE && dom.select('script,style,select').length > 0) {
8282                                         content = node.innerHTML;
8283                                         node = node.cloneNode(false);
8284                                         dom.setHTML(node, content);
8285                                 } else
8286                                         node = node.cloneNode(true);
8287
8288                                 // Nodes needs to be attached to something in WebKit/Opera
8289                                 // Older builds of Opera crashes if you attach the node to an document created dynamically
8290                                 // and since we can't feature detect a crash we need to sniff the acutal build number
8291                                 // This fix will make DOM ranges and make Sizzle happy!
8292                                 impl = node.ownerDocument.implementation;
8293                                 if (impl.createHTMLDocument) {
8294                                         // Create an empty HTML document
8295                                         doc = impl.createHTMLDocument("");
8296
8297                                         // Add the element or it's children if it's a body element to the new document
8298                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
8299                                                 doc.body.appendChild(doc.importNode(node, true));
8300                                         });
8301
8302                                         // Grab first child or body element for serialization
8303                                         if (node.nodeName != 'BODY')
8304                                                 node = doc.body.firstChild;
8305                                         else
8306                                                 node = doc.body;
8307
8308                                         // set the new document in DOMUtils so createElement etc works
8309                                         oldDoc = dom.doc;
8310                                         dom.doc = doc;
8311                                 }
8312
8313                                 args = args || {};
8314                                 args.format = args.format || 'html';
8315
8316                                 // Pre process
8317                                 if (!args.no_events) {
8318                                         args.node = node;
8319                                         onPreProcess.dispatch(self, args);
8320                                 }
8321
8322                                 // Setup serializer
8323                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
8324
8325                                 // Parse and serialize HTML
8326                                 args.content = htmlSerializer.serialize(
8327                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
8328                                 );
8329
8330                                 // Replace all BOM characters for now until we can find a better solution
8331                                 if (!args.cleanup)
8332                                         args.content = args.content.replace(/\uFEFF/g, '');
8333
8334                                 // Post process
8335                                 if (!args.no_events)
8336                                         onPostProcess.dispatch(self, args);
8337
8338                                 // Restore the old document if it was changed
8339                                 if (oldDoc)
8340                                         dom.doc = oldDoc;
8341
8342                                 args.node = null;
8343
8344                                 return args.content;
8345                         },
8346
8347                         addRules : function(rules) {
8348                                 schema.addValidElements(rules);
8349                         },
8350
8351                         setRules : function(rules) {
8352                                 schema.setValidElements(rules);
8353                         }
8354                 };
8355         };
8356 })(tinymce);
8357 (function(tinymce) {
8358         tinymce.dom.ScriptLoader = function(settings) {
8359                 var QUEUED = 0,
8360                         LOADING = 1,
8361                         LOADED = 2,
8362                         states = {},
8363                         queue = [],
8364                         scriptLoadedCallbacks = {},
8365                         queueLoadedCallbacks = [],
8366                         loading = 0,
8367                         undefined;
8368
8369                 function loadScript(url, callback) {
8370                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;
8371
8372                         // Execute callback when script is loaded
8373                         function done() {
8374                                 dom.remove(id);
8375
8376                                 if (elm)
8377                                         elm.onreadystatechange = elm.onload = elm = null;
8378
8379                                 callback();
8380                         };
8381                         
8382                         function error() {
8383                                 // Report the error so it's easier for people to spot loading errors
8384                                 if (typeof(console) !== "undefined" && console.log)
8385                                         console.log("Failed to load: " + url);
8386
8387                                 // We can't mark it as done if there is a load error since
8388                                 // A) We don't want to produce 404 errors on the server and
8389                                 // B) the onerror event won't fire on all browsers.
8390                                 // done();
8391                         };
8392
8393                         id = dom.uniqueId();
8394
8395                         if (tinymce.isIE6) {
8396                                 uri = new tinymce.util.URI(url);
8397                                 loc = location;
8398
8399                                 // If script is from same domain and we
8400                                 // use IE 6 then use XHR since it's more reliable
8401                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
8402                                         tinymce.util.XHR.send({
8403                                                 url : tinymce._addVer(uri.getURI()),
8404                                                 success : function(content) {
8405                                                         // Create new temp script element
8406                                                         var script = dom.create('script', {
8407                                                                 type : 'text/javascript'
8408                                                         });
8409
8410                                                         // Evaluate script in global scope
8411                                                         script.text = content;
8412                                                         document.getElementsByTagName('head')[0].appendChild(script);
8413                                                         dom.remove(script);
8414
8415                                                         done();
8416                                                 },
8417                                                 
8418                                                 error : error
8419                                         });
8420
8421                                         return;
8422                                 }
8423                         }
8424
8425                         // Create new script element
8426                         elm = dom.create('script', {
8427                                 id : id,
8428                                 type : 'text/javascript',
8429                                 src : tinymce._addVer(url)
8430                         });
8431
8432                         // Add onload listener for non IE browsers since IE9
8433                         // fires onload event before the script is parsed and executed
8434                         if (!tinymce.isIE)
8435                                 elm.onload = done;
8436
8437                         // Add onerror event will get fired on some browsers but not all of them
8438                         elm.onerror = error;
8439
8440                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
8441                         if (!tinymce.isOpera) {
8442                                 elm.onreadystatechange = function() {
8443                                         var state = elm.readyState;
8444
8445                                         // Loaded state is passed on IE 6 however there
8446                                         // are known issues with this method but we can't use
8447                                         // XHR in a cross domain loading
8448                                         if (state == 'complete' || state == 'loaded')
8449                                                 done();
8450                                 };
8451                         }
8452
8453                         // Most browsers support this feature so we report errors
8454                         // for those at least to help users track their missing plugins etc
8455                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
8456                         /*elm.onerror = function() {
8457                                 alert('Failed to load: ' + url);
8458                         };*/
8459
8460                         // Add script to document
8461                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
8462                 };
8463
8464                 this.isDone = function(url) {
8465                         return states[url] == LOADED;
8466                 };
8467
8468                 this.markDone = function(url) {
8469                         states[url] = LOADED;
8470                 };
8471
8472                 this.add = this.load = function(url, callback, scope) {
8473                         var item, state = states[url];
8474
8475                         // Add url to load queue
8476                         if (state == undefined) {
8477                                 queue.push(url);
8478                                 states[url] = QUEUED;
8479                         }
8480
8481                         if (callback) {
8482                                 // Store away callback for later execution
8483                                 if (!scriptLoadedCallbacks[url])
8484                                         scriptLoadedCallbacks[url] = [];
8485
8486                                 scriptLoadedCallbacks[url].push({
8487                                         func : callback,
8488                                         scope : scope || this
8489                                 });
8490                         }
8491                 };
8492
8493                 this.loadQueue = function(callback, scope) {
8494                         this.loadScripts(queue, callback, scope);
8495                 };
8496
8497                 this.loadScripts = function(scripts, callback, scope) {
8498                         var loadScripts;
8499
8500                         function execScriptLoadedCallbacks(url) {
8501                                 // Execute URL callback functions
8502                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
8503                                         callback.func.call(callback.scope);
8504                                 });
8505
8506                                 scriptLoadedCallbacks[url] = undefined;
8507                         };
8508
8509                         queueLoadedCallbacks.push({
8510                                 func : callback,
8511                                 scope : scope || this
8512                         });
8513
8514                         loadScripts = function() {
8515                                 var loadingScripts = tinymce.grep(scripts);
8516
8517                                 // Current scripts has been handled
8518                                 scripts.length = 0;
8519
8520                                 // Load scripts that needs to be loaded
8521                                 tinymce.each(loadingScripts, function(url) {
8522                                         // Script is already loaded then execute script callbacks directly
8523                                         if (states[url] == LOADED) {
8524                                                 execScriptLoadedCallbacks(url);
8525                                                 return;
8526                                         }
8527
8528                                         // Is script not loading then start loading it
8529                                         if (states[url] != LOADING) {
8530                                                 states[url] = LOADING;
8531                                                 loading++;
8532
8533                                                 loadScript(url, function() {
8534                                                         states[url] = LOADED;
8535                                                         loading--;
8536
8537                                                         execScriptLoadedCallbacks(url);
8538
8539                                                         // Load more scripts if they where added by the recently loaded script
8540                                                         loadScripts();
8541                                                 });
8542                                         }
8543                                 });
8544
8545                                 // No scripts are currently loading then execute all pending queue loaded callbacks
8546                                 if (!loading) {
8547                                         tinymce.each(queueLoadedCallbacks, function(callback) {
8548                                                 callback.func.call(callback.scope);
8549                                         });
8550
8551                                         queueLoadedCallbacks.length = 0;
8552                                 }
8553                         };
8554
8555                         loadScripts();
8556                 };
8557         };
8558
8559         // Global script loader
8560         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
8561 })(tinymce);
8562
8563 tinymce.dom.TreeWalker = function(start_node, root_node) {
8564         var node = start_node;
8565
8566         function findSibling(node, start_name, sibling_name, shallow) {
8567                 var sibling, parent;
8568
8569                 if (node) {
8570                         // Walk into nodes if it has a start
8571                         if (!shallow && node[start_name])
8572                                 return node[start_name];
8573
8574                         // Return the sibling if it has one
8575                         if (node != root_node) {
8576                                 sibling = node[sibling_name];
8577                                 if (sibling)
8578                                         return sibling;
8579
8580                                 // Walk up the parents to look for siblings
8581                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
8582                                         sibling = parent[sibling_name];
8583                                         if (sibling)
8584                                                 return sibling;
8585                                 }
8586                         }
8587                 }
8588         };
8589
8590         this.current = function() {
8591                 return node;
8592         };
8593
8594         this.next = function(shallow) {
8595                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
8596         };
8597
8598         this.prev = function(shallow) {
8599                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
8600         };
8601 };
8602
8603 (function(tinymce) {
8604         tinymce.dom.RangeUtils = function(dom) {
8605                 var INVISIBLE_CHAR = '\uFEFF';
8606
8607                 this.walk = function(rng, callback) {
8608                         var startContainer = rng.startContainer,
8609                                 startOffset = rng.startOffset,
8610                                 endContainer = rng.endContainer,
8611                                 endOffset = rng.endOffset,
8612                                 ancestor, startPoint,
8613                                 endPoint, node, parent, siblings, nodes;
8614
8615                         // Handle table cell selection the table plugin enables
8616                         // you to fake select table cells and perform formatting actions on them
8617                         nodes = dom.select('td.mceSelected,th.mceSelected');
8618                         if (nodes.length > 0) {
8619                                 tinymce.each(nodes, function(node) {
8620                                         callback([node]);
8621                                 });
8622
8623                                 return;
8624                         }
8625
8626                         function collectSiblings(node, name, end_node) {
8627                                 var siblings = [];
8628
8629                                 for (; node && node != end_node; node = node[name])
8630                                         siblings.push(node);
8631
8632                                 return siblings;
8633                         };
8634
8635                         function findEndPoint(node, root) {
8636                                 do {
8637                                         if (node.parentNode == root)
8638                                                 return node;
8639
8640                                         node = node.parentNode;
8641                                 } while(node);
8642                         };
8643
8644                         function walkBoundary(start_node, end_node, next) {
8645                                 var siblingName = next ? 'nextSibling' : 'previousSibling';
8646
8647                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
8648                                         parent = node.parentNode;
8649                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
8650
8651                                         if (siblings.length) {
8652                                                 if (!next)
8653                                                         siblings.reverse();
8654
8655                                                 callback(siblings);
8656                                         }
8657                                 }
8658                         };
8659
8660                         // If index based start position then resolve it
8661                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
8662                                 startContainer = startContainer.childNodes[startOffset];
8663
8664                         // If index based end position then resolve it
8665                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
8666                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
8667
8668                         // Find common ancestor and end points
8669                         ancestor = dom.findCommonAncestor(startContainer, endContainer);
8670
8671                         // Same container
8672                         if (startContainer == endContainer)
8673                                 return callback([startContainer]);
8674
8675                         // Process left side
8676                         for (node = startContainer; node; node = node.parentNode) {
8677                                 if (node == endContainer)
8678                                         return walkBoundary(startContainer, ancestor, true);
8679
8680                                 if (node == ancestor)
8681                                         break;
8682                         }
8683
8684                         // Process right side
8685                         for (node = endContainer; node; node = node.parentNode) {
8686                                 if (node == startContainer)
8687                                         return walkBoundary(endContainer, ancestor);
8688
8689                                 if (node == ancestor)
8690                                         break;
8691                         }
8692
8693                         // Find start/end point
8694                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;
8695                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;
8696
8697                         // Walk left leaf
8698                         walkBoundary(startContainer, startPoint, true);
8699
8700                         // Walk the middle from start to end point
8701                         siblings = collectSiblings(
8702                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
8703                                 'nextSibling',
8704                                 endPoint == endContainer ? endPoint.nextSibling : endPoint
8705                         );
8706
8707                         if (siblings.length)
8708                                 callback(siblings);
8709
8710                         // Walk right leaf
8711                         walkBoundary(endContainer, endPoint);
8712                 };
8713
8714                 /*              this.split = function(rng) {
8715                         var startContainer = rng.startContainer,
8716                                 startOffset = rng.startOffset,
8717                                 endContainer = rng.endContainer,
8718                                 endOffset = rng.endOffset;
8719
8720                         function splitText(node, offset) {
8721                                 if (offset == node.nodeValue.length)
8722                                         node.appendData(INVISIBLE_CHAR);
8723
8724                                 node = node.splitText(offset);
8725
8726                                 if (node.nodeValue === INVISIBLE_CHAR)
8727                                         node.nodeValue = '';
8728
8729                                 return node;
8730                         };
8731
8732                         // Handle single text node
8733                         if (startContainer == endContainer) {
8734                                 if (startContainer.nodeType == 3) {
8735                                         if (startOffset != 0)
8736                                                 startContainer = endContainer = splitText(startContainer, startOffset);
8737
8738                                         if (endOffset - startOffset != startContainer.nodeValue.length)
8739                                                 splitText(startContainer, endOffset - startOffset);
8740                                 }
8741                         } else {
8742                                 // Split startContainer text node if needed
8743                                 if (startContainer.nodeType == 3 && startOffset != 0) {
8744                                         startContainer = splitText(startContainer, startOffset);
8745                                         startOffset = 0;
8746                                 }
8747
8748                                 // Split endContainer text node if needed
8749                                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
8750                                         endContainer = splitText(endContainer, endOffset).previousSibling;
8751                                         endOffset = endContainer.nodeValue.length;
8752                                 }
8753                         }
8754
8755                         return {
8756                                 startContainer : startContainer,
8757                                 startOffset : startOffset,
8758                                 endContainer : endContainer,
8759                                 endOffset : endOffset
8760                         };
8761                 };
8762 */
8763         };
8764
8765         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
8766                 if (rng1 && rng2) {
8767                         // Compare native IE ranges
8768                         if (rng1.item || rng1.duplicate) {
8769                                 // Both are control ranges and the selected element matches
8770                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
8771                                         return true;
8772
8773                                 // Both are text ranges and the range matches
8774                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
8775                                         return true;
8776                         } else {
8777                                 // Compare w3c ranges
8778                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
8779                         }
8780                 }
8781
8782                 return false;
8783         };
8784 })(tinymce);
8785
8786 (function(tinymce) {
8787         var Event = tinymce.dom.Event, each = tinymce.each;
8788
8789         tinymce.create('tinymce.ui.KeyboardNavigation', {
8790                 KeyboardNavigation: function(settings, dom) {
8791                         var t = this, root = settings.root, items = settings.items,
8792                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
8793                                         excludeFromTabOrder = settings.excludeFromTabOrder,
8794                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
8795
8796                         dom = dom || tinymce.DOM;
8797
8798                         itemFocussed = function(evt) {
8799                                 focussedId = evt.target.id;
8800                         };
8801                         
8802                         itemBlurred = function(evt) {
8803                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');
8804                         };
8805                         
8806                         rootFocussed = function(evt) {
8807                                 var item = dom.get(focussedId);
8808                                 dom.setAttrib(item, 'tabindex', '0');
8809                                 item.focus();
8810                         };
8811                         
8812                         t.focus = function() {
8813                                 dom.get(focussedId).focus();
8814                         };
8815
8816                         t.destroy = function() {
8817                                 each(items, function(item) {
8818                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);
8819                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);
8820                                 });
8821
8822                                 dom.unbind(dom.get(root), 'focus', rootFocussed);
8823                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);
8824
8825                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
8826                                 t.destroy = function() {};
8827                         };
8828                         
8829                         t.moveFocus = function(dir, evt) {
8830                                 var idx = -1, controls = t.controls, newFocus;
8831
8832                                 if (!focussedId)
8833                                         return;
8834
8835                                 each(items, function(item, index) {
8836                                         if (item.id === focussedId) {
8837                                                 idx = index;
8838                                                 return false;
8839                                         }
8840                                 });
8841
8842                                 idx += dir;
8843                                 if (idx < 0) {
8844                                         idx = items.length - 1;
8845                                 } else if (idx >= items.length) {
8846                                         idx = 0;
8847                                 }
8848                                 
8849                                 newFocus = items[idx];
8850                                 dom.setAttrib(focussedId, 'tabindex', '-1');
8851                                 dom.setAttrib(newFocus.id, 'tabindex', '0');
8852                                 dom.get(newFocus.id).focus();
8853
8854                                 if (settings.actOnFocus) {
8855                                         settings.onAction(newFocus.id);
8856                                 }
8857
8858                                 if (evt)
8859                                         Event.cancel(evt);
8860                         };
8861                         
8862                         rootKeydown = function(evt) {
8863                                 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;
8864                                 
8865                                 switch (evt.keyCode) {
8866                                         case DOM_VK_LEFT:
8867                                                 if (enableLeftRight) t.moveFocus(-1);
8868                                                 break;
8869         
8870                                         case DOM_VK_RIGHT:
8871                                                 if (enableLeftRight) t.moveFocus(1);
8872                                                 break;
8873         
8874                                         case DOM_VK_UP:
8875                                                 if (enableUpDown) t.moveFocus(-1);
8876                                                 break;
8877
8878                                         case DOM_VK_DOWN:
8879                                                 if (enableUpDown) t.moveFocus(1);
8880                                                 break;
8881
8882                                         case DOM_VK_ESCAPE:
8883                                                 if (settings.onCancel) {
8884                                                         settings.onCancel();
8885                                                         Event.cancel(evt);
8886                                                 }
8887                                                 break;
8888
8889                                         case DOM_VK_ENTER:
8890                                         case DOM_VK_RETURN:
8891                                         case DOM_VK_SPACE:
8892                                                 if (settings.onAction) {
8893                                                         settings.onAction(focussedId);
8894                                                         Event.cancel(evt);
8895                                                 }
8896                                                 break;
8897                                 }
8898                         };
8899
8900                         // Set up state and listeners for each item.
8901                         each(items, function(item, idx) {
8902                                 var tabindex;
8903
8904                                 if (!item.id) {
8905                                         item.id = dom.uniqueId('_mce_item_');
8906                                 }
8907
8908                                 if (excludeFromTabOrder) {
8909                                         dom.bind(item.id, 'blur', itemBlurred);
8910                                         tabindex = '-1';
8911                                 } else {
8912                                         tabindex = (idx === 0 ? '0' : '-1');
8913                                 }
8914
8915                                 dom.setAttrib(item.id, 'tabindex', tabindex);
8916                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);
8917                         });
8918                         
8919                         // Setup initial state for root element.
8920                         if (items[0]){
8921                                 focussedId = items[0].id;
8922                         }
8923
8924                         dom.setAttrib(root, 'tabindex', '-1');
8925                         
8926                         // Setup listeners for root element.
8927                         dom.bind(dom.get(root), 'focus', rootFocussed);
8928                         dom.bind(dom.get(root), 'keydown', rootKeydown);
8929                 }
8930         });
8931 })(tinymce);
8932 (function(tinymce) {
8933         // Shorten class names
8934         var DOM = tinymce.DOM, is = tinymce.is;
8935
8936         tinymce.create('tinymce.ui.Control', {
8937                 Control : function(id, s, editor) {
8938                         this.id = id;
8939                         this.settings = s = s || {};
8940                         this.rendered = false;
8941                         this.onRender = new tinymce.util.Dispatcher(this);
8942                         this.classPrefix = '';
8943                         this.scope = s.scope || this;
8944                         this.disabled = 0;
8945                         this.active = 0;
8946                         this.editor = editor;
8947                 },
8948                 
8949                 setAriaProperty : function(property, value) {
8950                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
8951                         if (element) {
8952                                 DOM.setAttrib(element, 'aria-' + property, !!value);
8953                         }
8954                 },
8955                 
8956                 focus : function() {
8957                         DOM.get(this.id).focus();
8958                 },
8959
8960                 setDisabled : function(s) {
8961                         if (s != this.disabled) {
8962                                 this.setAriaProperty('disabled', s);
8963
8964                                 this.setState('Disabled', s);
8965                                 this.setState('Enabled', !s);
8966                                 this.disabled = s;
8967                         }
8968                 },
8969
8970                 isDisabled : function() {
8971                         return this.disabled;
8972                 },
8973
8974                 setActive : function(s) {
8975                         if (s != this.active) {
8976                                 this.setState('Active', s);
8977                                 this.active = s;
8978                                 this.setAriaProperty('pressed', s);
8979                         }
8980                 },
8981
8982                 isActive : function() {
8983                         return this.active;
8984                 },
8985
8986                 setState : function(c, s) {
8987                         var n = DOM.get(this.id);
8988
8989                         c = this.classPrefix + c;
8990
8991                         if (s)
8992                                 DOM.addClass(n, c);
8993                         else
8994                                 DOM.removeClass(n, c);
8995                 },
8996
8997                 isRendered : function() {
8998                         return this.rendered;
8999                 },
9000
9001                 renderHTML : function() {
9002                 },
9003
9004                 renderTo : function(n) {
9005                         DOM.setHTML(n, this.renderHTML());
9006                 },
9007
9008                 postRender : function() {
9009                         var t = this, b;
9010
9011                         // Set pending states
9012                         if (is(t.disabled)) {
9013                                 b = t.disabled;
9014                                 t.disabled = -1;
9015                                 t.setDisabled(b);
9016                         }
9017
9018                         if (is(t.active)) {
9019                                 b = t.active;
9020                                 t.active = -1;
9021                                 t.setActive(b);
9022                         }
9023                 },
9024
9025                 remove : function() {
9026                         DOM.remove(this.id);
9027                         this.destroy();
9028                 },
9029
9030                 destroy : function() {
9031                         tinymce.dom.Event.clear(this.id);
9032                 }
9033         });
9034 })(tinymce);
9035 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
9036         Container : function(id, s, editor) {
9037                 this.parent(id, s, editor);
9038
9039                 this.controls = [];
9040
9041                 this.lookup = {};
9042         },
9043
9044         add : function(c) {
9045                 this.lookup[c.id] = c;
9046                 this.controls.push(c);
9047
9048                 return c;
9049         },
9050
9051         get : function(n) {
9052                 return this.lookup[n];
9053         }
9054 });
9055
9056
9057 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9058         Separator : function(id, s) {
9059                 this.parent(id, s);
9060                 this.classPrefix = 'mceSeparator';
9061                 this.setDisabled(true);
9062         },
9063
9064         renderHTML : function() {
9065                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
9066         }
9067 });
9068
9069 (function(tinymce) {
9070         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9071
9072         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
9073                 MenuItem : function(id, s) {
9074                         this.parent(id, s);
9075                         this.classPrefix = 'mceMenuItem';
9076                 },
9077
9078                 setSelected : function(s) {
9079                         this.setState('Selected', s);
9080                         this.setAriaProperty('checked', !!s);
9081                         this.selected = s;
9082                 },
9083
9084                 isSelected : function() {
9085                         return this.selected;
9086                 },
9087
9088                 postRender : function() {
9089                         var t = this;
9090                         
9091                         t.parent();
9092
9093                         // Set pending state
9094                         if (is(t.selected))
9095                                 t.setSelected(t.selected);
9096                 }
9097         });
9098 })(tinymce);
9099
9100 (function(tinymce) {
9101         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9102
9103         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
9104                 Menu : function(id, s) {
9105                         var t = this;
9106
9107                         t.parent(id, s);
9108                         t.items = {};
9109                         t.collapsed = false;
9110                         t.menuCount = 0;
9111                         t.onAddItem = new tinymce.util.Dispatcher(this);
9112                 },
9113
9114                 expand : function(d) {
9115                         var t = this;
9116
9117                         if (d) {
9118                                 walk(t, function(o) {
9119                                         if (o.expand)
9120                                                 o.expand();
9121                                 }, 'items', t);
9122                         }
9123
9124                         t.collapsed = false;
9125                 },
9126
9127                 collapse : function(d) {
9128                         var t = this;
9129
9130                         if (d) {
9131                                 walk(t, function(o) {
9132                                         if (o.collapse)
9133                                                 o.collapse();
9134                                 }, 'items', t);
9135                         }
9136
9137                         t.collapsed = true;
9138                 },
9139
9140                 isCollapsed : function() {
9141                         return this.collapsed;
9142                 },
9143
9144                 add : function(o) {
9145                         if (!o.settings)
9146                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
9147
9148                         this.onAddItem.dispatch(this, o);
9149
9150                         return this.items[o.id] = o;
9151                 },
9152
9153                 addSeparator : function() {
9154                         return this.add({separator : true});
9155                 },
9156
9157                 addMenu : function(o) {
9158                         if (!o.collapse)
9159                                 o = this.createMenu(o);
9160
9161                         this.menuCount++;
9162
9163                         return this.add(o);
9164                 },
9165
9166                 hasMenus : function() {
9167                         return this.menuCount !== 0;
9168                 },
9169
9170                 remove : function(o) {
9171                         delete this.items[o.id];
9172                 },
9173
9174                 removeAll : function() {
9175                         var t = this;
9176
9177                         walk(t, function(o) {
9178                                 if (o.removeAll)
9179                                         o.removeAll();
9180                                 else
9181                                         o.remove();
9182
9183                                 o.destroy();
9184                         }, 'items', t);
9185
9186                         t.items = {};
9187                 },
9188
9189                 createMenu : function(o) {
9190                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
9191
9192                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
9193
9194                         return m;
9195                 }
9196         });
9197 })(tinymce);
9198 (function(tinymce) {
9199         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
9200
9201         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
9202                 DropMenu : function(id, s) {
9203                         s = s || {};
9204                         s.container = s.container || DOM.doc.body;
9205                         s.offset_x = s.offset_x || 0;
9206                         s.offset_y = s.offset_y || 0;
9207                         s.vp_offset_x = s.vp_offset_x || 0;
9208                         s.vp_offset_y = s.vp_offset_y || 0;
9209
9210                         if (is(s.icons) && !s.icons)
9211                                 s['class'] += ' mceNoIcons';
9212
9213                         this.parent(id, s);
9214                         this.onShowMenu = new tinymce.util.Dispatcher(this);
9215                         this.onHideMenu = new tinymce.util.Dispatcher(this);
9216                         this.classPrefix = 'mceMenu';
9217                 },
9218
9219                 createMenu : function(s) {
9220                         var t = this, cs = t.settings, m;
9221
9222                         s.container = s.container || cs.container;
9223                         s.parent = t;
9224                         s.constrain = s.constrain || cs.constrain;
9225                         s['class'] = s['class'] || cs['class'];
9226                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
9227                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
9228                         s.keyboard_focus = cs.keyboard_focus;
9229                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
9230
9231                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
9232
9233                         return m;
9234                 },
9235                 
9236                 focus : function() {
9237                         var t = this;
9238                         if (t.keyboardNav) {
9239                                 t.keyboardNav.focus();
9240                         }
9241                 },
9242
9243                 update : function() {
9244                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
9245
9246                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
9247                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
9248
9249                         if (!DOM.boxModel)
9250                                 t.element.setStyles({width : tw + 2, height : th + 2});
9251                         else
9252                                 t.element.setStyles({width : tw, height : th});
9253
9254                         if (s.max_width)
9255                                 DOM.setStyle(co, 'width', tw);
9256
9257                         if (s.max_height) {
9258                                 DOM.setStyle(co, 'height', th);
9259
9260                                 if (tb.clientHeight < s.max_height)
9261                                         DOM.setStyle(co, 'overflow', 'hidden');
9262                         }
9263                 },
9264
9265                 showMenu : function(x, y, px) {
9266                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
9267
9268                         t.collapse(1);
9269
9270                         if (t.isMenuVisible)
9271                                 return;
9272
9273                         if (!t.rendered) {
9274                                 co = DOM.add(t.settings.container, t.renderNode());
9275
9276                                 each(t.items, function(o) {
9277                                         o.postRender();
9278                                 });
9279
9280                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
9281                         } else
9282                                 co = DOM.get('menu_' + t.id);
9283
9284                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
9285                         if (!tinymce.isOpera)
9286                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
9287
9288                         DOM.show(co);
9289                         t.update();
9290
9291                         x += s.offset_x || 0;
9292                         y += s.offset_y || 0;
9293                         vp.w -= 4;
9294                         vp.h -= 4;
9295
9296                         // Move inside viewport if not submenu
9297                         if (s.constrain) {
9298                                 w = co.clientWidth - ot;
9299                                 h = co.clientHeight - ot;
9300                                 mx = vp.x + vp.w;
9301                                 my = vp.y + vp.h;
9302
9303                                 if ((x + s.vp_offset_x + w) > mx)
9304                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
9305
9306                                 if ((y + s.vp_offset_y + h) > my)
9307                                         y = Math.max(0, (my - s.vp_offset_y) - h);
9308                         }
9309
9310                         DOM.setStyles(co, {left : x , top : y});
9311                         t.element.update();
9312
9313                         t.isMenuVisible = 1;
9314                         t.mouseClickFunc = Event.add(co, 'click', function(e) {
9315                                 var m;
9316
9317                                 e = e.target;
9318
9319                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
9320                                         m = t.items[e.id];
9321
9322                                         if (m.isDisabled())
9323                                                 return;
9324
9325                                         dm = t;
9326
9327                                         while (dm) {
9328                                                 if (dm.hideMenu)
9329                                                         dm.hideMenu();
9330
9331                                                 dm = dm.settings.parent;
9332                                         }
9333
9334                                         if (m.settings.onclick)
9335                                                 m.settings.onclick(e);
9336
9337                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
9338                                 }
9339                         });
9340
9341                         if (t.hasMenus()) {
9342                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
9343                                         var m, r, mi;
9344
9345                                         e = e.target;
9346                                         if (e && (e = DOM.getParent(e, 'tr'))) {
9347                                                 m = t.items[e.id];
9348
9349                                                 if (t.lastMenu)
9350                                                         t.lastMenu.collapse(1);
9351
9352                                                 if (m.isDisabled())
9353                                                         return;
9354
9355                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
9356                                                         //p = DOM.getPos(s.container);
9357                                                         r = DOM.getRect(e);
9358                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
9359                                                         t.lastMenu = m;
9360                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
9361                                                 }
9362                                         }
9363                                 });
9364                         }
9365                         
9366                         Event.add(co, 'keydown', t._keyHandler, t);
9367
9368                         t.onShowMenu.dispatch(t);
9369
9370                         if (s.keyboard_focus) { 
9371                                 t._setupKeyboardNav(); 
9372                         }
9373                 },
9374
9375                 hideMenu : function(c) {
9376                         var t = this, co = DOM.get('menu_' + t.id), e;
9377
9378                         if (!t.isMenuVisible)
9379                                 return;
9380
9381                         if (t.keyboardNav) t.keyboardNav.destroy();
9382                         Event.remove(co, 'mouseover', t.mouseOverFunc);
9383                         Event.remove(co, 'click', t.mouseClickFunc);
9384                         Event.remove(co, 'keydown', t._keyHandler);
9385                         DOM.hide(co);
9386                         t.isMenuVisible = 0;
9387
9388                         if (!c)
9389                                 t.collapse(1);
9390
9391                         if (t.element)
9392                                 t.element.hide();
9393
9394                         if (e = DOM.get(t.id))
9395                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
9396
9397                         t.onHideMenu.dispatch(t);
9398                 },
9399
9400                 add : function(o) {
9401                         var t = this, co;
9402
9403                         o = t.parent(o);
9404
9405                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))
9406                                 t._add(DOM.select('tbody', co)[0], o);
9407
9408                         return o;
9409                 },
9410
9411                 collapse : function(d) {
9412                         this.parent(d);
9413                         this.hideMenu(1);
9414                 },
9415
9416                 remove : function(o) {
9417                         DOM.remove(o.id);
9418                         this.destroy();
9419
9420                         return this.parent(o);
9421                 },
9422
9423                 destroy : function() {
9424                         var t = this, co = DOM.get('menu_' + t.id);
9425
9426                         if (t.keyboardNav) t.keyboardNav.destroy();
9427                         Event.remove(co, 'mouseover', t.mouseOverFunc);
9428                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
9429                         Event.remove(co, 'click', t.mouseClickFunc);
9430                         Event.remove(co, 'keydown', t._keyHandler);
9431
9432                         if (t.element)
9433                                 t.element.remove();
9434
9435                         DOM.remove(co);
9436                 },
9437
9438                 renderNode : function() {
9439                         var t = this, s = t.settings, n, tb, co, w;
9440
9441                         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'});
9442                         if (t.settings.parent) {
9443                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
9444                         }
9445                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
9446                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
9447
9448                         if (s.menu_line)
9449                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
9450
9451 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
9452                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
9453                         tb = DOM.add(n, 'tbody');
9454
9455                         each(t.items, function(o) {
9456                                 t._add(tb, o);
9457                         });
9458
9459                         t.rendered = true;
9460
9461                         return w;
9462                 },
9463
9464                 // Internal functions
9465                 _setupKeyboardNav : function(){
9466                         var contextMenu, menuItems, t=this; 
9467                         contextMenu = DOM.select('#menu_' + t.id)[0];
9468                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
9469                         menuItems.splice(0,0,contextMenu);
9470                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({
9471                                 root: 'menu_' + t.id,
9472                                 items: menuItems,
9473                                 onCancel: function() {
9474                                         t.hideMenu();
9475                                 },
9476                                 enableUpDown: true
9477                         });
9478                         contextMenu.focus();
9479                 },
9480
9481                 _keyHandler : function(evt) {
9482                         var t = this, e;
9483                         switch (evt.keyCode) {
9484                                 case 37: // Left
9485                                         if (t.settings.parent) {
9486                                                 t.hideMenu();
9487                                                 t.settings.parent.focus();
9488                                                 Event.cancel(evt);
9489                                         }
9490                                         break;
9491                                 case 39: // Right
9492                                         if (t.mouseOverFunc)
9493                                                 t.mouseOverFunc(evt);
9494                                         break;
9495                         }
9496                 },
9497
9498                 _add : function(tb, o) {
9499                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
9500
9501                         if (s.separator) {
9502                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
9503                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
9504
9505                                 if (n = ro.previousSibling)
9506                                         DOM.addClass(n, 'mceLast');
9507
9508                                 return;
9509                         }
9510
9511                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
9512                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
9513                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
9514
9515                         if (s.parent) {
9516                                 DOM.setAttrib(a, 'aria-haspopup', 'true');
9517                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
9518                         }
9519
9520                         DOM.addClass(it, s['class']);
9521 //                      n = DOM.add(n, 'span', {'class' : 'item'});
9522
9523                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
9524
9525                         if (s.icon_src)
9526                                 DOM.add(ic, 'img', {src : s.icon_src});
9527
9528                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
9529
9530                         if (o.settings.style)
9531                                 DOM.setAttrib(n, 'style', o.settings.style);
9532
9533                         if (tb.childNodes.length == 1)
9534                                 DOM.addClass(ro, 'mceFirst');
9535
9536                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
9537                                 DOM.addClass(ro, 'mceFirst');
9538
9539                         if (o.collapse)
9540                                 DOM.addClass(ro, cp + 'ItemSub');
9541
9542                         if (n = ro.previousSibling)
9543                                 DOM.removeClass(n, 'mceLast');
9544
9545                         DOM.addClass(ro, 'mceLast');
9546                 }
9547         });
9548 })(tinymce);
9549 (function(tinymce) {
9550         var DOM = tinymce.DOM;
9551
9552         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
9553                 Button : function(id, s, ed) {
9554                         this.parent(id, s, ed);
9555                         this.classPrefix = 'mceButton';
9556                 },
9557
9558                 renderHTML : function() {
9559                         var cp = this.classPrefix, s = this.settings, h, l;
9560
9561                         l = DOM.encode(s.label || '');
9562                         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) + '">';
9563                         if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
9564                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
9565                         else
9566                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
9567
9568                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
9569                         h += '</a>';
9570                         return h;
9571                 },
9572
9573                 postRender : function() {
9574                         var t = this, s = t.settings;
9575
9576                         tinymce.dom.Event.add(t.id, 'click', function(e) {
9577                                 if (!t.isDisabled())
9578                                         return s.onclick.call(s.scope, e);
9579                         });
9580                 }
9581         });
9582 })(tinymce);
9583
9584 (function(tinymce) {
9585         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9586
9587         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
9588                 ListBox : function(id, s, ed) {
9589                         var t = this;
9590
9591                         t.parent(id, s, ed);
9592
9593                         t.items = [];
9594
9595                         t.onChange = new Dispatcher(t);
9596
9597                         t.onPostRender = new Dispatcher(t);
9598
9599                         t.onAdd = new Dispatcher(t);
9600
9601                         t.onRenderMenu = new tinymce.util.Dispatcher(this);
9602
9603                         t.classPrefix = 'mceListBox';
9604                 },
9605
9606                 select : function(va) {
9607                         var t = this, fv, f;
9608
9609                         if (va == undefined)
9610                                 return t.selectByIndex(-1);
9611
9612                         // Is string or number make function selector
9613                         if (va && va.call)
9614                                 f = va;
9615                         else {
9616                                 f = function(v) {
9617                                         return v == va;
9618                                 };
9619                         }
9620
9621                         // Do we need to do something?
9622                         if (va != t.selectedValue) {
9623                                 // Find item
9624                                 each(t.items, function(o, i) {
9625                                         if (f(o.value)) {
9626                                                 fv = 1;
9627                                                 t.selectByIndex(i);
9628                                                 return false;
9629                                         }
9630                                 });
9631
9632                                 if (!fv)
9633                                         t.selectByIndex(-1);
9634                         }
9635                 },
9636
9637                 selectByIndex : function(idx) {
9638                         var t = this, e, o;
9639
9640                         if (idx != t.selectedIndex) {
9641                                 e = DOM.get(t.id + '_text');
9642                                 o = t.items[idx];
9643
9644                                 if (o) {
9645                                         t.selectedValue = o.value;
9646                                         t.selectedIndex = idx;
9647                                         DOM.setHTML(e, DOM.encode(o.title));
9648                                         DOM.removeClass(e, 'mceTitle');
9649                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);
9650                                 } else {
9651                                         DOM.setHTML(e, DOM.encode(t.settings.title));
9652                                         DOM.addClass(e, 'mceTitle');
9653                                         t.selectedValue = t.selectedIndex = null;
9654                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
9655                                 }
9656                                 e = 0;
9657                         }
9658                 },
9659
9660                 add : function(n, v, o) {
9661                         var t = this;
9662
9663                         o = o || {};
9664                         o = tinymce.extend(o, {
9665                                 title : n,
9666                                 value : v
9667                         });
9668
9669                         t.items.push(o);
9670                         t.onAdd.dispatch(t, o);
9671                 },
9672
9673                 getLength : function() {
9674                         return this.items.length;
9675                 },
9676
9677                 renderHTML : function() {
9678                         var h = '', t = this, s = t.settings, cp = t.classPrefix;
9679
9680                         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>';
9681                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
9682                         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>';
9683                         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>';
9684                         h += '</tr></tbody></table></span>';
9685
9686                         return h;
9687                 },
9688
9689                 showMenu : function() {
9690                         var t = this, p2, e = DOM.get(this.id), m;
9691
9692                         if (t.isDisabled() || t.items.length == 0)
9693                                 return;
9694
9695                         if (t.menu && t.menu.isMenuVisible)
9696                                 return t.hideMenu();
9697
9698                         if (!t.isMenuRendered) {
9699                                 t.renderMenu();
9700                                 t.isMenuRendered = true;
9701                         }
9702
9703                         p2 = DOM.getPos(e);
9704
9705                         m = t.menu;
9706                         m.settings.offset_x = p2.x;
9707                         m.settings.offset_y = p2.y;
9708                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
9709
9710                         // Select in menu
9711                         if (t.oldID)
9712                                 m.items[t.oldID].setSelected(0);
9713
9714                         each(t.items, function(o) {
9715                                 if (o.value === t.selectedValue) {
9716                                         m.items[o.id].setSelected(1);
9717                                         t.oldID = o.id;
9718                                 }
9719                         });
9720
9721                         m.showMenu(0, e.clientHeight);
9722
9723                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9724                         DOM.addClass(t.id, t.classPrefix + 'Selected');
9725
9726                         //DOM.get(t.id + '_text').focus();
9727                 },
9728
9729                 hideMenu : function(e) {
9730                         var t = this;
9731
9732                         if (t.menu && t.menu.isMenuVisible) {
9733                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');
9734
9735                                 // Prevent double toogles by canceling the mouse click event to the button
9736                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
9737                                         return;
9738
9739                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9740                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');
9741                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9742                                         t.menu.hideMenu();
9743                                 }
9744                         }
9745                 },
9746
9747                 renderMenu : function() {
9748                         var t = this, m;
9749
9750                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9751                                 menu_line : 1,
9752                                 'class' : t.classPrefix + 'Menu mceNoIcons',
9753                                 max_width : 150,
9754                                 max_height : 150
9755                         });
9756
9757                         m.onHideMenu.add(function() {
9758                                 t.hideMenu();
9759                                 t.focus();
9760                         });
9761
9762                         m.add({
9763                                 title : t.settings.title,
9764                                 'class' : 'mceMenuItemTitle',
9765                                 onclick : function() {
9766                                         if (t.settings.onselect('') !== false)
9767                                                 t.select(''); // Must be runned after
9768                                 }
9769                         });
9770
9771                         each(t.items, function(o) {
9772                                 // No value then treat it as a title
9773                                 if (o.value === undefined) {
9774                                         m.add({
9775                                                 title : o.title,
9776                                                 'class' : 'mceMenuItemTitle',
9777                                                 onclick : function() {
9778                                                         if (t.settings.onselect('') !== false)
9779                                                                 t.select(''); // Must be runned after
9780                                                 }
9781                                         });
9782                                 } else {
9783                                         o.id = DOM.uniqueId();
9784                                         o.onclick = function() {
9785                                                 if (t.settings.onselect(o.value) !== false)
9786                                                         t.select(o.value); // Must be runned after
9787                                         };
9788
9789                                         m.add(o);
9790                                 }
9791                         });
9792
9793                         t.onRenderMenu.dispatch(t, m);
9794                         t.menu = m;
9795                 },
9796
9797                 postRender : function() {
9798                         var t = this, cp = t.classPrefix;
9799
9800                         Event.add(t.id, 'click', t.showMenu, t);
9801                         Event.add(t.id, 'keydown', function(evt) {
9802                                 if (evt.keyCode == 32) { // Space
9803                                         t.showMenu(evt);
9804                                         Event.cancel(evt);
9805                                 }
9806                         });
9807                         Event.add(t.id, 'focus', function() {
9808                                 if (!t._focused) {
9809                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
9810                                                 if (e.keyCode == 40) {
9811                                                         t.showMenu();
9812                                                         Event.cancel(e);
9813                                                 }
9814                                         });
9815                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
9816                                                 var v;
9817                                                 if (e.keyCode == 13) {
9818                                                         // Fake select on enter
9819                                                         v = t.selectedValue;
9820                                                         t.selectedValue = null; // Needs to be null to fake change
9821                                                         Event.cancel(e);
9822                                                         t.settings.onselect(v);
9823                                                 }
9824                                         });
9825                                 }
9826
9827                                 t._focused = 1;
9828                         });
9829                         Event.add(t.id, 'blur', function() {
9830                                 Event.remove(t.id, 'keydown', t.keyDownHandler);
9831                                 Event.remove(t.id, 'keypress', t.keyPressHandler);
9832                                 t._focused = 0;
9833                         });
9834
9835                         // Old IE doesn't have hover on all elements
9836                         if (tinymce.isIE6 || !DOM.boxModel) {
9837                                 Event.add(t.id, 'mouseover', function() {
9838                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9839                                                 DOM.addClass(t.id, cp + 'Hover');
9840                                 });
9841
9842                                 Event.add(t.id, 'mouseout', function() {
9843                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9844                                                 DOM.removeClass(t.id, cp + 'Hover');
9845                                 });
9846                         }
9847
9848                         t.onPostRender.dispatch(t, DOM.get(t.id));
9849                 },
9850
9851                 destroy : function() {
9852                         this.parent();
9853
9854                         Event.clear(this.id + '_text');
9855                         Event.clear(this.id + '_open');
9856                 }
9857         });
9858 })(tinymce);
9859 (function(tinymce) {
9860         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9861
9862         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
9863                 NativeListBox : function(id, s) {
9864                         this.parent(id, s);
9865                         this.classPrefix = 'mceNativeListBox';
9866                 },
9867
9868                 setDisabled : function(s) {
9869                         DOM.get(this.id).disabled = s;
9870                         this.setAriaProperty('disabled', s);
9871                 },
9872
9873                 isDisabled : function() {
9874                         return DOM.get(this.id).disabled;
9875                 },
9876
9877                 select : function(va) {
9878                         var t = this, fv, f;
9879
9880                         if (va == undefined)
9881                                 return t.selectByIndex(-1);
9882
9883                         // Is string or number make function selector
9884                         if (va && va.call)
9885                                 f = va;
9886                         else {
9887                                 f = function(v) {
9888                                         return v == va;
9889                                 };
9890                         }
9891
9892                         // Do we need to do something?
9893                         if (va != t.selectedValue) {
9894                                 // Find item
9895                                 each(t.items, function(o, i) {
9896                                         if (f(o.value)) {
9897                                                 fv = 1;
9898                                                 t.selectByIndex(i);
9899                                                 return false;
9900                                         }
9901                                 });
9902
9903                                 if (!fv)
9904                                         t.selectByIndex(-1);
9905                         }
9906                 },
9907
9908                 selectByIndex : function(idx) {
9909                         DOM.get(this.id).selectedIndex = idx + 1;
9910                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;
9911                 },
9912
9913                 add : function(n, v, a) {
9914                         var o, t = this;
9915
9916                         a = a || {};
9917                         a.value = v;
9918
9919                         if (t.isRendered())
9920                                 DOM.add(DOM.get(this.id), 'option', a, n);
9921
9922                         o = {
9923                                 title : n,
9924                                 value : v,
9925                                 attribs : a
9926                         };
9927
9928                         t.items.push(o);
9929                         t.onAdd.dispatch(t, o);
9930                 },
9931
9932                 getLength : function() {
9933                         return this.items.length;
9934                 },
9935
9936                 renderHTML : function() {
9937                         var h, t = this;
9938
9939                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
9940
9941                         each(t.items, function(it) {
9942                                 h += DOM.createHTML('option', {value : it.value}, it.title);
9943                         });
9944
9945                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
9946                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
9947                         return h;
9948                 },
9949
9950                 postRender : function() {
9951                         var t = this, ch, changeListenerAdded = true;
9952
9953                         t.rendered = true;
9954
9955                         function onChange(e) {
9956                                 var v = t.items[e.target.selectedIndex - 1];
9957
9958                                 if (v && (v = v.value)) {
9959                                         t.onChange.dispatch(t, v);
9960
9961                                         if (t.settings.onselect)
9962                                                 t.settings.onselect(v);
9963                                 }
9964                         };
9965
9966                         Event.add(t.id, 'change', onChange);
9967
9968                         // Accessibility keyhandler
9969                         Event.add(t.id, 'keydown', function(e) {
9970                                 var bf;
9971
9972                                 Event.remove(t.id, 'change', ch);
9973                                 changeListenerAdded = false;
9974
9975                                 bf = Event.add(t.id, 'blur', function() {
9976                                         if (changeListenerAdded) return;
9977                                         changeListenerAdded = true;
9978                                         Event.add(t.id, 'change', onChange);
9979                                         Event.remove(t.id, 'blur', bf);
9980                                 });
9981
9982                                 if (e.keyCode == 13 || e.keyCode == 32) {
9983                                         onChange(e);
9984                                         return Event.cancel(e);
9985                                 }
9986                         });
9987
9988                         t.onPostRender.dispatch(t, DOM.get(t.id));
9989                 }
9990         });
9991 })(tinymce);
9992 (function(tinymce) {
9993         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9994
9995         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
9996                 MenuButton : function(id, s, ed) {
9997                         this.parent(id, s, ed);
9998
9999                         this.onRenderMenu = new tinymce.util.Dispatcher(this);
10000
10001                         s.menu_container = s.menu_container || DOM.doc.body;
10002                 },
10003
10004                 showMenu : function() {
10005                         var t = this, p1, p2, e = DOM.get(t.id), m;
10006
10007                         if (t.isDisabled())
10008                                 return;
10009
10010                         if (!t.isMenuRendered) {
10011                                 t.renderMenu();
10012                                 t.isMenuRendered = true;
10013                         }
10014
10015                         if (t.isMenuVisible)
10016                                 return t.hideMenu();
10017
10018                         p1 = DOM.getPos(t.settings.menu_container);
10019                         p2 = DOM.getPos(e);
10020
10021                         m = t.menu;
10022                         m.settings.offset_x = p2.x;
10023                         m.settings.offset_y = p2.y;
10024                         m.settings.vp_offset_x = p2.x;
10025                         m.settings.vp_offset_y = p2.y;
10026                         m.settings.keyboard_focus = t._focused;
10027                         m.showMenu(0, e.clientHeight);
10028
10029                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10030                         t.setState('Selected', 1);
10031
10032                         t.isMenuVisible = 1;
10033                 },
10034
10035                 renderMenu : function() {
10036                         var t = this, m;
10037
10038                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10039                                 menu_line : 1,
10040                                 'class' : this.classPrefix + 'Menu',
10041                                 icons : t.settings.icons
10042                         });
10043
10044                         m.onHideMenu.add(function() {
10045                                 t.hideMenu();
10046                                 t.focus();
10047                         });
10048
10049                         t.onRenderMenu.dispatch(t, m);
10050                         t.menu = m;
10051                 },
10052
10053                 hideMenu : function(e) {
10054                         var t = this;
10055
10056                         // Prevent double toogles by canceling the mouse click event to the button
10057                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
10058                                 return;
10059
10060                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10061                                 t.setState('Selected', 0);
10062                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10063                                 if (t.menu)
10064                                         t.menu.hideMenu();
10065                         }
10066
10067                         t.isMenuVisible = 0;
10068                 },
10069
10070                 postRender : function() {
10071                         var t = this, s = t.settings;
10072
10073                         Event.add(t.id, 'click', function() {
10074                                 if (!t.isDisabled()) {
10075                                         if (s.onclick)
10076                                                 s.onclick(t.value);
10077
10078                                         t.showMenu();
10079                                 }
10080                         });
10081                 }
10082         });
10083 })(tinymce);
10084
10085 (function(tinymce) {
10086         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10087
10088         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
10089                 SplitButton : function(id, s, ed) {
10090                         this.parent(id, s, ed);
10091                         this.classPrefix = 'mceSplitButton';
10092                 },
10093
10094                 renderHTML : function() {
10095                         var h, t = this, s = t.settings, h1;
10096
10097                         h = '<tbody><tr>';
10098
10099                         if (s.image)
10100                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
10101                         else
10102                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
10103
10104                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
10105                         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>';
10106         
10107                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
10108                         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>';
10109
10110                         h += '</tr></tbody>';
10111                         h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
10112                         return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
10113                 },
10114
10115                 postRender : function() {
10116                         var t = this, s = t.settings, activate;
10117
10118                         if (s.onclick) {
10119                                 activate = function(evt) {
10120                                         if (!t.isDisabled()) {
10121                                                 s.onclick(t.value);
10122                                                 Event.cancel(evt);
10123                                         }
10124                                 };
10125                                 Event.add(t.id + '_action', 'click', activate);
10126                                 Event.add(t.id, ['click', 'keydown'], function(evt) {
10127                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
10128                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
10129                                                 activate();
10130                                                 Event.cancel(evt);
10131                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
10132                                                 t.showMenu();
10133                                                 Event.cancel(evt);
10134                                         }
10135                                 });
10136                         }
10137
10138                         Event.add(t.id + '_open', 'click', function (evt) {
10139                                 t.showMenu();
10140                                 Event.cancel(evt);
10141                         });
10142                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
10143                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
10144
10145                         // Old IE doesn't have hover on all elements
10146                         if (tinymce.isIE6 || !DOM.boxModel) {
10147                                 Event.add(t.id, 'mouseover', function() {
10148                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
10149                                                 DOM.addClass(t.id, 'mceSplitButtonHover');
10150                                 });
10151
10152                                 Event.add(t.id, 'mouseout', function() {
10153                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
10154                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');
10155                                 });
10156                         }
10157                 },
10158
10159                 destroy : function() {
10160                         this.parent();
10161
10162                         Event.clear(this.id + '_action');
10163                         Event.clear(this.id + '_open');
10164                         Event.clear(this.id);
10165                 }
10166         });
10167 })(tinymce);
10168
10169 (function(tinymce) {
10170         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
10171
10172         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
10173                 ColorSplitButton : function(id, s, ed) {
10174                         var t = this;
10175
10176                         t.parent(id, s, ed);
10177
10178                         t.settings = s = tinymce.extend({
10179                                 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',
10180                                 grid_width : 8,
10181                                 default_color : '#888888'
10182                         }, t.settings);
10183
10184                         t.onShowMenu = new tinymce.util.Dispatcher(t);
10185
10186                         t.onHideMenu = new tinymce.util.Dispatcher(t);
10187
10188                         t.value = s.default_color;
10189                 },
10190
10191                 showMenu : function() {
10192                         var t = this, r, p, e, p2;
10193
10194                         if (t.isDisabled())
10195                                 return;
10196
10197                         if (!t.isMenuRendered) {
10198                                 t.renderMenu();
10199                                 t.isMenuRendered = true;
10200                         }
10201
10202                         if (t.isMenuVisible)
10203                                 return t.hideMenu();
10204
10205                         e = DOM.get(t.id);
10206                         DOM.show(t.id + '_menu');
10207                         DOM.addClass(e, 'mceSplitButtonSelected');
10208                         p2 = DOM.getPos(e);
10209                         DOM.setStyles(t.id + '_menu', {
10210                                 left : p2.x,
10211                                 top : p2.y + e.clientHeight,
10212                                 zIndex : 200000
10213                         });
10214                         e = 0;
10215
10216                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10217                         t.onShowMenu.dispatch(t);
10218
10219                         if (t._focused) {
10220                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
10221                                         if (e.keyCode == 27)
10222                                                 t.hideMenu();
10223                                 });
10224
10225                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
10226                         }
10227
10228                         t.isMenuVisible = 1;
10229                 },
10230
10231                 hideMenu : function(e) {
10232                         var t = this;
10233
10234                         if (t.isMenuVisible) {
10235                                 // Prevent double toogles by canceling the mouse click event to the button
10236                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
10237                                         return;
10238
10239                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
10240                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');
10241                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10242                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
10243                                         DOM.hide(t.id + '_menu');
10244                                 }
10245
10246                                 t.isMenuVisible = 0;
10247                         }
10248                 },
10249
10250                 renderMenu : function() {
10251                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
10252
10253                         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;'});
10254                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
10255                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});
10256
10257                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
10258                         tb = DOM.add(n, 'tbody');
10259
10260                         // Generate color grid
10261                         i = 0;
10262                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
10263                                 c = c.replace(/^#/, '');
10264
10265                                 if (!i--) {
10266                                         tr = DOM.add(tb, 'tr');
10267                                         i = s.grid_width - 1;
10268                                 }
10269
10270                                 n = DOM.add(tr, 'td');
10271                                 n = DOM.add(n, 'a', {
10272                                         role : 'option',
10273                                         href : 'javascript:;',
10274                                         style : {
10275                                                 backgroundColor : '#' + c
10276                                         },
10277                                         'title': t.editor.getLang('colors.' + c, c),
10278                                         'data-mce-color' : '#' + c
10279                                 });
10280
10281                                 if (t.editor.forcedHighContrastMode) {
10282                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
10283                                         if (n.getContext && (context = n.getContext("2d"))) {
10284                                                 context.fillStyle = '#' + c;
10285                                                 context.fillRect(0, 0, 16, 16);
10286                                         } else {
10287                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.
10288                                                 DOM.remove(n);
10289                                         }
10290                                 }
10291                         });
10292
10293                         if (s.more_colors_func) {
10294                                 n = DOM.add(tb, 'tr');
10295                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
10296                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
10297
10298                                 Event.add(n, 'click', function(e) {
10299                                         s.more_colors_func.call(s.more_colors_scope || this);
10300                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
10301                                 });
10302                         }
10303
10304                         DOM.addClass(m, 'mceColorSplitMenu');
10305                         
10306                         new tinymce.ui.KeyboardNavigation({
10307                                 root: t.id + '_menu',
10308                                 items: DOM.select('a', t.id + '_menu'),
10309                                 onCancel: function() {
10310                                         t.hideMenu();
10311                                         t.focus();
10312                                 }
10313                         });
10314
10315                         // Prevent IE from scrolling and hindering click to occur #4019
10316                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
10317
10318                         Event.add(t.id + '_menu', 'click', function(e) {
10319                                 var c;
10320
10321                                 e = DOM.getParent(e.target, 'a', tb);
10322
10323                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
10324                                         t.setColor(c);
10325
10326                                 return Event.cancel(e); // Prevent IE auto save warning
10327                         });
10328
10329                         return w;
10330                 },
10331
10332                 setColor : function(c) {
10333                         this.displayColor(c);
10334                         this.hideMenu();
10335                         this.settings.onselect(c);
10336                 },
10337                 
10338                 displayColor : function(c) {
10339                         var t = this;
10340
10341                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
10342
10343                         t.value = c;
10344                 },
10345
10346                 postRender : function() {
10347                         var t = this, id = t.id;
10348
10349                         t.parent();
10350                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
10351                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
10352                 },
10353
10354                 destroy : function() {
10355                         this.parent();
10356
10357                         Event.clear(this.id + '_menu');
10358                         Event.clear(this.id + '_more');
10359                         DOM.remove(this.id + '_menu');
10360                 }
10361         });
10362 })(tinymce);
10363
10364 (function(tinymce) {
10365 // Shorten class names
10366 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
10367 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
10368         renderHTML : function() {
10369                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
10370
10371                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
10372                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
10373                 h.push("<span role='application'>");
10374                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
10375                 each(controls, function(toolbar) {
10376                         h.push(toolbar.renderHTML());
10377                 });
10378                 h.push("</span>");
10379                 h.push('</div>');
10380
10381                 return h.join('');
10382         },
10383         
10384         focus : function() {
10385                 this.keyNav.focus();
10386         },
10387         
10388         postRender : function() {
10389                 var t = this, items = [];
10390
10391                 each(t.controls, function(toolbar) {
10392                         each (toolbar.controls, function(control) {
10393                                 if (control.id) {
10394                                         items.push(control);
10395                                 }
10396                         });
10397                 });
10398
10399                 t.keyNav = new tinymce.ui.KeyboardNavigation({
10400                         root: t.id,
10401                         items: items,
10402                         onCancel: function() {
10403                                 t.editor.focus();
10404                         },
10405                         excludeFromTabOrder: !t.settings.tab_focus_toolbar
10406                 });
10407         },
10408         
10409         destroy : function() {
10410                 var self = this;
10411
10412                 self.parent();
10413                 self.keyNav.destroy();
10414                 Event.clear(self.id);
10415         }
10416 });
10417 })(tinymce);
10418
10419 (function(tinymce) {
10420 // Shorten class names
10421 var dom = tinymce.DOM, each = tinymce.each;
10422 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10423         renderHTML : function() {
10424                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
10425
10426                 cl = t.controls;
10427                 for (i=0; i<cl.length; i++) {
10428                         // Get current control, prev control, next control and if the control is a list box or not
10429                         co = cl[i];
10430                         pr = cl[i - 1];
10431                         nx = cl[i + 1];
10432
10433                         // Add toolbar start
10434                         if (i === 0) {
10435                                 c = 'mceToolbarStart';
10436
10437                                 if (co.Button)
10438                                         c += ' mceToolbarStartButton';
10439                                 else if (co.SplitButton)
10440                                         c += ' mceToolbarStartSplitButton';
10441                                 else if (co.ListBox)
10442                                         c += ' mceToolbarStartListBox';
10443
10444                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10445                         }
10446
10447                         // Add toolbar end before list box and after the previous button
10448                         // This is to fix the o2k7 editor skins
10449                         if (pr && co.ListBox) {
10450                                 if (pr.Button || pr.SplitButton)
10451                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
10452                         }
10453
10454                         // Render control HTML
10455
10456                         // IE 8 quick fix, needed to propertly generate a hit area for anchors
10457                         if (dom.stdMode)
10458                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
10459                         else
10460                                 h += '<td>' + co.renderHTML() + '</td>';
10461
10462                         // Add toolbar start after list box and before the next button
10463                         // This is to fix the o2k7 editor skins
10464                         if (nx && co.ListBox) {
10465                                 if (nx.Button || nx.SplitButton)
10466                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
10467                         }
10468                 }
10469
10470                 c = 'mceToolbarEnd';
10471
10472                 if (co.Button)
10473                         c += ' mceToolbarEndButton';
10474                 else if (co.SplitButton)
10475                         c += ' mceToolbarEndSplitButton';
10476                 else if (co.ListBox)
10477                         c += ' mceToolbarEndListBox';
10478
10479                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10480
10481                 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>');
10482         }
10483 });
10484 })(tinymce);
10485
10486 (function(tinymce) {
10487         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
10488
10489         tinymce.create('tinymce.AddOnManager', {
10490                 AddOnManager : function() {
10491                         var self = this;
10492
10493                         self.items = [];
10494                         self.urls = {};
10495                         self.lookup = {};
10496                         self.onAdd = new Dispatcher(self);
10497                 },
10498
10499                 get : function(n) {
10500                         if (this.lookup[n]) {
10501                                 return this.lookup[n].instance;
10502                         } else {
10503                                 return undefined;
10504                         }
10505                 },
10506
10507                 dependencies : function(n) {
10508                         var result;
10509                         if (this.lookup[n]) {
10510                                 result = this.lookup[n].dependencies;
10511                         }
10512                         return result || [];
10513                 },
10514
10515                 requireLangPack : function(n) {
10516                         var s = tinymce.settings;
10517
10518                         if (s && s.language && s.language_load !== false)
10519                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
10520                 },
10521
10522                 add : function(id, o, dependencies) {
10523                         this.items.push(o);
10524                         this.lookup[id] = {instance:o, dependencies:dependencies};
10525                         this.onAdd.dispatch(this, id, o);
10526
10527                         return o;
10528                 },
10529                 createUrl: function(baseUrl, dep) {
10530                         if (typeof dep === "object") {
10531                                 return dep
10532                         } else {
10533                                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
10534                         }
10535                 },
10536
10537                 addComponents: function(pluginName, scripts) {
10538                         var pluginUrl = this.urls[pluginName];
10539                         tinymce.each(scripts, function(script){
10540                                 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 
10541                         });
10542                 },
10543
10544                 load : function(n, u, cb, s) {
10545                         var t = this, url = u;
10546
10547                         function loadDependencies() {
10548                                 var dependencies = t.dependencies(n);
10549                                 tinymce.each(dependencies, function(dep) {
10550                                         var newUrl = t.createUrl(u, dep);
10551                                         t.load(newUrl.resource, newUrl, undefined, undefined);
10552                                 });
10553                                 if (cb) {
10554                                         if (s) {
10555                                                 cb.call(s);
10556                                         } else {
10557                                                 cb.call(tinymce.ScriptLoader);
10558                                         }
10559                                 }
10560                         }
10561
10562                         if (t.urls[n])
10563                                 return;
10564                         if (typeof u === "object")
10565                                 url = u.prefix + u.resource + u.suffix;
10566
10567                         if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
10568                                 url = tinymce.baseURL + '/' + url;
10569
10570                         t.urls[n] = url.substring(0, url.lastIndexOf('/'));
10571
10572                         if (t.lookup[n]) {
10573                                 loadDependencies();
10574                         } else {
10575                                 tinymce.ScriptLoader.add(url, loadDependencies, s);
10576                         }
10577                 }
10578         });
10579
10580         // Create plugin and theme managers
10581         tinymce.PluginManager = new tinymce.AddOnManager();
10582         tinymce.ThemeManager = new tinymce.AddOnManager();
10583 }(tinymce));
10584
10585 (function(tinymce) {
10586         // Shorten names
10587         var each = tinymce.each, extend = tinymce.extend,
10588                 DOM = tinymce.DOM, Event = tinymce.dom.Event,
10589                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10590                 explode = tinymce.explode,
10591                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
10592
10593         // Setup some URLs where the editor API is located and where the document is
10594         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
10595         if (!/[\/\\]$/.test(tinymce.documentBaseURL))
10596                 tinymce.documentBaseURL += '/';
10597
10598         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
10599
10600         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
10601
10602         // Add before unload listener
10603         // This was required since IE was leaking memory if you added and removed beforeunload listeners
10604         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
10605         tinymce.onBeforeUnload = new Dispatcher(tinymce);
10606
10607         // Must be on window or IE will leak if the editor is placed in frame or iframe
10608         Event.add(window, 'beforeunload', function(e) {
10609                 tinymce.onBeforeUnload.dispatch(tinymce, e);
10610         });
10611
10612         tinymce.onAddEditor = new Dispatcher(tinymce);
10613
10614         tinymce.onRemoveEditor = new Dispatcher(tinymce);
10615
10616         tinymce.EditorManager = extend(tinymce, {
10617                 editors : [],
10618
10619                 i18n : {},
10620
10621                 activeEditor : null,
10622
10623                 init : function(s) {
10624                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
10625
10626                         function execCallback(se, n, s) {
10627                                 var f = se[n];
10628
10629                                 if (!f)
10630                                         return;
10631
10632                                 if (tinymce.is(f, 'string')) {
10633                                         s = f.replace(/\.\w+$/, '');
10634                                         s = s ? tinymce.resolve(s) : 0;
10635                                         f = tinymce.resolve(f);
10636                                 }
10637
10638                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
10639                         };
10640
10641                         s = extend({
10642                                 theme : "simple",
10643                                 language : "en"
10644                         }, s);
10645
10646                         t.settings = s;
10647
10648                         // Legacy call
10649                         Event.add(document, 'init', function() {
10650                                 var l, co;
10651
10652                                 execCallback(s, 'onpageload');
10653
10654                                 switch (s.mode) {
10655                                         case "exact":
10656                                                 l = s.elements || '';
10657
10658                                                 if(l.length > 0) {
10659                                                         each(explode(l), function(v) {
10660                                                                 if (DOM.get(v)) {
10661                                                                         ed = new tinymce.Editor(v, s);
10662                                                                         el.push(ed);
10663                                                                         ed.render(1);
10664                                                                 } else {
10665                                                                         each(document.forms, function(f) {
10666                                                                                 each(f.elements, function(e) {
10667                                                                                         if (e.name === v) {
10668                                                                                                 v = 'mce_editor_' + instanceCounter++;
10669                                                                                                 DOM.setAttrib(e, 'id', v);
10670
10671                                                                                                 ed = new tinymce.Editor(v, s);
10672                                                                                                 el.push(ed);
10673                                                                                                 ed.render(1);
10674                                                                                         }
10675                                                                                 });
10676                                                                         });
10677                                                                 }
10678                                                         });
10679                                                 }
10680                                                 break;
10681
10682                                         case "textareas":
10683                                         case "specific_textareas":
10684                                                 function hasClass(n, c) {
10685                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
10686                                                 };
10687
10688                                                 each(DOM.select('textarea'), function(v) {
10689                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))
10690                                                                 return;
10691
10692                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {
10693                                                                 // Can we use the name
10694                                                                 e = DOM.get(v.name);
10695                                                                 if (!v.id && !e)
10696                                                                         v.id = v.name;
10697
10698                                                                 // Generate unique name if missing or already exists
10699                                                                 if (!v.id || t.get(v.id))
10700                                                                         v.id = DOM.uniqueId();
10701
10702                                                                 ed = new tinymce.Editor(v.id, s);
10703                                                                 el.push(ed);
10704                                                                 ed.render(1);
10705                                                         }
10706                                                 });
10707                                                 break;
10708                                 }
10709
10710                                 // Call onInit when all editors are initialized
10711                                 if (s.oninit) {
10712                                         l = co = 0;
10713
10714                                         each(el, function(ed) {
10715                                                 co++;
10716
10717                                                 if (!ed.initialized) {
10718                                                         // Wait for it
10719                                                         ed.onInit.add(function() {
10720                                                                 l++;
10721
10722                                                                 // All done
10723                                                                 if (l == co)
10724                                                                         execCallback(s, 'oninit');
10725                                                         });
10726                                                 } else
10727                                                         l++;
10728
10729                                                 // All done
10730                                                 if (l == co)
10731                                                         execCallback(s, 'oninit');                                      
10732                                         });
10733                                 }
10734                         });
10735                 },
10736
10737                 get : function(id) {
10738                         if (id === undefined)
10739                                 return this.editors;
10740
10741                         return this.editors[id];
10742                 },
10743
10744                 getInstanceById : function(id) {
10745                         return this.get(id);
10746                 },
10747
10748                 add : function(editor) {
10749                         var self = this, editors = self.editors;
10750
10751                         // Add named and index editor instance
10752                         editors[editor.id] = editor;
10753                         editors.push(editor);
10754
10755                         self._setActive(editor);
10756                         self.onAddEditor.dispatch(self, editor);
10757
10758
10759                         return editor;
10760                 },
10761
10762                 remove : function(editor) {
10763                         var t = this, i, editors = t.editors;
10764
10765                         // Not in the collection
10766                         if (!editors[editor.id])
10767                                 return null;
10768
10769                         delete editors[editor.id];
10770
10771                         for (i = 0; i < editors.length; i++) {
10772                                 if (editors[i] == editor) {
10773                                         editors.splice(i, 1);
10774                                         break;
10775                                 }
10776                         }
10777
10778                         // Select another editor since the active one was removed
10779                         if (t.activeEditor == editor)
10780                                 t._setActive(editors[0]);
10781
10782                         editor.destroy();
10783                         t.onRemoveEditor.dispatch(t, editor);
10784
10785                         return editor;
10786                 },
10787
10788                 execCommand : function(c, u, v) {
10789                         var t = this, ed = t.get(v), w;
10790
10791                         // Manager commands
10792                         switch (c) {
10793                                 case "mceFocus":
10794                                         ed.focus();
10795                                         return true;
10796
10797                                 case "mceAddEditor":
10798                                 case "mceAddControl":
10799                                         if (!t.get(v))
10800                                                 new tinymce.Editor(v, t.settings).render();
10801
10802                                         return true;
10803
10804                                 case "mceAddFrameControl":
10805                                         w = v.window;
10806
10807                                         // Add tinyMCE global instance and tinymce namespace to specified window
10808                                         w.tinyMCE = tinyMCE;
10809                                         w.tinymce = tinymce;
10810
10811                                         tinymce.DOM.doc = w.document;
10812                                         tinymce.DOM.win = w;
10813
10814                                         ed = new tinymce.Editor(v.element_id, v);
10815                                         ed.render();
10816
10817                                         // Fix IE memory leaks
10818                                         if (tinymce.isIE) {
10819                                                 function clr() {
10820                                                         ed.destroy();
10821                                                         w.detachEvent('onunload', clr);
10822                                                         w = w.tinyMCE = w.tinymce = null; // IE leak
10823                                                 };
10824
10825                                                 w.attachEvent('onunload', clr);
10826                                         }
10827
10828                                         v.page_window = null;
10829
10830                                         return true;
10831
10832                                 case "mceRemoveEditor":
10833                                 case "mceRemoveControl":
10834                                         if (ed)
10835                                                 ed.remove();
10836
10837                                         return true;
10838
10839                                 case 'mceToggleEditor':
10840                                         if (!ed) {
10841                                                 t.execCommand('mceAddControl', 0, v);
10842                                                 return true;
10843                                         }
10844
10845                                         if (ed.isHidden())
10846                                                 ed.show();
10847                                         else
10848                                                 ed.hide();
10849
10850                                         return true;
10851                         }
10852
10853                         // Run command on active editor
10854                         if (t.activeEditor)
10855                                 return t.activeEditor.execCommand(c, u, v);
10856
10857                         return false;
10858                 },
10859
10860                 execInstanceCommand : function(id, c, u, v) {
10861                         var ed = this.get(id);
10862
10863                         if (ed)
10864                                 return ed.execCommand(c, u, v);
10865
10866                         return false;
10867                 },
10868
10869                 triggerSave : function() {
10870                         each(this.editors, function(e) {
10871                                 e.save();
10872                         });
10873                 },
10874
10875                 addI18n : function(p, o) {
10876                         var lo, i18n = this.i18n;
10877
10878                         if (!tinymce.is(p, 'string')) {
10879                                 each(p, function(o, lc) {
10880                                         each(o, function(o, g) {
10881                                                 each(o, function(o, k) {
10882                                                         if (g === 'common')
10883                                                                 i18n[lc + '.' + k] = o;
10884                                                         else
10885                                                                 i18n[lc + '.' + g + '.' + k] = o;
10886                                                 });
10887                                         });
10888                                 });
10889                         } else {
10890                                 each(o, function(o, k) {
10891                                         i18n[p + '.' + k] = o;
10892                                 });
10893                         }
10894                 },
10895
10896                 // Private methods
10897
10898                 _setActive : function(editor) {
10899                         this.selectedInstance = this.activeEditor = editor;
10900                 }
10901         });
10902 })(tinymce);
10903
10904 (function(tinymce) {
10905         // Shorten these names
10906         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
10907                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10908                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10909                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10910                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
10911
10912         tinymce.create('tinymce.Editor', {
10913                 Editor : function(id, s) {
10914                         var t = this;
10915
10916                         t.id = t.editorId = id;
10917
10918                         t.execCommands = {};
10919                         t.queryStateCommands = {};
10920                         t.queryValueCommands = {};
10921
10922                         t.isNotDirty = false;
10923
10924                         t.plugins = {};
10925
10926                         // Add events to the editor
10927                         each([
10928                                 'onPreInit',
10929
10930                                 'onBeforeRenderUI',
10931
10932                                 'onPostRender',
10933
10934                                 'onInit',
10935
10936                                 'onRemove',
10937
10938                                 'onActivate',
10939
10940                                 'onDeactivate',
10941
10942                                 'onClick',
10943
10944                                 'onEvent',
10945
10946                                 'onMouseUp',
10947
10948                                 'onMouseDown',
10949
10950                                 'onDblClick',
10951
10952                                 'onKeyDown',
10953
10954                                 'onKeyUp',
10955
10956                                 'onKeyPress',
10957
10958                                 'onContextMenu',
10959
10960                                 'onSubmit',
10961
10962                                 'onReset',
10963
10964                                 'onPaste',
10965
10966                                 'onPreProcess',
10967
10968                                 'onPostProcess',
10969
10970                                 'onBeforeSetContent',
10971
10972                                 'onBeforeGetContent',
10973
10974                                 'onSetContent',
10975
10976                                 'onGetContent',
10977
10978                                 'onLoadContent',
10979
10980                                 'onSaveContent',
10981
10982                                 'onNodeChange',
10983
10984                                 'onChange',
10985
10986                                 'onBeforeExecCommand',
10987
10988                                 'onExecCommand',
10989
10990                                 'onUndo',
10991
10992                                 'onRedo',
10993
10994                                 'onVisualAid',
10995
10996                                 'onSetProgressState'
10997                         ], function(e) {
10998                                 t[e] = new Dispatcher(t);
10999                         });
11000
11001                         t.settings = s = extend({
11002                                 id : id,
11003                                 language : 'en',
11004                                 docs_language : 'en',
11005                                 theme : 'simple',
11006                                 skin : 'default',
11007                                 delta_width : 0,
11008                                 delta_height : 0,
11009                                 popup_css : '',
11010                                 plugins : '',
11011                                 document_base_url : tinymce.documentBaseURL,
11012                                 add_form_submit_trigger : 1,
11013                                 submit_patch : 1,
11014                                 add_unload_trigger : 1,
11015                                 convert_urls : 1,
11016                                 relative_urls : 1,
11017                                 remove_script_host : 1,
11018                                 table_inline_editing : 0,
11019                                 object_resizing : 1,
11020                                 cleanup : 1,
11021                                 accessibility_focus : 1,
11022                                 custom_shortcuts : 1,
11023                                 custom_undo_redo_keyboard_shortcuts : 1,
11024                                 custom_undo_redo_restore_selection : 1,
11025                                 custom_undo_redo : 1,
11026                                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
11027                                 visual_table_class : 'mceItemTable',
11028                                 visual : 1,
11029                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
11030                                 apply_source_formatting : 1,
11031                                 directionality : 'ltr',
11032                                 forced_root_block : 'p',
11033                                 hidden_input : 1,
11034                                 padd_empty_editor : 1,
11035                                 render_ui : 1,
11036                                 init_theme : 1,
11037                                 force_p_newlines : 1,
11038                                 indentation : '30px',
11039                                 keep_styles : 1,
11040                                 fix_table_elements : 1,
11041                                 inline_styles : 1,
11042                                 convert_fonts_to_spans : true,
11043                                 indent : 'simple',
11044                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
11045                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
11046                                 validate : true,
11047                                 entity_encoding : 'named',
11048                                 url_converter : t.convertURL,
11049                                 url_converter_scope : t,
11050                                 ie7_compat : true
11051                         }, s);
11052
11053                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
11054                                 base_uri : tinyMCE.baseURI
11055                         });
11056
11057                         t.baseURI = tinymce.baseURI;
11058
11059                         t.contentCSS = [];
11060
11061                         // Call setup
11062                         t.execCallback('setup', t);
11063                 },
11064
11065                 render : function(nst) {
11066                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
11067
11068                         // Page is not loaded yet, wait for it
11069                         if (!Event.domLoaded) {
11070                                 Event.add(document, 'init', function() {
11071                                         t.render();
11072                                 });
11073                                 return;
11074                         }
11075
11076                         tinyMCE.settings = s;
11077
11078                         // Element not found, then skip initialization
11079                         if (!t.getElement())
11080                                 return;
11081
11082                         // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
11083                         // here since the browser says it has contentEditable support but there is no visible
11084                         // caret We will remove this check ones Apple implements full contentEditable support
11085                         if (tinymce.isIDevice && !tinymce.isIOS5)
11086                                 return;
11087
11088                         // Add hidden input for non input elements inside form elements
11089                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
11090                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
11091
11092                         if (tinymce.WindowManager)
11093                                 t.windowManager = new tinymce.WindowManager(t);
11094
11095                         if (s.encoding == 'xml') {
11096                                 t.onGetContent.add(function(ed, o) {
11097                                         if (o.save)
11098                                                 o.content = DOM.encode(o.content);
11099                                 });
11100                         }
11101
11102                         if (s.add_form_submit_trigger) {
11103                                 t.onSubmit.addToTop(function() {
11104                                         if (t.initialized) {
11105                                                 t.save();
11106                                                 t.isNotDirty = 1;
11107                                         }
11108                                 });
11109                         }
11110
11111                         if (s.add_unload_trigger) {
11112                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
11113                                         if (t.initialized && !t.destroyed && !t.isHidden())
11114                                                 t.save({format : 'raw', no_events : true});
11115                                 });
11116                         }
11117
11118                         tinymce.addUnload(t.destroy, t);
11119
11120                         if (s.submit_patch) {
11121                                 t.onBeforeRenderUI.add(function() {
11122                                         var n = t.getElement().form;
11123
11124                                         if (!n)
11125                                                 return;
11126
11127                                         // Already patched
11128                                         if (n._mceOldSubmit)
11129                                                 return;
11130
11131                                         // Check page uses id="submit" or name="submit" for it's submit button
11132                                         if (!n.submit.nodeType && !n.submit.length) {
11133                                                 t.formElement = n;
11134                                                 n._mceOldSubmit = n.submit;
11135                                                 n.submit = function() {
11136                                                         // Save all instances
11137                                                         tinymce.triggerSave();
11138                                                         t.isNotDirty = 1;
11139
11140                                                         return t.formElement._mceOldSubmit(t.formElement);
11141                                                 };
11142                                         }
11143
11144                                         n = null;
11145                                 });
11146                         }
11147
11148                         // Load scripts
11149                         function loadScripts() {
11150                                 if (s.language && s.language_load !== false)
11151                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
11152
11153                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
11154                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
11155
11156                                 each(explode(s.plugins), function(p) {
11157                                         if (p &&!PluginManager.urls[p]) {
11158                                                 if (p.charAt(0) == '-') {
11159                                                         p = p.substr(1, p.length);
11160                                                         var dependencies = PluginManager.dependencies(p);
11161                                                         each(dependencies, function(dep) {
11162                                                                 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
11163                                                                 var dep = PluginManager.createUrl(defaultSettings, dep);
11164                                                                 PluginManager.load(dep.resource, dep);
11165                                                                 
11166                                                         });
11167                                                 } else {
11168                                                         // Skip safari plugin, since it is removed as of 3.3b1
11169                                                         if (p == 'safari') {
11170                                                                 return;
11171                                                         }
11172                                                         PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
11173                                                 }
11174                                         }
11175                                 });
11176
11177                                 // Init when que is loaded
11178                                 sl.loadQueue(function() {
11179                                         if (!t.removed)
11180                                                 t.init();
11181                                 });
11182                         };
11183
11184                         loadScripts();
11185                 },
11186
11187                 init : function() {
11188                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
11189
11190                         tinymce.add(t);
11191
11192                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
11193
11194                         if (s.theme) {
11195                                 s.theme = s.theme.replace(/-/, '');
11196                                 o = ThemeManager.get(s.theme);
11197                                 t.theme = new o();
11198
11199                                 if (t.theme.init && s.init_theme)
11200                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
11201                         }
11202                         function initPlugin(p) {
11203                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
11204                                 if (c && tinymce.inArray(initializedPlugins,p) === -1) {
11205                                         each(PluginManager.dependencies(p), function(dep){
11206                                                 initPlugin(dep);
11207                                         });
11208                                         po = new c(t, u);
11209
11210                                         t.plugins[p] = po;
11211
11212                                         if (po.init) {
11213                                                 po.init(t, u);
11214                                                 initializedPlugins.push(p);
11215                                         }
11216                                 }
11217                         }
11218                         
11219                         // Create all plugins
11220                         each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
11221
11222                         // Setup popup CSS path(s)
11223                         if (s.popup_css !== false) {
11224                                 if (s.popup_css)
11225                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
11226                                 else
11227                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
11228                         }
11229
11230                         if (s.popup_css_add)
11231                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
11232
11233                         t.controlManager = new tinymce.ControlManager(t);
11234
11235                         if (s.custom_undo_redo) {
11236                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
11237                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
11238                                                 t.undoManager.beforeChange();
11239                                 });
11240
11241                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
11242                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
11243                                                 t.undoManager.add();
11244                                 });
11245                         }
11246
11247                         t.onExecCommand.add(function(ed, c) {
11248                                 // Don't refresh the select lists until caret move
11249                                 if (!/^(FontName|FontSize)$/.test(c))
11250                                         t.nodeChanged();
11251                         });
11252
11253                         // Remove ghost selections on images and tables in Gecko
11254                         if (isGecko) {
11255                                 function repaint(a, o) {
11256                                         if (!o || !o.initial)
11257                                                 t.execCommand('mceRepaint');
11258                                 };
11259
11260                                 t.onUndo.add(repaint);
11261                                 t.onRedo.add(repaint);
11262                                 t.onSetContent.add(repaint);
11263                         }
11264
11265                         // Enables users to override the control factory
11266                         t.onBeforeRenderUI.dispatch(t, t.controlManager);
11267
11268                         // Measure box
11269                         if (s.render_ui) {
11270                                 w = s.width || e.style.width || e.offsetWidth;
11271                                 h = s.height || e.style.height || e.offsetHeight;
11272                                 t.orgDisplay = e.style.display;
11273                                 re = /^[0-9\.]+(|px)$/i;
11274
11275                                 if (re.test('' + w))
11276                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
11277
11278                                 if (re.test('' + h))
11279                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
11280
11281                                 // Render UI
11282                                 o = t.theme.renderUI({
11283                                         targetNode : e,
11284                                         width : w,
11285                                         height : h,
11286                                         deltaWidth : s.delta_width,
11287                                         deltaHeight : s.delta_height
11288                                 });
11289
11290                                 t.editorContainer = o.editorContainer;
11291                         }
11292
11293
11294                         // User specified a document.domain value
11295                         if (document.domain && location.hostname != document.domain)
11296                                 tinymce.relaxedDomain = document.domain;
11297
11298                         // Resize editor
11299                         DOM.setStyles(o.sizeContainer || o.editorContainer, {
11300                                 width : w,
11301                                 height : h
11302                         });
11303
11304                         // Load specified content CSS last
11305                         if (s.content_css) {
11306                                 tinymce.each(explode(s.content_css), function(u) {
11307                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
11308                                 });
11309                         }
11310
11311                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
11312                         if (h < 100)
11313                                 h = 100;
11314
11315                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
11316
11317                         // We only need to override paths if we have to
11318                         // IE has a bug where it remove site absolute urls to relative ones if this is specified
11319                         if (s.document_base_url != tinymce.documentBaseURL)
11320                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
11321
11322                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
11323                         if (s.ie7_compat)
11324                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
11325                         else
11326                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
11327
11328                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
11329
11330                         // Firefox 2 doesn't load stylesheets correctly this way
11331                         if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
11332                                 for (i = 0; i < t.contentCSS.length; i++)
11333                                         t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
11334
11335                                 t.contentCSS = [];
11336                         }
11337
11338                         bi = s.body_id || 'tinymce';
11339                         if (bi.indexOf('=') != -1) {
11340                                 bi = t.getParam('body_id', '', 'hash');
11341                                 bi = bi[t.id] || bi;
11342                         }
11343
11344                         bc = s.body_class || '';
11345                         if (bc.indexOf('=') != -1) {
11346                                 bc = t.getParam('body_class', '', 'hash');
11347                                 bc = bc[t.id] || '';
11348                         }
11349
11350                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
11351
11352                         // Domain relaxing enabled, then set document domain
11353                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
11354                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
11355                                 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();})()';                         
11356                         }
11357
11358                         // Create iframe
11359                         // TODO: ACC add the appropriate description on this.
11360                         n = DOM.add(o.iframeContainer, 'iframe', { 
11361                                 id : t.id + "_ifr",
11362                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
11363                                 frameBorder : '0',
11364                                 allowTransparency : "true",
11365                                 title : s.aria_label,
11366                                 style : {
11367                                         width : '100%',
11368                                         height : h
11369                                 }
11370                         });
11371
11372                         t.contentAreaContainer = o.iframeContainer;
11373                         DOM.get(o.editorContainer).style.display = t.orgDisplay;
11374                         DOM.get(t.id).style.display = 'none';
11375                         DOM.setAttrib(t.id, 'aria-hidden', true);
11376
11377                         if (!tinymce.relaxedDomain || !u)
11378                                 t.setupIframe();
11379
11380                         e = n = o = null; // Cleanup
11381                 },
11382
11383                 setupIframe : function(filled) {
11384                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
11385
11386                         // Setup iframe body
11387                         if ((!isIE || !tinymce.relaxedDomain) && !filled) {
11388                                 // We need to wait for the load event on Gecko
11389                                 if (isGecko && !s.readonly) {
11390                                         t.getWin().addEventListener("DOMContentLoaded", function() {
11391                                                 window.setTimeout(function() {
11392                                                         var b = t.getBody(), undef;
11393
11394                                                         // Editable element needs to have some contents or backspace/delete won't work properly for some odd reason on FF 3.6 or older
11395                                                         b.innerHTML = '<br>';
11396
11397                                                         // Check if Gecko supports contentEditable mode FF2 doesn't
11398                                                         if (b.contentEditable !== undef) {
11399                                                                 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
11400                                                                 b.contentEditable = false;
11401                                                                 b.contentEditable = true;
11402
11403                                                                 // Caret doesn't get rendered when you mousedown on the HTML element on FF 3.x
11404                                                                 t.onMouseDown.add(function(ed, e) {
11405                                                                         if (e.target.nodeName === "HTML") {
11406                                                                                 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
11407                                                                                 b.contentEditable = false;
11408                                                                                 b.contentEditable = true;
11409
11410                                                                                 d.designMode = 'on'; // Render the caret
11411
11412                                                                                 // Remove design mode again after a while so it has some time to execute
11413                                                                                 window.setTimeout(function() {
11414                                                                                         d.designMode = 'off';
11415                                                                                         t.getBody().focus();
11416                                                                                 }, 1);
11417                                                                         }
11418                                                                 });
11419                                                         } else
11420                                                                 d.designMode = 'on';
11421
11422                                                         // Call setup frame once the contentEditable/designMode has been initialized
11423                                                         // since the caret won't be rendered some times otherwise.
11424                                                         t.setupIframe(true);
11425                                                 }, 1);
11426                                         }, false);
11427                                 }
11428
11429                                 d.open();
11430                                 d.write(t.iframeHTML);
11431                                 d.close();
11432
11433                                 if (tinymce.relaxedDomain)
11434                                         d.domain = tinymce.relaxedDomain;
11435
11436                                 // Wait for iframe onload event on Gecko
11437                                 if (isGecko && !s.readonly)
11438                                         return;
11439                         }
11440
11441                         // It will not steal focus while setting contentEditable
11442                         b = t.getBody();
11443                         b.disabled = true;
11444
11445                         if (!isGecko && !s.readonly)
11446                                 b.contentEditable = true;
11447
11448                         b.disabled = false;
11449
11450                         t.schema = new tinymce.html.Schema(s);
11451
11452                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
11453                                 keep_values : true,
11454                                 url_converter : t.convertURL,
11455                                 url_converter_scope : t,
11456                                 hex_colors : s.force_hex_style_colors,
11457                                 class_filter : s.class_filter,
11458                                 update_styles : 1,
11459                                 fix_ie_paragraphs : 1,
11460                                 schema : t.schema
11461                         });
11462
11463                         t.parser = new tinymce.html.DomParser(s, t.schema);
11464
11465                         // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
11466                         if (!t.settings.allow_html_in_named_anchor) {
11467                                 t.parser.addAttributeFilter('name', function(nodes, name) {
11468                                         var i = nodes.length, sibling, prevSibling, parent, node;
11469         
11470                                         while (i--) {
11471                                                 node = nodes[i];
11472                                                 if (node.name === 'a' && node.firstChild) {
11473                                                         parent = node.parent;
11474         
11475                                                         // Move children after current node
11476                                                         sibling = node.lastChild;
11477                                                         do {
11478                                                                 prevSibling = sibling.prev;
11479                                                                 parent.insert(sibling, node);
11480                                                                 sibling = prevSibling;
11481                                                         } while (sibling);
11482                                                 }
11483                                         }
11484                                 });
11485                         }
11486
11487                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style
11488                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
11489                                 var i = nodes.length, node, dom = t.dom, value, internalName;
11490
11491                                 while (i--) {
11492                                         node = nodes[i];
11493                                         value = node.attr(name);
11494                                         internalName = 'data-mce-' + name;
11495
11496                                         // Add internal attribute if we need to we don't on a refresh of the document
11497                                         if (!node.attributes.map[internalName]) {       
11498                                                 if (name === "style")
11499                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
11500                                                 else
11501                                                         node.attr(internalName, t.convertURL(value, name, node.name));
11502                                         }
11503                                 }
11504                         });
11505
11506                         // Keep scripts from executing
11507                         t.parser.addNodeFilter('script', function(nodes, name) {
11508                                 var i = nodes.length;
11509
11510                                 while (i--)
11511                                         nodes[i].attr('type', 'mce-text/javascript');
11512                         });
11513
11514                         t.parser.addNodeFilter('#cdata', function(nodes, name) {
11515                                 var i = nodes.length, node;
11516
11517                                 while (i--) {
11518                                         node = nodes[i];
11519                                         node.type = 8;
11520                                         node.name = '#comment';
11521                                         node.value = '[CDATA[' + node.value + ']]';
11522                                 }
11523                         });
11524
11525                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
11526                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
11527
11528                                 while (i--) {
11529                                         node = nodes[i];
11530
11531                                         if (node.isEmpty(nonEmptyElements))
11532                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
11533                                 }
11534                         });
11535
11536                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
11537
11538                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
11539
11540                         t.formatter = new tinymce.Formatter(this);
11541
11542                         // Register default formats
11543                         t.formatter.register({
11544                                 alignleft : [
11545                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
11546                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
11547                                 ],
11548
11549                                 aligncenter : [
11550                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
11551                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
11552                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
11553                                 ],
11554
11555                                 alignright : [
11556                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
11557                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
11558                                 ],
11559
11560                                 alignfull : [
11561                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
11562                                 ],
11563
11564                                 bold : [
11565                                         {inline : 'strong', remove : 'all'},
11566                                         {inline : 'span', styles : {fontWeight : 'bold'}},
11567                                         {inline : 'b', remove : 'all'}
11568                                 ],
11569
11570                                 italic : [
11571                                         {inline : 'em', remove : 'all'},
11572                                         {inline : 'span', styles : {fontStyle : 'italic'}},
11573                                         {inline : 'i', remove : 'all'}
11574                                 ],
11575
11576                                 underline : [
11577                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
11578                                         {inline : 'u', remove : 'all'}
11579                                 ],
11580
11581                                 strikethrough : [
11582                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
11583                                         {inline : 'strike', remove : 'all'}
11584                                 ],
11585
11586                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
11587                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
11588                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
11589                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
11590                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
11591                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
11592                                 subscript : {inline : 'sub'},
11593                                 superscript : {inline : 'sup'},
11594
11595                                 removeformat : [
11596                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
11597                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
11598                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
11599                                 ]
11600                         });
11601
11602                         // Register default block formats
11603                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
11604                                 t.formatter.register(name, {block : name, remove : 'all'});
11605                         });
11606
11607                         // Register user defined formats
11608                         t.formatter.register(t.settings.formats);
11609
11610                         t.undoManager = new tinymce.UndoManager(t);
11611
11612                         // Pass through
11613                         t.undoManager.onAdd.add(function(um, l) {
11614                                 if (um.hasUndo())
11615                                         return t.onChange.dispatch(t, l, um);
11616                         });
11617
11618                         t.undoManager.onUndo.add(function(um, l) {
11619                                 return t.onUndo.dispatch(t, l, um);
11620                         });
11621
11622                         t.undoManager.onRedo.add(function(um, l) {
11623                                 return t.onRedo.dispatch(t, l, um);
11624                         });
11625
11626                         t.forceBlocks = new tinymce.ForceBlocks(t, {
11627                                 forced_root_block : s.forced_root_block
11628                         });
11629
11630                         t.editorCommands = new tinymce.EditorCommands(t);
11631
11632                         // Pass through
11633                         t.serializer.onPreProcess.add(function(se, o) {
11634                                 return t.onPreProcess.dispatch(t, o, se);
11635                         });
11636
11637                         t.serializer.onPostProcess.add(function(se, o) {
11638                                 return t.onPostProcess.dispatch(t, o, se);
11639                         });
11640
11641                         t.onPreInit.dispatch(t);
11642
11643                         if (!s.gecko_spellcheck)
11644                                 t.getBody().spellcheck = 0;
11645
11646                         if (!s.readonly)
11647                                 t._addEvents();
11648
11649                         t.controlManager.onPostRender.dispatch(t, t.controlManager);
11650                         t.onPostRender.dispatch(t);
11651
11652                         t.quirks = new tinymce.util.Quirks(this);
11653
11654                         if (s.directionality)
11655                                 t.getBody().dir = s.directionality;
11656
11657                         if (s.nowrap)
11658                                 t.getBody().style.whiteSpace = "nowrap";
11659
11660                         if (s.handle_node_change_callback) {
11661                                 t.onNodeChange.add(function(ed, cm, n) {
11662                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
11663                                 });
11664                         }
11665
11666                         if (s.save_callback) {
11667                                 t.onSaveContent.add(function(ed, o) {
11668                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
11669
11670                                         if (h)
11671                                                 o.content = h;
11672                                 });
11673                         }
11674
11675                         if (s.onchange_callback) {
11676                                 t.onChange.add(function(ed, l) {
11677                                         t.execCallback('onchange_callback', t, l);
11678                                 });
11679                         }
11680
11681                         if (s.protect) {
11682                                 t.onBeforeSetContent.add(function(ed, o) {
11683                                         if (s.protect) {
11684                                                 each(s.protect, function(pattern) {
11685                                                         o.content = o.content.replace(pattern, function(str) {
11686                                                                 return '<!--mce:protected ' + escape(str) + '-->';
11687                                                         });
11688                                                 });
11689                                         }
11690                                 });
11691                         }
11692
11693                         if (s.convert_newlines_to_brs) {
11694                                 t.onBeforeSetContent.add(function(ed, o) {
11695                                         if (o.initial)
11696                                                 o.content = o.content.replace(/\r?\n/g, '<br />');
11697                                 });
11698                         }
11699
11700                         if (s.preformatted) {
11701                                 t.onPostProcess.add(function(ed, o) {
11702                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');
11703                                         o.content = o.content.replace(/<\/pre>\s*$/, '');
11704
11705                                         if (o.set)
11706                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
11707                                 });
11708                         }
11709
11710                         if (s.verify_css_classes) {
11711                                 t.serializer.attribValueFilter = function(n, v) {
11712                                         var s, cl;
11713
11714                                         if (n == 'class') {
11715                                                 // Build regexp for classes
11716                                                 if (!t.classesRE) {
11717                                                         cl = t.dom.getClasses();
11718
11719                                                         if (cl.length > 0) {
11720                                                                 s = '';
11721
11722                                                                 each (cl, function(o) {
11723                                                                         s += (s ? '|' : '') + o['class'];
11724                                                                 });
11725
11726                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');
11727                                                         }
11728                                                 }
11729
11730                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
11731                                         }
11732
11733                                         return v;
11734                                 };
11735                         }
11736
11737                         if (s.cleanup_callback) {
11738                                 t.onBeforeSetContent.add(function(ed, o) {
11739                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11740                                 });
11741
11742                                 t.onPreProcess.add(function(ed, o) {
11743                                         if (o.set)
11744                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
11745
11746                                         if (o.get)
11747                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
11748                                 });
11749
11750                                 t.onPostProcess.add(function(ed, o) {
11751                                         if (o.set)
11752                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11753
11754                                         if (o.get)                                              
11755                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
11756                                 });
11757                         }
11758
11759                         if (s.save_callback) {
11760                                 t.onGetContent.add(function(ed, o) {
11761                                         if (o.save)
11762                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
11763                                 });
11764                         }
11765
11766                         if (s.handle_event_callback) {
11767                                 t.onEvent.add(function(ed, e, o) {
11768                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)
11769                                                 Event.cancel(e);
11770                                 });
11771                         }
11772
11773                         // Add visual aids when new contents is added
11774                         t.onSetContent.add(function() {
11775                                 t.addVisual(t.getBody());
11776                         });
11777
11778                         // Remove empty contents
11779                         if (s.padd_empty_editor) {
11780                                 t.onPostProcess.add(function(ed, o) {
11781                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
11782                                 });
11783                         }
11784
11785                         if (isGecko) {
11786                                 // Fix gecko link bug, when a link is placed at the end of block elements there is
11787                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link
11788                                 function fixLinks(ed, o) {
11789                                         each(ed.dom.select('a'), function(n) {
11790                                                 var pn = n.parentNode;
11791
11792                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)
11793                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
11794                                         });
11795                                 };
11796
11797                                 t.onExecCommand.add(function(ed, cmd) {
11798                                         if (cmd === 'CreateLink')
11799                                                 fixLinks(ed);
11800                                 });
11801
11802                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
11803                         }
11804
11805                         t.load({initial : true, format : 'html'});
11806                         t.startContent = t.getContent({format : 'raw'});
11807                         t.undoManager.add();
11808                         t.initialized = true;
11809
11810                         t.onInit.dispatch(t);
11811                         t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
11812                         t.execCallback('init_instance_callback', t);
11813                         t.focus(true);
11814                         t.nodeChanged({initial : 1});
11815
11816                         // Load specified content CSS last
11817                         each(t.contentCSS, function(u) {
11818                                 t.dom.loadCSS(u);
11819                         });
11820
11821                         // Handle auto focus
11822                         if (s.auto_focus) {
11823                                 setTimeout(function () {
11824                                         var ed = tinymce.get(s.auto_focus);
11825
11826                                         ed.selection.select(ed.getBody(), 1);
11827                                         ed.selection.collapse(1);
11828                                         ed.getBody().focus();
11829                                         ed.getWin().focus();
11830                                 }, 100);
11831                         }
11832
11833                         e = null;
11834                 },
11835
11836
11837                 focus : function(sf) {
11838                         var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
11839
11840                         if (!sf) {
11841                                 // Get selected control element
11842                                 ieRng = selection.getRng();
11843                                 if (ieRng.item) {
11844                                         controlElm = ieRng.item(0);
11845                                 }
11846
11847                                 selection.normalize();
11848
11849                                 // Is not content editable
11850                                 if (!ce)
11851                                         t.getWin().focus();
11852
11853                                 // Focus the body as well since it's contentEditable
11854                                 if (tinymce.isGecko) {
11855                                         t.getBody().focus();
11856                                 }
11857
11858                                 // Restore selected control element
11859                                 // This is needed when for example an image is selected within a
11860                                 // layer a call to focus will then remove the control selection
11861                                 if (controlElm && controlElm.ownerDocument == doc) {
11862                                         ieRng = doc.body.createControlRange();
11863                                         ieRng.addElement(controlElm);
11864                                         ieRng.select();
11865                                 }
11866
11867                         }
11868
11869                         if (tinymce.activeEditor != t) {
11870                                 if ((oed = tinymce.activeEditor) != null)
11871                                         oed.onDeactivate.dispatch(oed, t);
11872
11873                                 t.onActivate.dispatch(t, oed);
11874                         }
11875
11876                         tinymce._setActive(t);
11877                 },
11878
11879                 execCallback : function(n) {
11880                         var t = this, f = t.settings[n], s;
11881
11882                         if (!f)
11883                                 return;
11884
11885                         // Look through lookup
11886                         if (t.callbackLookup && (s = t.callbackLookup[n])) {
11887                                 f = s.func;
11888                                 s = s.scope;
11889                         }
11890
11891                         if (is(f, 'string')) {
11892                                 s = f.replace(/\.\w+$/, '');
11893                                 s = s ? tinymce.resolve(s) : 0;
11894                                 f = tinymce.resolve(f);
11895                                 t.callbackLookup = t.callbackLookup || {};
11896                                 t.callbackLookup[n] = {func : f, scope : s};
11897                         }
11898
11899                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
11900                 },
11901
11902                 translate : function(s) {
11903                         var c = this.settings.language || 'en', i18n = tinymce.i18n;
11904
11905                         if (!s)
11906                                 return '';
11907
11908                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
11909                                 return i18n[c + '.' + b] || '{#' + b + '}';
11910                         });
11911                 },
11912
11913                 getLang : function(n, dv) {
11914                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
11915                 },
11916
11917                 getParam : function(n, dv, ty) {
11918                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
11919
11920                         if (ty === 'hash') {
11921                                 o = {};
11922
11923                                 if (is(v, 'string')) {
11924                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
11925                                                 v = v.split('=');
11926
11927                                                 if (v.length > 1)
11928                                                         o[tr(v[0])] = tr(v[1]);
11929                                                 else
11930                                                         o[tr(v[0])] = tr(v);
11931                                         });
11932                                 } else
11933                                         o = v;
11934
11935                                 return o;
11936                         }
11937
11938                         return v;
11939                 },
11940
11941                 nodeChanged : function(o) {
11942                         var t = this, s = t.selection, n = s.getStart() || t.getBody();
11943
11944                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
11945                         if (t.initialized) {
11946                                 o = o || {};
11947                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
11948
11949                                 // Get parents and add them to object
11950                                 o.parents = [];
11951                                 t.dom.getParent(n, function(node) {
11952                                         if (node.nodeName == 'BODY')
11953                                                 return true;
11954
11955                                         o.parents.push(node);
11956                                 });
11957
11958                                 t.onNodeChange.dispatch(
11959                                         t,
11960                                         o ? o.controlManager || t.controlManager : t.controlManager,
11961                                         n,
11962                                         s.isCollapsed(),
11963                                         o
11964                                 );
11965                         }
11966                 },
11967
11968                 addButton : function(n, s) {
11969                         var t = this;
11970
11971                         t.buttons = t.buttons || {};
11972                         t.buttons[n] = s;
11973                 },
11974
11975                 addCommand : function(name, callback, scope) {
11976                         this.execCommands[name] = {func : callback, scope : scope || this};
11977                 },
11978
11979                 addQueryStateHandler : function(name, callback, scope) {
11980                         this.queryStateCommands[name] = {func : callback, scope : scope || this};
11981                 },
11982
11983                 addQueryValueHandler : function(name, callback, scope) {
11984                         this.queryValueCommands[name] = {func : callback, scope : scope || this};
11985                 },
11986
11987                 addShortcut : function(pa, desc, cmd_func, sc) {
11988                         var t = this, c;
11989
11990                         if (!t.settings.custom_shortcuts)
11991                                 return false;
11992
11993                         t.shortcuts = t.shortcuts || {};
11994
11995                         if (is(cmd_func, 'string')) {
11996                                 c = cmd_func;
11997
11998                                 cmd_func = function() {
11999                                         t.execCommand(c, false, null);
12000                                 };
12001                         }
12002
12003                         if (is(cmd_func, 'object')) {
12004                                 c = cmd_func;
12005
12006                                 cmd_func = function() {
12007                                         t.execCommand(c[0], c[1], c[2]);
12008                                 };
12009                         }
12010
12011                         each(explode(pa), function(pa) {
12012                                 var o = {
12013                                         func : cmd_func,
12014                                         scope : sc || this,
12015                                         desc : desc,
12016                                         alt : false,
12017                                         ctrl : false,
12018                                         shift : false
12019                                 };
12020
12021                                 each(explode(pa, '+'), function(v) {
12022                                         switch (v) {
12023                                                 case 'alt':
12024                                                 case 'ctrl':
12025                                                 case 'shift':
12026                                                         o[v] = true;
12027                                                         break;
12028
12029                                                 default:
12030                                                         o.charCode = v.charCodeAt(0);
12031                                                         o.keyCode = v.toUpperCase().charCodeAt(0);
12032                                         }
12033                                 });
12034
12035                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
12036                         });
12037
12038                         return true;
12039                 },
12040
12041                 execCommand : function(cmd, ui, val, a) {
12042                         var t = this, s = 0, o, st;
12043
12044                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
12045                                 t.focus();
12046
12047                         o = {};
12048                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
12049                         if (o.terminate)
12050                                 return false;
12051
12052                         // Command callback
12053                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
12054                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12055                                 return true;
12056                         }
12057
12058                         // Registred commands
12059                         if (o = t.execCommands[cmd]) {
12060                                 st = o.func.call(o.scope, ui, val);
12061
12062                                 // Fall through on true
12063                                 if (st !== true) {
12064                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
12065                                         return st;
12066                                 }
12067                         }
12068
12069                         // Plugin commands
12070                         each(t.plugins, function(p) {
12071                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {
12072                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
12073                                         s = 1;
12074                                         return false;
12075                                 }
12076                         });
12077
12078                         if (s)
12079                                 return true;
12080
12081                         // Theme commands
12082                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
12083                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12084                                 return true;
12085                         }
12086
12087                         // Editor commands
12088                         if (t.editorCommands.execCommand(cmd, ui, val)) {
12089                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12090                                 return true;
12091                         }
12092
12093                         // Browser commands
12094                         t.getDoc().execCommand(cmd, ui, val);
12095                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
12096                 },
12097
12098                 queryCommandState : function(cmd) {
12099                         var t = this, o, s;
12100
12101                         // Is hidden then return undefined
12102                         if (t._isHidden())
12103                                 return;
12104
12105                         // Registred commands
12106                         if (o = t.queryStateCommands[cmd]) {
12107                                 s = o.func.call(o.scope);
12108
12109                                 // Fall though on true
12110                                 if (s !== true)
12111                                         return s;
12112                         }
12113
12114                         // Registred commands
12115                         o = t.editorCommands.queryCommandState(cmd);
12116                         if (o !== -1)
12117                                 return o;
12118
12119                         // Browser commands
12120                         try {
12121                                 return this.getDoc().queryCommandState(cmd);
12122                         } catch (ex) {
12123                                 // Fails sometimes see bug: 1896577
12124                         }
12125                 },
12126
12127                 queryCommandValue : function(c) {
12128                         var t = this, o, s;
12129
12130                         // Is hidden then return undefined
12131                         if (t._isHidden())
12132                                 return;
12133
12134                         // Registred commands
12135                         if (o = t.queryValueCommands[c]) {
12136                                 s = o.func.call(o.scope);
12137
12138                                 // Fall though on true
12139                                 if (s !== true)
12140                                         return s;
12141                         }
12142
12143                         // Registred commands
12144                         o = t.editorCommands.queryCommandValue(c);
12145                         if (is(o))
12146                                 return o;
12147
12148                         // Browser commands
12149                         try {
12150                                 return this.getDoc().queryCommandValue(c);
12151                         } catch (ex) {
12152                                 // Fails sometimes see bug: 1896577
12153                         }
12154                 },
12155
12156                 show : function() {
12157                         var t = this;
12158
12159                         DOM.show(t.getContainer());
12160                         DOM.hide(t.id);
12161                         t.load();
12162                 },
12163
12164                 hide : function() {
12165                         var t = this, d = t.getDoc();
12166
12167                         // Fixed bug where IE has a blinking cursor left from the editor
12168                         if (isIE && d)
12169                                 d.execCommand('SelectAll');
12170
12171                         // We must save before we hide so Safari doesn't crash
12172                         t.save();
12173                         DOM.hide(t.getContainer());
12174                         DOM.setStyle(t.id, 'display', t.orgDisplay);
12175                 },
12176
12177                 isHidden : function() {
12178                         return !DOM.isHidden(this.id);
12179                 },
12180
12181                 setProgressState : function(b, ti, o) {
12182                         this.onSetProgressState.dispatch(this, b, ti, o);
12183
12184                         return b;
12185                 },
12186
12187                 load : function(o) {
12188                         var t = this, e = t.getElement(), h;
12189
12190                         if (e) {
12191                                 o = o || {};
12192                                 o.load = true;
12193
12194                                 // Double encode existing entities in the value
12195                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
12196                                 o.element = e;
12197
12198                                 if (!o.no_events)
12199                                         t.onLoadContent.dispatch(t, o);
12200
12201                                 o.element = e = null;
12202
12203                                 return h;
12204                         }
12205                 },
12206
12207                 save : function(o) {
12208                         var t = this, e = t.getElement(), h, f;
12209
12210                         if (!e || !t.initialized)
12211                                 return;
12212
12213                         o = o || {};
12214                         o.save = true;
12215
12216                         // Add undo level will trigger onchange event
12217                         if (!o.no_events) {
12218                                 t.undoManager.typing = false;
12219                                 t.undoManager.add();
12220                         }
12221
12222                         o.element = e;
12223                         h = o.content = t.getContent(o);
12224
12225                         if (!o.no_events)
12226                                 t.onSaveContent.dispatch(t, o);
12227
12228                         h = o.content;
12229
12230                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
12231                                 e.innerHTML = h;
12232
12233                                 // Update hidden form element
12234                                 if (f = DOM.getParent(t.id, 'form')) {
12235                                         each(f.elements, function(e) {
12236                                                 if (e.name == t.id) {
12237                                                         e.value = h;
12238                                                         return false;
12239                                                 }
12240                                         });
12241                                 }
12242                         } else
12243                                 e.value = h;
12244
12245                         o.element = e = null;
12246
12247                         return h;
12248                 },
12249
12250                 setContent : function(content, args) {
12251                         var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
12252
12253                         // Setup args object
12254                         args = args || {};
12255                         args.format = args.format || 'html';
12256                         args.set = true;
12257                         args.content = content;
12258
12259                         // Do preprocessing
12260                         if (!args.no_events)
12261                                 self.onBeforeSetContent.dispatch(self, args);
12262
12263                         content = args.content;
12264
12265                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
12266                         // It will also be impossible to place the caret in the editor unless there is a BR element present
12267                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
12268                                 forcedRootBlockName = self.settings.forced_root_block;
12269                                 if (forcedRootBlockName)
12270                                         content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
12271                                 else
12272                                         content = '<br data-mce-bogus="1">';
12273
12274                                 body.innerHTML = content;
12275                                 self.selection.select(body, true);
12276                                 self.selection.collapse(true);
12277                                 return;
12278                         }
12279
12280                         // Parse and serialize the html
12281                         if (args.format !== 'raw') {
12282                                 content = new tinymce.html.Serializer({}, self.schema).serialize(
12283                                         self.parser.parse(content)
12284                                 );
12285                         }
12286
12287                         // Set the new cleaned contents to the editor
12288                         args.content = tinymce.trim(content);
12289                         self.dom.setHTML(body, args.content);
12290
12291                         // Do post processing
12292                         if (!args.no_events)
12293                                 self.onSetContent.dispatch(self, args);
12294
12295                         self.selection.normalize();
12296
12297                         return args.content;
12298                 },
12299
12300                 getContent : function(args) {
12301                         var self = this, content;
12302
12303                         // Setup args object
12304                         args = args || {};
12305                         args.format = args.format || 'html';
12306                         args.get = true;
12307
12308                         // Do preprocessing
12309                         if (!args.no_events)
12310                                 self.onBeforeGetContent.dispatch(self, args);
12311
12312                         // Get raw contents or by default the cleaned contents
12313                         if (args.format == 'raw')
12314                                 content = self.getBody().innerHTML;
12315                         else
12316                                 content = self.serializer.serialize(self.getBody(), args);
12317
12318                         args.content = tinymce.trim(content);
12319
12320                         // Do post processing
12321                         if (!args.no_events)
12322                                 self.onGetContent.dispatch(self, args);
12323
12324                         return args.content;
12325                 },
12326
12327                 isDirty : function() {
12328                         var self = this;
12329
12330                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
12331                 },
12332
12333                 getContainer : function() {
12334                         var t = this;
12335
12336                         if (!t.container)
12337                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');
12338
12339                         return t.container;
12340                 },
12341
12342                 getContentAreaContainer : function() {
12343                         return this.contentAreaContainer;
12344                 },
12345
12346                 getElement : function() {
12347                         return DOM.get(this.settings.content_element || this.id);
12348                 },
12349
12350                 getWin : function() {
12351                         var t = this, e;
12352
12353                         if (!t.contentWindow) {
12354                                 e = DOM.get(t.id + "_ifr");
12355
12356                                 if (e)
12357                                         t.contentWindow = e.contentWindow;
12358                         }
12359
12360                         return t.contentWindow;
12361                 },
12362
12363                 getDoc : function() {
12364                         var t = this, w;
12365
12366                         if (!t.contentDocument) {
12367                                 w = t.getWin();
12368
12369                                 if (w)
12370                                         t.contentDocument = w.document;
12371                         }
12372
12373                         return t.contentDocument;
12374                 },
12375
12376                 getBody : function() {
12377                         return this.bodyElement || this.getDoc().body;
12378                 },
12379
12380                 convertURL : function(u, n, e) {
12381                         var t = this, s = t.settings;
12382
12383                         // Use callback instead
12384                         if (s.urlconverter_callback)
12385                                 return t.execCallback('urlconverter_callback', u, e, true, n);
12386
12387                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
12388                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
12389                                 return u;
12390
12391                         // Convert to relative
12392                         if (s.relative_urls)
12393                                 return t.documentBaseURI.toRelative(u);
12394
12395                         // Convert to absolute
12396                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
12397
12398                         return u;
12399                 },
12400
12401                 addVisual : function(e) {
12402                         var t = this, s = t.settings;
12403
12404                         e = e || t.getBody();
12405
12406                         if (!is(t.hasVisual))
12407                                 t.hasVisual = s.visual;
12408
12409                         each(t.dom.select('table,a', e), function(e) {
12410                                 var v;
12411
12412                                 switch (e.nodeName) {
12413                                         case 'TABLE':
12414                                                 v = t.dom.getAttrib(e, 'border');
12415
12416                                                 if (!v || v == '0') {
12417                                                         if (t.hasVisual)
12418                                                                 t.dom.addClass(e, s.visual_table_class);
12419                                                         else
12420                                                                 t.dom.removeClass(e, s.visual_table_class);
12421                                                 }
12422
12423                                                 return;
12424
12425                                         case 'A':
12426                                                 v = t.dom.getAttrib(e, 'name');
12427
12428                                                 if (v) {
12429                                                         if (t.hasVisual)
12430                                                                 t.dom.addClass(e, 'mceItemAnchor');
12431                                                         else
12432                                                                 t.dom.removeClass(e, 'mceItemAnchor');
12433                                                 }
12434
12435                                                 return;
12436                                 }
12437                         });
12438
12439                         t.onVisualAid.dispatch(t, e, t.hasVisual);
12440                 },
12441
12442                 remove : function() {
12443                         var t = this, e = t.getContainer();
12444
12445                         t.removed = 1; // Cancels post remove event execution
12446                         t.hide();
12447
12448                         t.execCallback('remove_instance_callback', t);
12449                         t.onRemove.dispatch(t);
12450
12451                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
12452                         t.onExecCommand.listeners = [];
12453
12454                         tinymce.remove(t);
12455                         DOM.remove(e);
12456                 },
12457
12458                 destroy : function(s) {
12459                         var t = this;
12460
12461                         // One time is enough
12462                         if (t.destroyed)
12463                                 return;
12464
12465                         if (!s) {
12466                                 tinymce.removeUnload(t.destroy);
12467                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
12468
12469                                 // Manual destroy
12470                                 if (t.theme && t.theme.destroy)
12471                                         t.theme.destroy();
12472
12473                                 // Destroy controls, selection and dom
12474                                 t.controlManager.destroy();
12475                                 t.selection.destroy();
12476                                 t.dom.destroy();
12477
12478                                 // Remove all events
12479
12480                                 // Don't clear the window or document if content editable
12481                                 // is enabled since other instances might still be present
12482                                 if (!t.settings.content_editable) {
12483                                         Event.clear(t.getWin());
12484                                         Event.clear(t.getDoc());
12485                                 }
12486
12487                                 Event.clear(t.getBody());
12488                                 Event.clear(t.formElement);
12489                         }
12490
12491                         if (t.formElement) {
12492                                 t.formElement.submit = t.formElement._mceOldSubmit;
12493                                 t.formElement._mceOldSubmit = null;
12494                         }
12495
12496                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
12497
12498                         if (t.selection)
12499                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
12500
12501                         t.destroyed = 1;
12502                 },
12503
12504                 // Internal functions
12505
12506                 _addEvents : function() {
12507                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
12508                         var t = this, i, s = t.settings, dom = t.dom, lo = {
12509                                 mouseup : 'onMouseUp',
12510                                 mousedown : 'onMouseDown',
12511                                 click : 'onClick',
12512                                 keyup : 'onKeyUp',
12513                                 keydown : 'onKeyDown',
12514                                 keypress : 'onKeyPress',
12515                                 submit : 'onSubmit',
12516                                 reset : 'onReset',
12517                                 contextmenu : 'onContextMenu',
12518                                 dblclick : 'onDblClick',
12519                                 paste : 'onPaste' // Doesn't work in all browsers yet
12520                         };
12521
12522                         function eventHandler(e, o) {
12523                                 var ty = e.type;
12524
12525                                 // Don't fire events when it's removed
12526                                 if (t.removed)
12527                                         return;
12528
12529                                 // Generic event handler
12530                                 if (t.onEvent.dispatch(t, e, o) !== false) {
12531                                         // Specific event handler
12532                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);
12533                                 }
12534                         };
12535
12536                         // Add DOM events
12537                         each(lo, function(v, k) {
12538                                 switch (k) {
12539                                         case 'contextmenu':
12540                                                 dom.bind(t.getDoc(), k, eventHandler);
12541                                                 break;
12542
12543                                         case 'paste':
12544                                                 dom.bind(t.getBody(), k, function(e) {
12545                                                         eventHandler(e);
12546                                                 });
12547                                                 break;
12548
12549                                         case 'submit':
12550                                         case 'reset':
12551                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
12552                                                 break;
12553
12554                                         default:
12555                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
12556                                 }
12557                         });
12558
12559                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
12560                                 t.focus(true);
12561                         });
12562
12563
12564                         // Fixes bug where a specified document_base_uri could result in broken images
12565                         // This will also fix drag drop of images in Gecko
12566                         if (tinymce.isGecko) {
12567                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
12568                                         var v;
12569
12570                                         e = e.target;
12571
12572                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
12573                                                 e.src = t.documentBaseURI.toAbsolute(v);
12574                                 });
12575                         }
12576
12577                         // Set various midas options in Gecko
12578                         if (isGecko) {
12579                                 function setOpts() {
12580                                         var t = this, d = t.getDoc(), s = t.settings;
12581
12582                                         if (isGecko && !s.readonly) {
12583                                                 if (t._isHidden()) {
12584                                                         try {
12585                                                                 if (!s.content_editable) {
12586                                                                         d.body.contentEditable = false;
12587                                                                         d.body.contentEditable = true;
12588                                                                 }
12589                                                         } catch (ex) {
12590                                                                 // Fails if it's hidden
12591                                                         }
12592                                                 }
12593
12594                                                 try {
12595                                                         // Try new Gecko method
12596                                                         d.execCommand("styleWithCSS", 0, false);
12597                                                 } catch (ex) {
12598                                                         // Use old method
12599                                                         if (!t._isHidden())
12600                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
12601                                                 }
12602
12603                                                 if (!s.table_inline_editing)
12604                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
12605
12606                                                 if (!s.object_resizing)
12607                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
12608                                         }
12609                                 };
12610
12611                                 t.onBeforeExecCommand.add(setOpts);
12612                                 t.onMouseDown.add(setOpts);
12613                         }
12614
12615                         t.onClick.add(function(ed, e) {
12616                                 e = e.target;
12617
12618                                 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
12619                                 // WebKit can't even do simple things like selecting an image
12620                                 // Needs tobe the setBaseAndExtend or it will fail to select floated images
12621                                 if (tinymce.isWebKit && e.nodeName == 'IMG')
12622                                         t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
12623
12624                                 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))
12625                                         t.selection.select(e);
12626
12627                                 t.nodeChanged();
12628                         });
12629
12630                         // Add node change handlers
12631                         t.onMouseUp.add(t.nodeChanged);
12632                         //t.onClick.add(t.nodeChanged);
12633                         t.onKeyUp.add(function(ed, e) {
12634                                 var c = e.keyCode;
12635
12636                                 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)
12637                                         t.nodeChanged();
12638                         });
12639
12640
12641                         // Add block quote deletion handler
12642                         t.onKeyDown.add(function(ed, e) {
12643                                 // Was the BACKSPACE key pressed?
12644                                 if (e.keyCode != 8)
12645                                         return;
12646
12647                                 var n = ed.selection.getRng().startContainer;
12648                                 var offset = ed.selection.getRng().startOffset;
12649
12650                                 while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
12651                                         n = n.parentNode;
12652                                         
12653                                 // Is the cursor at the beginning of a blockquote?
12654                                 if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
12655                                         // Remove the blockquote
12656                                         ed.formatter.toggle('blockquote', null, n.parentNode);
12657
12658                                         // Move the caret to the beginning of n
12659                                         var rng = ed.selection.getRng();
12660                                         rng.setStart(n, 0);
12661                                         rng.setEnd(n, 0);
12662                                         ed.selection.setRng(rng);
12663                                         ed.selection.collapse(false);
12664                                 }
12665                         });
12666  
12667
12668
12669                         // Add reset handler
12670                         t.onReset.add(function() {
12671                                 t.setContent(t.startContent, {format : 'raw'});
12672                         });
12673
12674                         // Add shortcuts
12675                         if (s.custom_shortcuts) {
12676                                 if (s.custom_undo_redo_keyboard_shortcuts) {
12677                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
12678                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
12679                                 }
12680
12681                                 // Add default shortcuts for gecko
12682                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
12683                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
12684                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
12685
12686                                 // BlockFormat shortcuts keys
12687                                 for (i=1; i<=6; i++)
12688                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
12689
12690                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
12691                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
12692                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
12693
12694                                 function find(e) {
12695                                         var v = null;
12696
12697                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)
12698                                                 return v;
12699
12700                                         each(t.shortcuts, function(o) {
12701                                                 if (tinymce.isMac && o.ctrl != e.metaKey)
12702                                                         return;
12703                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
12704                                                         return;
12705
12706                                                 if (o.alt != e.altKey)
12707                                                         return;
12708
12709                                                 if (o.shift != e.shiftKey)
12710                                                         return;
12711
12712                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
12713                                                         v = o;
12714                                                         return false;
12715                                                 }
12716                                         });
12717
12718                                         return v;
12719                                 };
12720
12721                                 t.onKeyUp.add(function(ed, e) {
12722                                         var o = find(e);
12723
12724                                         if (o)
12725                                                 return Event.cancel(e);
12726                                 });
12727
12728                                 t.onKeyPress.add(function(ed, e) {
12729                                         var o = find(e);
12730
12731                                         if (o)
12732                                                 return Event.cancel(e);
12733                                 });
12734
12735                                 t.onKeyDown.add(function(ed, e) {
12736                                         var o = find(e);
12737
12738                                         if (o) {
12739                                                 o.func.call(o.scope);
12740                                                 return Event.cancel(e);
12741                                         }
12742                                 });
12743                         }
12744
12745                         if (tinymce.isIE) {
12746                                 // Fix so resize will only update the width and height attributes not the styles of an image
12747                                 // It will also block mceItemNoResize items
12748                                 dom.bind(t.getDoc(), 'controlselect', function(e) {
12749                                         var re = t.resizeInfo, cb;
12750
12751                                         e = e.target;
12752
12753                                         // Don't do this action for non image elements
12754                                         if (e.nodeName !== 'IMG')
12755                                                 return;
12756
12757                                         if (re)
12758                                                 dom.unbind(re.node, re.ev, re.cb);
12759
12760                                         if (!dom.hasClass(e, 'mceItemNoResize')) {
12761                                                 ev = 'resizeend';
12762                                                 cb = dom.bind(e, ev, function(e) {
12763                                                         var v;
12764
12765                                                         e = e.target;
12766
12767                                                         if (v = dom.getStyle(e, 'width')) {
12768                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
12769                                                                 dom.setStyle(e, 'width', '');
12770                                                         }
12771
12772                                                         if (v = dom.getStyle(e, 'height')) {
12773                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
12774                                                                 dom.setStyle(e, 'height', '');
12775                                                         }
12776                                                 });
12777                                         } else {
12778                                                 ev = 'resizestart';
12779                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
12780                                         }
12781
12782                                         re = t.resizeInfo = {
12783                                                 node : e,
12784                                                 ev : ev,
12785                                                 cb : cb
12786                                         };
12787                                 });
12788                         }
12789
12790                         if (tinymce.isOpera) {
12791                                 t.onClick.add(function(ed, e) {
12792                                         Event.prevent(e);
12793                                 });
12794                         }
12795
12796                         // Add custom undo/redo handlers
12797                         if (s.custom_undo_redo) {
12798                                 function addUndo() {
12799                                         t.undoManager.typing = false;
12800                                         t.undoManager.add();
12801                                 };
12802
12803                                 dom.bind(t.getDoc(), 'focusout', function(e) {
12804                                         if (!t.removed && t.undoManager.typing)
12805                                                 addUndo();
12806                                 });
12807
12808                                 // Add undo level when contents is drag/dropped within the editor
12809                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
12810                                         addUndo();
12811                                 });
12812
12813                                 t.onKeyUp.add(function(ed, e) {
12814                                         var keyCode = e.keyCode;
12815
12816                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
12817                                                 addUndo();
12818                                 });
12819
12820                                 t.onKeyDown.add(function(ed, e) {
12821                                         var keyCode = e.keyCode, sel;
12822
12823                                         if (keyCode == 8) {
12824                                                 sel = t.getDoc().selection;
12825
12826                                                 // Fix IE control + backspace browser bug
12827                                                 if (sel && sel.createRange && sel.createRange().item) {
12828                                                         t.undoManager.beforeChange();
12829                                                         ed.dom.remove(sel.createRange().item(0));
12830                                                         addUndo();
12831
12832                                                         return Event.cancel(e);
12833                                                 }
12834                                         }
12835
12836                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
12837                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
12838                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
12839                                                 // Todo: Remove this once we normalize enter behavior on IE
12840                                                 if (tinymce.isIE && keyCode == 13)
12841                                                         t.undoManager.beforeChange();
12842
12843                                                 if (t.undoManager.typing)
12844                                                         addUndo();
12845
12846                                                 return;
12847                                         }
12848
12849                                         // If key isn't shift,ctrl,alt,capslock,metakey
12850                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
12851                                                 t.undoManager.beforeChange();
12852                                                 t.undoManager.typing = true;
12853                                                 t.undoManager.add();
12854                                         }
12855                                 });
12856
12857                                 t.onMouseDown.add(function() {
12858                                         if (t.undoManager.typing)
12859                                                 addUndo();
12860                                 });
12861                         }
12862
12863                         // Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5
12864                         // It only fires the nodeChange event every 50ms since it would other wise update the UI when you type and it hogs the CPU
12865                         if (tinymce.isWebKit) {
12866                                 dom.bind(t.getDoc(), 'selectionchange', function() {
12867                                         if (t.selectionTimer) {
12868                                                 window.clearTimeout(t.selectionTimer);
12869                                                 t.selectionTimer = 0;
12870                                         }
12871
12872                                         t.selectionTimer = window.setTimeout(function() {
12873                                                 t.nodeChanged();
12874                                         }, 50);
12875                                 });
12876                         }
12877
12878                         // Bug fix for FireFox keeping styles from end of selection instead of start.
12879                         if (tinymce.isGecko) {
12880                                 function getAttributeApplyFunction() {
12881                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
12882
12883                                         return function() {
12884                                                 var target = t.selection.getStart();
12885
12886                                                 if (target !== t.getBody()) {
12887                                                         t.dom.removeAllAttribs(target);
12888
12889                                                         each(template, function(attr) {
12890                                                                 target.setAttributeNode(attr.cloneNode(true));
12891                                                         });
12892                                                 }
12893                                         };
12894                                 }
12895
12896                                 function isSelectionAcrossElements() {
12897                                         var s = t.selection;
12898
12899                                         return !s.isCollapsed() && s.getStart() != s.getEnd();
12900                                 }
12901
12902                                 t.onKeyPress.add(function(ed, e) {
12903                                         var applyAttributes;
12904
12905                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
12906                                                 applyAttributes = getAttributeApplyFunction();
12907                                                 t.getDoc().execCommand('delete', false, null);
12908                                                 applyAttributes();
12909
12910                                                 return Event.cancel(e);
12911                                         }
12912                                 });
12913
12914                                 t.dom.bind(t.getDoc(), 'cut', function(e) {
12915                                         var applyAttributes;
12916
12917                                         if (isSelectionAcrossElements()) {
12918                                                 applyAttributes = getAttributeApplyFunction();
12919                                                 t.onKeyUp.addToTop(Event.cancel, Event);
12920
12921                                                 setTimeout(function() {
12922                                                         applyAttributes();
12923                                                         t.onKeyUp.remove(Event.cancel, Event);
12924                                                 }, 0);
12925                                         }
12926                                 });
12927                         }
12928                 },
12929
12930                 _isHidden : function() {
12931                         var s;
12932
12933                         if (!isGecko)
12934                                 return 0;
12935
12936                         // Weird, wheres that cursor selection?
12937                         s = this.selection.getSel();
12938                         return (!s || !s.rangeCount || s.rangeCount == 0);
12939                 }
12940         });
12941 })(tinymce);
12942
12943 (function(tinymce) {
12944         // Added for compression purposes
12945         var each = tinymce.each, undefined, TRUE = true, FALSE = false;
12946
12947         tinymce.EditorCommands = function(editor) {
12948                 var dom = editor.dom,
12949                         selection = editor.selection,
12950                         commands = {state: {}, exec : {}, value : {}},
12951                         settings = editor.settings,
12952                         bookmark;
12953
12954                 function execCommand(command, ui, value) {
12955                         var func;
12956
12957                         command = command.toLowerCase();
12958                         if (func = commands.exec[command]) {
12959                                 func(command, ui, value);
12960                                 return TRUE;
12961                         }
12962
12963                         return FALSE;
12964                 };
12965
12966                 function queryCommandState(command) {
12967                         var func;
12968
12969                         command = command.toLowerCase();
12970                         if (func = commands.state[command])
12971                                 return func(command);
12972
12973                         return -1;
12974                 };
12975
12976                 function queryCommandValue(command) {
12977                         var func;
12978
12979                         command = command.toLowerCase();
12980                         if (func = commands.value[command])
12981                                 return func(command);
12982
12983                         return FALSE;
12984                 };
12985
12986                 function addCommands(command_list, type) {
12987                         type = type || 'exec';
12988
12989                         each(command_list, function(callback, command) {
12990                                 each(command.toLowerCase().split(','), function(command) {
12991                                         commands[type][command] = callback;
12992                                 });
12993                         });
12994                 };
12995
12996                 // Expose public methods
12997                 tinymce.extend(this, {
12998                         execCommand : execCommand,
12999                         queryCommandState : queryCommandState,
13000                         queryCommandValue : queryCommandValue,
13001                         addCommands : addCommands
13002                 });
13003
13004                 // Private methods
13005
13006                 function execNativeCommand(command, ui, value) {
13007                         if (ui === undefined)
13008                                 ui = FALSE;
13009
13010                         if (value === undefined)
13011                                 value = null;
13012
13013                         return editor.getDoc().execCommand(command, ui, value);
13014                 };
13015
13016                 function isFormatMatch(name) {
13017                         return editor.formatter.match(name);
13018                 };
13019
13020                 function toggleFormat(name, value) {
13021                         editor.formatter.toggle(name, value ? {value : value} : undefined);
13022                 };
13023
13024                 function storeSelection(type) {
13025                         bookmark = selection.getBookmark(type);
13026                 };
13027
13028                 function restoreSelection() {
13029                         selection.moveToBookmark(bookmark);
13030                 };
13031
13032                 // Add execCommand overrides
13033                 addCommands({
13034                         // Ignore these, added for compatibility
13035                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},
13036
13037                         // Add undo manager logic
13038                         'mceEndUndoLevel,mceAddUndoLevel' : function() {
13039                                 editor.undoManager.add();
13040                         },
13041
13042                         'Cut,Copy,Paste' : function(command) {
13043                                 var doc = editor.getDoc(), failed;
13044
13045                                 // Try executing the native command
13046                                 try {
13047                                         execNativeCommand(command);
13048                                 } catch (ex) {
13049                                         // Command failed
13050                                         failed = TRUE;
13051                                 }
13052
13053                                 // Present alert message about clipboard access not being available
13054                                 if (failed || !doc.queryCommandSupported(command)) {
13055                                         if (tinymce.isGecko) {
13056                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
13057                                                         if (state)
13058                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
13059                                                 });
13060                                         } else
13061                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
13062                                 }
13063                         },
13064
13065                         // Override unlink command
13066                         unlink : function(command) {
13067                                 if (selection.isCollapsed())
13068                                         selection.select(selection.getNode());
13069
13070                                 execNativeCommand(command);
13071                                 selection.collapse(FALSE);
13072                         },
13073
13074                         // Override justify commands to use the text formatter engine
13075                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13076                                 var align = command.substring(7);
13077
13078                                 // Remove all other alignments first
13079                                 each('left,center,right,full'.split(','), function(name) {
13080                                         if (align != name)
13081                                                 editor.formatter.remove('align' + name);
13082                                 });
13083
13084                                 toggleFormat('align' + align);
13085                                 execCommand('mceRepaint');
13086                         },
13087
13088                         // Override list commands to fix WebKit bug
13089                         'InsertUnorderedList,InsertOrderedList' : function(command) {
13090                                 var listElm, listParent;
13091
13092                                 execNativeCommand(command);
13093
13094                                 // WebKit produces lists within block elements so we need to split them
13095                                 // we will replace the native list creation logic to custom logic later on
13096                                 // TODO: Remove this when the list creation logic is removed
13097                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
13098                                 if (listElm) {
13099                                         listParent = listElm.parentNode;
13100
13101                                         // If list is within a text block then split that block
13102                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
13103                                                 storeSelection();
13104                                                 dom.split(listParent, listElm);
13105                                                 restoreSelection();
13106                                         }
13107                                 }
13108                         },
13109
13110                         // Override commands to use the text formatter engine
13111                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13112                                 toggleFormat(command);
13113                         },
13114
13115                         // Override commands to use the text formatter engine
13116                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
13117                                 toggleFormat(command, value);
13118                         },
13119
13120                         FontSize : function(command, ui, value) {
13121                                 var fontClasses, fontSizes;
13122
13123                                 // Convert font size 1-7 to styles
13124                                 if (value >= 1 && value <= 7) {
13125                                         fontSizes = tinymce.explode(settings.font_size_style_values);
13126                                         fontClasses = tinymce.explode(settings.font_size_classes);
13127
13128                                         if (fontClasses)
13129                                                 value = fontClasses[value - 1] || value;
13130                                         else
13131                                                 value = fontSizes[value - 1] || value;
13132                                 }
13133
13134                                 toggleFormat(command, value);
13135                         },
13136
13137                         RemoveFormat : function(command) {
13138                                 editor.formatter.remove(command);
13139                         },
13140
13141                         mceBlockQuote : function(command) {
13142                                 toggleFormat('blockquote');
13143                         },
13144
13145                         FormatBlock : function(command, ui, value) {
13146                                 return toggleFormat(value || 'p');
13147                         },
13148
13149                         mceCleanup : function() {
13150                                 var bookmark = selection.getBookmark();
13151
13152                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
13153
13154                                 selection.moveToBookmark(bookmark);
13155                         },
13156
13157                         mceRemoveNode : function(command, ui, value) {
13158                                 var node = value || selection.getNode();
13159
13160                                 // Make sure that the body node isn't removed
13161                                 if (node != editor.getBody()) {
13162                                         storeSelection();
13163                                         editor.dom.remove(node, TRUE);
13164                                         restoreSelection();
13165                                 }
13166                         },
13167
13168                         mceSelectNodeDepth : function(command, ui, value) {
13169                                 var counter = 0;
13170
13171                                 dom.getParent(selection.getNode(), function(node) {
13172                                         if (node.nodeType == 1 && counter++ == value) {
13173                                                 selection.select(node);
13174                                                 return FALSE;
13175                                         }
13176                                 }, editor.getBody());
13177                         },
13178
13179                         mceSelectNode : function(command, ui, value) {
13180                                 selection.select(value);
13181                         },
13182
13183                         mceInsertContent : function(command, ui, value) {
13184                                 var parser, serializer, parentNode, rootNode, fragment, args,
13185                                         marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
13186
13187                                 // Setup parser and serializer
13188                                 parser = editor.parser;
13189                                 serializer = new tinymce.html.Serializer({}, editor.schema);
13190                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
13191
13192                                 // Run beforeSetContent handlers on the HTML to be inserted
13193                                 args = {content: value, format: 'html'};
13194                                 selection.onBeforeSetContent.dispatch(selection, args);
13195                                 value = args.content;
13196
13197                                 // Add caret at end of contents if it's missing
13198                                 if (value.indexOf('{$caret}') == -1)
13199                                         value += '{$caret}';
13200
13201                                 // Replace the caret marker with a span bookmark element
13202                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);
13203
13204                                 // Insert node maker where we will insert the new HTML and get it's parent
13205                                 if (!selection.isCollapsed())
13206                                         editor.getDoc().execCommand('Delete', false, null);
13207
13208                                 parentNode = selection.getNode();
13209
13210                                 // Parse the fragment within the context of the parent node
13211                                 args = {context : parentNode.nodeName.toLowerCase()};
13212                                 fragment = parser.parse(value, args);
13213
13214                                 // Move the caret to a more suitable location
13215                                 node = fragment.lastChild;
13216                                 if (node.attr('id') == 'mce_marker') {
13217                                         marker = node;
13218
13219                                         for (node = node.prev; node; node = node.walk(true)) {
13220                                                 if (node.type == 3 || !dom.isBlock(node.name)) {
13221                                                         node.parent.insert(marker, node, node.name === 'br');
13222                                                         break;
13223                                                 }
13224                                         }
13225                                 }
13226
13227                                 // If parser says valid we can insert the contents into that parent
13228                                 if (!args.invalid) {
13229                                         value = serializer.serialize(fragment);
13230
13231                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent
13232                                         node = parentNode.firstChild;
13233                                         node2 = parentNode.lastChild;
13234                                         if (!node || (node === node2 && node.nodeName === 'BR'))
13235                                                 dom.setHTML(parentNode, value);
13236                                         else
13237                                                 selection.setContent(value);
13238                                 } else {
13239                                         // If the fragment was invalid within that context then we need
13240                                         // to parse and process the parent it's inserted into
13241
13242                                         // Insert bookmark node and get the parent
13243                                         selection.setContent(bookmarkHtml);
13244                                         parentNode = editor.selection.getNode();
13245                                         rootNode = editor.getBody();
13246
13247                                         // Opera will return the document node when selection is in root
13248                                         if (parentNode.nodeType == 9)
13249                                                 parentNode = node = rootNode;
13250                                         else
13251                                                 node = parentNode;
13252
13253                                         // Find the ancestor just before the root element
13254                                         while (node !== rootNode) {
13255                                                 parentNode = node;
13256                                                 node = node.parentNode;
13257                                         }
13258
13259                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
13260                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
13261                                         value = serializer.serialize(
13262                                                 parser.parse(
13263                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem
13264                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
13265                                                                 return serializer.serialize(fragment);
13266                                                         })
13267                                                 )
13268                                         );
13269
13270                                         // Set the inner/outer HTML depending on if we are in the root or not
13271                                         if (parentNode == rootNode)
13272                                                 dom.setHTML(rootNode, value);
13273                                         else
13274                                                 dom.setOuterHTML(parentNode, value);
13275                                 }
13276
13277                                 marker = dom.get('mce_marker');
13278
13279                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
13280                                 nodeRect = dom.getRect(marker);
13281                                 viewPortRect = dom.getViewPort(editor.getWin());
13282
13283                                 // Check if node is out side the viewport if it is then scroll to it
13284                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
13285                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
13286                                         viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
13287                                         viewportBodyElement.scrollLeft = nodeRect.x;
13288                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
13289                                 }
13290
13291                                 // Move selection before marker and remove it
13292                                 rng = dom.createRng();
13293
13294                                 // If previous sibling is a text node set the selection to the end of that node
13295                                 node = marker.previousSibling;
13296                                 if (node && node.nodeType == 3) {
13297                                         rng.setStart(node, node.nodeValue.length);
13298                                 } else {
13299                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
13300                                         rng.setStartBefore(marker);
13301                                         rng.setEndBefore(marker);
13302                                 }
13303
13304                                 // Remove the marker node and set the new range
13305                                 dom.remove(marker);
13306                                 selection.setRng(rng);
13307
13308                                 // Dispatch after event and add any visual elements needed
13309                                 selection.onSetContent.dispatch(selection, args);
13310                                 editor.addVisual();
13311                         },
13312
13313                         mceInsertRawHTML : function(command, ui, value) {
13314                                 selection.setContent('tiny_mce_marker');
13315                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
13316                         },
13317
13318                         mceSetContent : function(command, ui, value) {
13319                                 editor.setContent(value);
13320                         },
13321
13322                         'Indent,Outdent' : function(command) {
13323                                 var intentValue, indentUnit, value;
13324
13325                                 // Setup indent level
13326                                 intentValue = settings.indentation;
13327                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
13328                                 intentValue = parseInt(intentValue);
13329
13330                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
13331                                         each(selection.getSelectedBlocks(), function(element) {
13332                                                 if (command == 'outdent') {
13333                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
13334                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
13335                                                 } else
13336                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
13337                                         });
13338                                 } else
13339                                         execNativeCommand(command);
13340                         },
13341
13342                         mceRepaint : function() {
13343                                 var bookmark;
13344
13345                                 if (tinymce.isGecko) {
13346                                         try {
13347                                                 storeSelection(TRUE);
13348
13349                                                 if (selection.getSel())
13350                                                         selection.getSel().selectAllChildren(editor.getBody());
13351
13352                                                 selection.collapse(TRUE);
13353                                                 restoreSelection();
13354                                         } catch (ex) {
13355                                                 // Ignore
13356                                         }
13357                                 }
13358                         },
13359
13360                         mceToggleFormat : function(command, ui, value) {
13361                                 editor.formatter.toggle(value);
13362                         },
13363
13364                         InsertHorizontalRule : function() {
13365                                 editor.execCommand('mceInsertContent', false, '<hr />');
13366                         },
13367
13368                         mceToggleVisualAid : function() {
13369                                 editor.hasVisual = !editor.hasVisual;
13370                                 editor.addVisual();
13371                         },
13372
13373                         mceReplaceContent : function(command, ui, value) {
13374                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
13375                         },
13376
13377                         mceInsertLink : function(command, ui, value) {
13378                                 var link = dom.getParent(selection.getNode(), 'a'), img, style, cls;
13379
13380                                 if (tinymce.is(value, 'string'))
13381                                         value = {href : value};
13382
13383                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
13384                                 value.href = value.href.replace(' ', '%20');
13385
13386                                 if (!link) {
13387                                         // WebKit can't create links on floated images for some odd reason
13388                                         // So, just remove styles and restore it later
13389                                         if (tinymce.isWebKit) {
13390                                                 img = dom.getParent(selection.getNode(), 'img');
13391
13392                                                 if (img) {
13393                                                         style = img.style.cssText;
13394                                                         cls = img.className;
13395                                                         img.style.cssText = null;
13396                                                         img.className = null;
13397                                                 }
13398                                         }
13399
13400                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
13401
13402                                         // Restore styles
13403                                         if (style)
13404                                                 img.style.cssText = style;
13405                                         if (cls)
13406                                                 img.className = cls;
13407
13408                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
13409                                                 dom.setAttribs(link, value);
13410                                         });
13411                                 } else {
13412                                         if (value.href)
13413                                                 dom.setAttribs(link, value);
13414                                         else
13415                                                 editor.dom.remove(link, TRUE);
13416                                 }
13417                         },
13418                         
13419                         selectAll : function() {
13420                                 var root = dom.getRoot(), rng = dom.createRng();
13421
13422                                 rng.setStart(root, 0);
13423                                 rng.setEnd(root, root.childNodes.length);
13424
13425                                 editor.selection.setRng(rng);
13426                         }
13427                 });
13428
13429                 // Add queryCommandState overrides
13430                 addCommands({
13431                         // Override justify commands
13432                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13433                                 return isFormatMatch('align' + command.substring(7));
13434                         },
13435
13436                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13437                                 return isFormatMatch(command);
13438                         },
13439
13440                         mceBlockQuote : function() {
13441                                 return isFormatMatch('blockquote');
13442                         },
13443
13444                         Outdent : function() {
13445                                 var node;
13446
13447                                 if (settings.inline_styles) {
13448                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13449                                                 return TRUE;
13450
13451                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13452                                                 return TRUE;
13453                                 }
13454
13455                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
13456                         },
13457
13458                         'InsertUnorderedList,InsertOrderedList' : function(command) {
13459                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
13460                         }
13461                 }, 'state');
13462
13463                 // Add queryCommandValue overrides
13464                 addCommands({
13465                         'FontSize,FontName' : function(command) {
13466                                 var value = 0, parent;
13467
13468                                 if (parent = dom.getParent(selection.getNode(), 'span')) {
13469                                         if (command == 'fontsize')
13470                                                 value = parent.style.fontSize;
13471                                         else
13472                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
13473                                 }
13474
13475                                 return value;
13476                         }
13477                 }, 'value');
13478
13479                 // Add undo manager logic
13480                 if (settings.custom_undo_redo) {
13481                         addCommands({
13482                                 Undo : function() {
13483                                         editor.undoManager.undo();
13484                                 },
13485
13486                                 Redo : function() {
13487                                         editor.undoManager.redo();
13488                                 }
13489                         });
13490                 }
13491         };
13492 })(tinymce);
13493
13494 (function(tinymce) {
13495         var Dispatcher = tinymce.util.Dispatcher;
13496
13497         tinymce.UndoManager = function(editor) {
13498                 var self, index = 0, data = [], beforeBookmark;
13499
13500                 function getContent() {
13501                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
13502                 };
13503
13504                 return self = {
13505                         typing : false,
13506
13507                         onAdd : new Dispatcher(self),
13508
13509                         onUndo : new Dispatcher(self),
13510
13511                         onRedo : new Dispatcher(self),
13512
13513                         beforeChange : function() {
13514                                 beforeBookmark = editor.selection.getBookmark(2, true);
13515                         },
13516
13517                         add : function(level) {
13518                                 var i, settings = editor.settings, lastLevel;
13519
13520                                 level = level || {};
13521                                 level.content = getContent();
13522
13523                                 // Add undo level if needed
13524                                 lastLevel = data[index];
13525                                 if (lastLevel && lastLevel.content == level.content)
13526                                         return null;
13527
13528                                 // Set before bookmark on previous level
13529                                 if (data[index])
13530                                         data[index].beforeBookmark = beforeBookmark;
13531
13532                                 // Time to compress
13533                                 if (settings.custom_undo_redo_levels) {
13534                                         if (data.length > settings.custom_undo_redo_levels) {
13535                                                 for (i = 0; i < data.length - 1; i++)
13536                                                         data[i] = data[i + 1];
13537
13538                                                 data.length--;
13539                                                 index = data.length;
13540                                         }
13541                                 }
13542
13543                                 // Get a non intrusive normalized bookmark
13544                                 level.bookmark = editor.selection.getBookmark(2, true);
13545
13546                                 // Crop array if needed
13547                                 if (index < data.length - 1)
13548                                         data.length = index + 1;
13549
13550                                 data.push(level);
13551                                 index = data.length - 1;
13552
13553                                 self.onAdd.dispatch(self, level);
13554                                 editor.isNotDirty = 0;
13555
13556                                 return level;
13557                         },
13558
13559                         undo : function() {
13560                                 var level, i;
13561
13562                                 if (self.typing) {
13563                                         self.add();
13564                                         self.typing = false;
13565                                 }
13566
13567                                 if (index > 0) {
13568                                         level = data[--index];
13569
13570                                         editor.setContent(level.content, {format : 'raw'});
13571                                         editor.selection.moveToBookmark(level.beforeBookmark);
13572
13573                                         self.onUndo.dispatch(self, level);
13574                                 }
13575
13576                                 return level;
13577                         },
13578
13579                         redo : function() {
13580                                 var level;
13581
13582                                 if (index < data.length - 1) {
13583                                         level = data[++index];
13584
13585                                         editor.setContent(level.content, {format : 'raw'});
13586                                         editor.selection.moveToBookmark(level.bookmark);
13587
13588                                         self.onRedo.dispatch(self, level);
13589                                 }
13590
13591                                 return level;
13592                         },
13593
13594                         clear : function() {
13595                                 data = [];
13596                                 index = 0;
13597                                 self.typing = false;
13598                         },
13599
13600                         hasUndo : function() {
13601                                 return index > 0 || this.typing;
13602                         },
13603
13604                         hasRedo : function() {
13605                                 return index < data.length - 1 && !this.typing;
13606                         }
13607                 };
13608         };
13609 })(tinymce);
13610
13611 (function(tinymce) {
13612         // Shorten names
13613         var Event = tinymce.dom.Event,
13614                 isIE = tinymce.isIE,
13615                 isGecko = tinymce.isGecko,
13616                 isOpera = tinymce.isOpera,
13617                 each = tinymce.each,
13618                 extend = tinymce.extend,
13619                 TRUE = true,
13620                 FALSE = false;
13621
13622         function cloneFormats(node) {
13623                 var clone, temp, inner;
13624
13625                 do {
13626                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
13627                                 if (clone) {
13628                                         temp = node.cloneNode(false);
13629                                         temp.appendChild(clone);
13630                                         clone = temp;
13631                                 } else {
13632                                         clone = inner = node.cloneNode(false);
13633                                 }
13634
13635                                 clone.removeAttribute('id');
13636                         }
13637                 } while (node = node.parentNode);
13638
13639                 if (clone)
13640                         return {wrapper : clone, inner : inner};
13641         };
13642
13643         // Checks if the selection/caret is at the end of the specified block element
13644         function isAtEnd(rng, par) {
13645                 var rng2 = par.ownerDocument.createRange();
13646
13647                 rng2.setStart(rng.endContainer, rng.endOffset);
13648                 rng2.setEndAfter(par);
13649
13650                 // 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
13651                 return rng2.cloneContents().textContent.length == 0;
13652         };
13653
13654         function splitList(selection, dom, li) {
13655                 var listBlock, block;
13656
13657                 if (dom.isEmpty(li)) {
13658                         listBlock = dom.getParent(li, 'ul,ol');
13659
13660                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
13661                                 dom.split(listBlock, li);
13662                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
13663                                 dom.replace(block, li);
13664                                 selection.select(block, 1);
13665                         }
13666
13667                         return FALSE;
13668                 }
13669
13670                 return TRUE;
13671         };
13672
13673         tinymce.create('tinymce.ForceBlocks', {
13674                 ForceBlocks : function(ed) {
13675                         var t = this, s = ed.settings, elm;
13676
13677                         t.editor = ed;
13678                         t.dom = ed.dom;
13679                         elm = (s.forced_root_block || 'p').toLowerCase();
13680                         s.element = elm.toUpperCase();
13681
13682                         ed.onPreInit.add(t.setup, t);
13683                 },
13684
13685                 setup : function() {
13686                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
13687
13688                         // Force root blocks
13689                         if (s.forced_root_block) {
13690                                 function addRootBlocks() {
13691                                         var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
13692
13693                                         if (!node || node.nodeType !== 1)
13694                                                 return;
13695
13696                                         // Check if node is wrapped in block
13697                                         while (node != rootNode) {
13698                                                 if (blockElements[node.nodeName])
13699                                                         return;
13700
13701                                                 node = node.parentNode;
13702                                         }
13703
13704                                         // Get current selection
13705                                         rng = selection.getRng();
13706                                         if (rng.setStart) {
13707                                                 startContainer = rng.startContainer;
13708                                                 startOffset = rng.startOffset;
13709                                                 endContainer = rng.endContainer;
13710                                                 endOffset = rng.endOffset;
13711                                         } else {
13712                                                 // Force control range into text range
13713                                                 if (rng.item) {
13714                                                         rng = ed.getDoc().body.createTextRange();
13715                                                         rng.moveToElementText(rng.item(0));
13716                                                 }
13717
13718                                                 tmpRng = rng.duplicate();
13719                                                 tmpRng.collapse(true);
13720                                                 startOffset = tmpRng.move('character', offset) * -1;
13721
13722                                                 if (!tmpRng.collapsed) {
13723                                                         tmpRng = rng.duplicate();
13724                                                         tmpRng.collapse(false);
13725                                                         endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
13726                                                 }
13727                                         }
13728
13729                                         // Wrap non block elements and text nodes
13730                                         for (node = rootNode.firstChild; node; node) {
13731                                                 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
13732                                                         if (!rootBlockNode) {
13733                                                                 rootBlockNode = dom.create(s.forced_root_block);
13734                                                                 node.parentNode.insertBefore(rootBlockNode, node);
13735                                                         }
13736
13737                                                         tempNode = node;
13738                                                         node = node.nextSibling;
13739                                                         rootBlockNode.appendChild(tempNode);
13740                                                 } else {
13741                                                         rootBlockNode = null;
13742                                                         node = node.nextSibling;
13743                                                 }
13744                                         }
13745
13746                                         if (rng.setStart) {
13747                                                 rng.setStart(startContainer, startOffset);
13748                                                 rng.setEnd(endContainer, endOffset);
13749                                                 selection.setRng(rng);
13750                                         } else {
13751                                                 try {
13752                                                         rng = ed.getDoc().body.createTextRange();
13753                                                         rng.moveToElementText(rootNode);
13754                                                         rng.collapse(true);
13755                                                         rng.moveStart('character', startOffset);
13756
13757                                                         if (endOffset > 0)
13758                                                                 rng.moveEnd('character', endOffset);
13759
13760                                                         rng.select();
13761                                                 } catch (ex) {
13762                                                         // Ignore
13763                                                 }
13764                                         }
13765
13766                                         ed.nodeChanged();
13767                                 };
13768
13769                                 ed.onKeyUp.add(addRootBlocks);
13770                                 ed.onClick.add(addRootBlocks);
13771                         }
13772
13773                         if (s.force_br_newlines) {
13774                                 // Force IE to produce BRs on enter
13775                                 if (isIE) {
13776                                         ed.onKeyPress.add(function(ed, e) {
13777                                                 var n;
13778
13779                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
13780                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});
13781                                                         n = dom.get('__');
13782                                                         n.removeAttribute('id');
13783                                                         selection.select(n);
13784                                                         selection.collapse();
13785                                                         return Event.cancel(e);
13786                                                 }
13787                                         });
13788                                 }
13789                         }
13790
13791                         if (s.force_p_newlines) {
13792                                 if (!isIE) {
13793                                         ed.onKeyPress.add(function(ed, e) {
13794                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
13795                                                         Event.cancel(e);
13796                                         });
13797                                 } else {
13798                                         // Ungly hack to for IE to preserve the formatting when you press
13799                                         // enter at the end of a block element with formatted contents
13800                                         // This logic overrides the browsers default logic with
13801                                         // custom logic that enables us to control the output
13802                                         tinymce.addUnload(function() {
13803                                                 t._previousFormats = 0; // Fix IE leak
13804                                         });
13805
13806                                         ed.onKeyPress.add(function(ed, e) {
13807                                                 t._previousFormats = 0;
13808
13809                                                 // Clone the current formats, this will later be applied to the new block contents
13810                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
13811                                                         t._previousFormats = cloneFormats(ed.selection.getStart());
13812                                         });
13813
13814                                         ed.onKeyUp.add(function(ed, e) {
13815                                                 // Let IE break the element and the wrap the new caret location in the previous formats
13816                                                 if (e.keyCode == 13 && !e.shiftKey) {
13817                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;
13818
13819                                                         // Parent is an empty block
13820                                                         if (!parent.hasChildNodes() && fmt) {
13821                                                                 parent = dom.getParent(parent, dom.isBlock);
13822
13823                                                                 if (parent && parent.nodeName != 'LI') {
13824                                                                         parent.innerHTML = '';
13825
13826                                                                         if (t._previousFormats) {
13827                                                                                 parent.appendChild(fmt.wrapper);
13828                                                                                 fmt.inner.innerHTML = '\uFEFF';
13829                                                                         } else
13830                                                                                 parent.innerHTML = '\uFEFF';
13831
13832                                                                         selection.select(parent, 1);
13833                                                                         selection.collapse(true);
13834                                                                         ed.getDoc().execCommand('Delete', false, null);
13835                                                                         t._previousFormats = 0;
13836                                                                 }
13837                                                         }
13838                                                 }
13839                                         });
13840                                 }
13841
13842                                 if (isGecko) {
13843                                         ed.onKeyDown.add(function(ed, e) {
13844                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
13845                                                         t.backspaceDelete(e, e.keyCode == 8);
13846                                         });
13847                                 }
13848                         }
13849
13850                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
13851                         if (tinymce.isWebKit) {
13852                                 function insertBr(ed) {
13853                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
13854
13855                                         // Insert BR element
13856                                         rng.insertNode(br = dom.create('br'));
13857
13858                                         // Place caret after BR
13859                                         rng.setStartAfter(br);
13860                                         rng.setEndAfter(br);
13861                                         selection.setRng(rng);
13862
13863                                         // Could not place caret after BR then insert an nbsp entity and move the caret
13864                                         if (selection.getSel().focusNode == br.previousSibling) {
13865                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
13866                                                 selection.collapse(TRUE);
13867                                         }
13868
13869                                         // Create a temporary DIV after the BR and get the position as it
13870                                         // seems like getPos() returns 0 for text nodes and BR elements.
13871                                         dom.insertAfter(div, br);
13872                                         divYPos = dom.getPos(div).y;
13873                                         dom.remove(div);
13874
13875                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
13876                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
13877                                                 ed.getWin().scrollTo(0, divYPos);
13878                                 };
13879
13880                                 ed.onKeyPress.add(function(ed, e) {
13881                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
13882                                                 insertBr(ed);
13883                                                 Event.cancel(e);
13884                                         }
13885                                 });
13886                         }
13887
13888                         // IE specific fixes
13889                         if (isIE) {
13890                                 // Replaces IE:s auto generated paragraphs with the specified element name
13891                                 if (s.element != 'P') {
13892                                         ed.onKeyPress.add(function(ed, e) {
13893                                                 t.lastElm = selection.getNode().nodeName;
13894                                         });
13895
13896                                         ed.onKeyUp.add(function(ed, e) {
13897                                                 var bl, n = selection.getNode(), b = ed.getBody();
13898
13899                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {
13900                                                         n = dom.rename(n, s.element);
13901                                                         selection.select(n);
13902                                                         selection.collapse();
13903                                                         ed.nodeChanged();
13904                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
13905                                                         bl = dom.getParent(n, 'p');
13906
13907                                                         if (bl) {
13908                                                                 dom.rename(bl, s.element);
13909                                                                 ed.nodeChanged();
13910                                                         }
13911                                                 }
13912                                         });
13913                                 }
13914                         }
13915                 },
13916
13917                 getParentBlock : function(n) {
13918                         var d = this.dom;
13919
13920                         return d.getParent(n, d.isBlock);
13921                 },
13922
13923                 insertPara : function(e) {
13924                         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;
13925                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
13926
13927                         ed.undoManager.beforeChange();
13928
13929                         // If root blocks are forced then use Operas default behavior since it's really good
13930 // Removed due to bug: #1853816
13931 //                      if (se.forced_root_block && isOpera)
13932 //                              return TRUE;
13933
13934                         // Setup before range
13935                         rb = d.createRange();
13936
13937                         // If is before the first block element and in body, then move it into first block element
13938                         rb.setStart(s.anchorNode, s.anchorOffset);
13939                         rb.collapse(TRUE);
13940
13941                         // Setup after range
13942                         ra = d.createRange();
13943
13944                         // If is before the first block element and in body, then move it into first block element
13945                         ra.setStart(s.focusNode, s.focusOffset);
13946                         ra.collapse(TRUE);
13947
13948                         // Setup start/end points
13949                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
13950                         sn = dir ? s.anchorNode : s.focusNode;
13951                         so = dir ? s.anchorOffset : s.focusOffset;
13952                         en = dir ? s.focusNode : s.anchorNode;
13953                         eo = dir ? s.focusOffset : s.anchorOffset;
13954
13955                         // If selection is in empty table cell
13956                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
13957                                 if (sn.firstChild.nodeName == 'BR')
13958                                         dom.remove(sn.firstChild); // Remove BR
13959
13960                                 // Create two new block elements
13961                                 if (sn.childNodes.length == 0) {
13962                                         ed.dom.add(sn, se.element, null, '<br />');
13963                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13964                                 } else {
13965                                         n = sn.innerHTML;
13966                                         sn.innerHTML = '';
13967                                         ed.dom.add(sn, se.element, null, n);
13968                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13969                                 }
13970
13971                                 // Move caret into the last one
13972                                 r = d.createRange();
13973                                 r.selectNodeContents(aft);
13974                                 r.collapse(1);
13975                                 ed.selection.setRng(r);
13976
13977                                 return FALSE;
13978                         }
13979
13980                         // If the caret is in an invalid location in FF we need to move it into the first block
13981                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
13982                                 sn = en = sn.firstChild;
13983                                 so = eo = 0;
13984                                 rb = d.createRange();
13985                                 rb.setStart(sn, 0);
13986                                 ra = d.createRange();
13987                                 ra.setStart(en, 0);
13988                         }
13989
13990                         // Never use body as start or end node
13991                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13992                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
13993                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13994                         en = en.nodeName == "BODY" ? en.firstChild : en;
13995
13996                         // Get start and end blocks
13997                         sb = t.getParentBlock(sn);
13998                         eb = t.getParentBlock(en);
13999                         bn = sb ? sb.nodeName : se.element; // Get block name to create
14000
14001                         // Return inside list use default browser behavior
14002                         if (n = t.dom.getParent(sb, 'li,pre')) {
14003                                 if (n.nodeName == 'LI')
14004                                         return splitList(ed.selection, t.dom, n);
14005
14006                                 return TRUE;
14007                         }
14008
14009                         // If caption or absolute layers then always generate new blocks within
14010                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
14011                                 bn = se.element;
14012                                 sb = null;
14013                         }
14014
14015                         // If caption or absolute layers then always generate new blocks within
14016                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
14017                                 bn = se.element;
14018                                 eb = null;
14019                         }
14020
14021                         // Use P instead
14022                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
14023                                 bn = se.element;
14024                                 sb = eb = null;
14025                         }
14026
14027                         // Setup new before and after blocks
14028                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
14029                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
14030
14031                         // Remove id from after clone
14032                         aft.removeAttribute('id');
14033
14034                         // Is header and cursor is at the end, then force paragraph under
14035                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
14036                                 aft = ed.dom.create(se.element);
14037
14038                         // Find start chop node
14039                         n = sc = sn;
14040                         do {
14041                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
14042                                         break;
14043
14044                                 sc = n;
14045                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
14046
14047                         // Find end chop node
14048                         n = ec = en;
14049                         do {
14050                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
14051                                         break;
14052
14053                                 ec = n;
14054                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
14055
14056                         // Place first chop part into before block element
14057                         if (sc.nodeName == bn)
14058                                 rb.setStart(sc, 0);
14059                         else
14060                                 rb.setStartBefore(sc);
14061
14062                         rb.setEnd(sn, so);
14063                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
14064
14065                         // Place secnd chop part within new block element
14066                         try {
14067                                 ra.setEndAfter(ec);
14068                         } catch(ex) {
14069                                 //console.debug(s.focusNode, s.focusOffset);
14070                         }
14071
14072                         ra.setStart(en, eo);
14073                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
14074
14075                         // Create range around everything
14076                         r = d.createRange();
14077                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
14078                                 r.setStartBefore(sc.parentNode);
14079                         } else {
14080                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
14081                                         r.setStartBefore(rb.startContainer);
14082                                 else
14083                                         r.setStart(rb.startContainer, rb.startOffset);
14084                         }
14085
14086                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)
14087                                 r.setEndAfter(ec.parentNode);
14088                         else
14089                                 r.setEnd(ra.endContainer, ra.endOffset);
14090
14091                         // Delete and replace it with new block elements
14092                         r.deleteContents();
14093
14094                         if (isOpera)
14095                                 ed.getWin().scrollTo(0, vp.y);
14096
14097                         // Never wrap blocks in blocks
14098                         if (bef.firstChild && bef.firstChild.nodeName == bn)
14099                                 bef.innerHTML = bef.firstChild.innerHTML;
14100
14101                         if (aft.firstChild && aft.firstChild.nodeName == bn)
14102                                 aft.innerHTML = aft.firstChild.innerHTML;
14103
14104                         function appendStyles(e, en) {
14105                                 var nl = [], nn, n, i;
14106
14107                                 e.innerHTML = '';
14108
14109                                 // Make clones of style elements
14110                                 if (se.keep_styles) {
14111                                         n = en;
14112                                         do {
14113                                                 // We only want style specific elements
14114                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
14115                                                         nn = n.cloneNode(FALSE);
14116                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
14117                                                         nl.push(nn);
14118                                                 }
14119                                         } while (n = n.parentNode);
14120                                 }
14121
14122                                 // Append style elements to aft
14123                                 if (nl.length > 0) {
14124                                         for (i = nl.length - 1, nn = e; i >= 0; i--)
14125                                                 nn = nn.appendChild(nl[i]);
14126
14127                                         // Padd most inner style element
14128                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
14129                                         return nl[0]; // Move caret to most inner element
14130                                 } else
14131                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
14132                         };
14133                                 
14134                         // Padd empty blocks
14135                         if (dom.isEmpty(bef))
14136                                 appendStyles(bef, sn);
14137
14138                         // Fill empty afterblook with current style
14139                         if (dom.isEmpty(aft))
14140                                 car = appendStyles(aft, en);
14141
14142                         // Opera needs this one backwards for older versions
14143                         if (isOpera && parseFloat(opera.version()) < 9.5) {
14144                                 r.insertNode(bef);
14145                                 r.insertNode(aft);
14146                         } else {
14147                                 r.insertNode(aft);
14148                                 r.insertNode(bef);
14149                         }
14150
14151                         // Normalize
14152                         aft.normalize();
14153                         bef.normalize();
14154
14155                         // Move cursor and scroll into view
14156                         ed.selection.select(aft, true);
14157                         ed.selection.collapse(true);
14158
14159                         // 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
14160                         y = ed.dom.getPos(aft).y;
14161                         //ch = aft.clientHeight;
14162
14163                         // Is element within viewport
14164                         if (y < vp.y || y + 25 > vp.y + vp.h) {
14165                                 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
14166
14167                                 /*console.debug(
14168                                         'Element: y=' + y + ', h=' + ch + ', ' +
14169                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
14170                                 );*/
14171                         }
14172
14173                         ed.undoManager.add();
14174
14175                         return FALSE;
14176                 },
14177
14178                 backspaceDelete : function(e, bs) {
14179                         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;
14180
14181                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
14182                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
14183                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
14184
14185                                 // Walk the dom backwards until we find a text node
14186                                 for (n = sc.lastChild; n; n = walker.prev()) {
14187                                         if (n.nodeType == 3) {
14188                                                 r.setStart(n, n.nodeValue.length);
14189                                                 r.collapse(true);
14190                                                 se.setRng(r);
14191                                                 return;
14192                                         }
14193                                 }
14194                         }
14195
14196                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
14197                         // This workaround removes the element by hand and moves the caret to the previous element
14198                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
14199                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
14200                                         // Find previous block element
14201                                         n = sc;
14202                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
14203
14204                                         if (n) {
14205                                                 if (sc != b.firstChild) {
14206                                                         // Find last text node
14207                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
14208                                                         while (tn = w.nextNode())
14209                                                                 n = tn;
14210
14211                                                         // Place caret at the end of last text node
14212                                                         r = ed.getDoc().createRange();
14213                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
14214                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
14215                                                         se.setRng(r);
14216
14217                                                         // Remove the target container
14218                                                         ed.dom.remove(sc);
14219                                                 }
14220
14221                                                 return Event.cancel(e);
14222                                         }
14223                                 }
14224                         }
14225                 }
14226         });
14227 })(tinymce);
14228
14229 (function(tinymce) {
14230         // Shorten names
14231         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
14232
14233         tinymce.create('tinymce.ControlManager', {
14234                 ControlManager : function(ed, s) {
14235                         var t = this, i;
14236
14237                         s = s || {};
14238                         t.editor = ed;
14239                         t.controls = {};
14240                         t.onAdd = new tinymce.util.Dispatcher(t);
14241                         t.onPostRender = new tinymce.util.Dispatcher(t);
14242                         t.prefix = s.prefix || ed.id + '_';
14243                         t._cls = {};
14244
14245                         t.onPostRender.add(function() {
14246                                 each(t.controls, function(c) {
14247                                         c.postRender();
14248                                 });
14249                         });
14250                 },
14251
14252                 get : function(id) {
14253                         return this.controls[this.prefix + id] || this.controls[id];
14254                 },
14255
14256                 setActive : function(id, s) {
14257                         var c = null;
14258
14259                         if (c = this.get(id))
14260                                 c.setActive(s);
14261
14262                         return c;
14263                 },
14264
14265                 setDisabled : function(id, s) {
14266                         var c = null;
14267
14268                         if (c = this.get(id))
14269                                 c.setDisabled(s);
14270
14271                         return c;
14272                 },
14273
14274                 add : function(c) {
14275                         var t = this;
14276
14277                         if (c) {
14278                                 t.controls[c.id] = c;
14279                                 t.onAdd.dispatch(c, t);
14280                         }
14281
14282                         return c;
14283                 },
14284
14285                 createControl : function(n) {
14286                         var c, t = this, ed = t.editor;
14287
14288                         each(ed.plugins, function(p) {
14289                                 if (p.createControl) {
14290                                         c = p.createControl(n, t);
14291
14292                                         if (c)
14293                                                 return false;
14294                                 }
14295                         });
14296
14297                         switch (n) {
14298                                 case "|":
14299                                 case "separator":
14300                                         return t.createSeparator();
14301                         }
14302
14303                         if (!c && ed.buttons && (c = ed.buttons[n]))
14304                                 return t.createButton(n, c);
14305
14306                         return t.add(c);
14307                 },
14308
14309                 createDropMenu : function(id, s, cc) {
14310                         var t = this, ed = t.editor, c, bm, v, cls;
14311
14312                         s = extend({
14313                                 'class' : 'mceDropDown',
14314                                 constrain : ed.settings.constrain_menus
14315                         }, s);
14316
14317                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
14318                         if (v = ed.getParam('skin_variant'))
14319                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
14320
14321                         id = t.prefix + id;
14322                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
14323                         c = t.controls[id] = new cls(id, s);
14324                         c.onAddItem.add(function(c, o) {
14325                                 var s = o.settings;
14326
14327                                 s.title = ed.getLang(s.title, s.title);
14328
14329                                 if (!s.onclick) {
14330                                         s.onclick = function(v) {
14331                                                 if (s.cmd)
14332                                                         ed.execCommand(s.cmd, s.ui || false, s.value);
14333                                         };
14334                                 }
14335                         });
14336
14337                         ed.onRemove.add(function() {
14338                                 c.destroy();
14339                         });
14340
14341                         // Fix for bug #1897785, #1898007
14342                         if (tinymce.isIE) {
14343                                 c.onShowMenu.add(function() {
14344                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
14345                                         ed.focus();
14346
14347                                         bm = ed.selection.getBookmark(1);
14348                                 });
14349
14350                                 c.onHideMenu.add(function() {
14351                                         if (bm) {
14352                                                 ed.selection.moveToBookmark(bm);
14353                                                 bm = 0;
14354                                         }
14355                                 });
14356                         }
14357
14358                         return t.add(c);
14359                 },
14360
14361                 createListBox : function(id, s, cc) {
14362                         var t = this, ed = t.editor, cmd, c, cls;
14363
14364                         if (t.get(id))
14365                                 return null;
14366
14367                         s.title = ed.translate(s.title);
14368                         s.scope = s.scope || ed;
14369
14370                         if (!s.onselect) {
14371                                 s.onselect = function(v) {
14372                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14373                                 };
14374                         }
14375
14376                         s = extend({
14377                                 title : s.title,
14378                                 'class' : 'mce_' + id,
14379                                 scope : s.scope,
14380                                 control_manager : t
14381                         }, s);
14382
14383                         id = t.prefix + id;
14384
14385                         if (ed.settings.use_native_selects)
14386                                 c = new tinymce.ui.NativeListBox(id, s);
14387                         else {
14388                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
14389                                 c = new cls(id, s, ed);
14390                         }
14391
14392                         t.controls[id] = c;
14393
14394                         // Fix focus problem in Safari
14395                         if (tinymce.isWebKit) {
14396                                 c.onPostRender.add(function(c, n) {
14397                                         // Store bookmark on mousedown
14398                                         Event.add(n, 'mousedown', function() {
14399                                                 ed.bookmark = ed.selection.getBookmark(1);
14400                                         });
14401
14402                                         // Restore on focus, since it might be lost
14403                                         Event.add(n, 'focus', function() {
14404                                                 ed.selection.moveToBookmark(ed.bookmark);
14405                                                 ed.bookmark = null;
14406                                         });
14407                                 });
14408                         }
14409
14410                         if (c.hideMenu)
14411                                 ed.onMouseDown.add(c.hideMenu, c);
14412
14413                         return t.add(c);
14414                 },
14415
14416                 createButton : function(id, s, cc) {
14417                         var t = this, ed = t.editor, o, c, cls;
14418
14419                         if (t.get(id))
14420                                 return null;
14421
14422                         s.title = ed.translate(s.title);
14423                         s.label = ed.translate(s.label);
14424                         s.scope = s.scope || ed;
14425
14426                         if (!s.onclick && !s.menu_button) {
14427                                 s.onclick = function() {
14428                                         ed.execCommand(s.cmd, s.ui || false, s.value);
14429                                 };
14430                         }
14431
14432                         s = extend({
14433                                 title : s.title,
14434                                 'class' : 'mce_' + id,
14435                                 unavailable_prefix : ed.getLang('unavailable', ''),
14436                                 scope : s.scope,
14437                                 control_manager : t
14438                         }, s);
14439
14440                         id = t.prefix + id;
14441
14442                         if (s.menu_button) {
14443                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
14444                                 c = new cls(id, s, ed);
14445                                 ed.onMouseDown.add(c.hideMenu, c);
14446                         } else {
14447                                 cls = t._cls.button || tinymce.ui.Button;
14448                                 c = new cls(id, s, ed);
14449                         }
14450
14451                         return t.add(c);
14452                 },
14453
14454                 createMenuButton : function(id, s, cc) {
14455                         s = s || {};
14456                         s.menu_button = 1;
14457
14458                         return this.createButton(id, s, cc);
14459                 },
14460
14461                 createSplitButton : function(id, s, cc) {
14462                         var t = this, ed = t.editor, cmd, c, cls;
14463
14464                         if (t.get(id))
14465                                 return null;
14466
14467                         s.title = ed.translate(s.title);
14468                         s.scope = s.scope || ed;
14469
14470                         if (!s.onclick) {
14471                                 s.onclick = function(v) {
14472                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14473                                 };
14474                         }
14475
14476                         if (!s.onselect) {
14477                                 s.onselect = function(v) {
14478                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14479                                 };
14480                         }
14481
14482                         s = extend({
14483                                 title : s.title,
14484                                 'class' : 'mce_' + id,
14485                                 scope : s.scope,
14486                                 control_manager : t
14487                         }, s);
14488
14489                         id = t.prefix + id;
14490                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
14491                         c = t.add(new cls(id, s, ed));
14492                         ed.onMouseDown.add(c.hideMenu, c);
14493
14494                         return c;
14495                 },
14496
14497                 createColorSplitButton : function(id, s, cc) {
14498                         var t = this, ed = t.editor, cmd, c, cls, bm;
14499
14500                         if (t.get(id))
14501                                 return null;
14502
14503                         s.title = ed.translate(s.title);
14504                         s.scope = s.scope || ed;
14505
14506                         if (!s.onclick) {
14507                                 s.onclick = function(v) {
14508                                         if (tinymce.isIE)
14509                                                 bm = ed.selection.getBookmark(1);
14510
14511                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14512                                 };
14513                         }
14514
14515                         if (!s.onselect) {
14516                                 s.onselect = function(v) {
14517                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14518                                 };
14519                         }
14520
14521                         s = extend({
14522                                 title : s.title,
14523                                 'class' : 'mce_' + id,
14524                                 'menu_class' : ed.getParam('skin') + 'Skin',
14525                                 scope : s.scope,
14526                                 more_colors_title : ed.getLang('more_colors')
14527                         }, s);
14528
14529                         id = t.prefix + id;
14530                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
14531                         c = new cls(id, s, ed);
14532                         ed.onMouseDown.add(c.hideMenu, c);
14533
14534                         // Remove the menu element when the editor is removed
14535                         ed.onRemove.add(function() {
14536                                 c.destroy();
14537                         });
14538
14539                         // Fix for bug #1897785, #1898007
14540                         if (tinymce.isIE) {
14541                                 c.onShowMenu.add(function() {
14542                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
14543                                         ed.focus();
14544                                         bm = ed.selection.getBookmark(1);
14545                                 });
14546
14547                                 c.onHideMenu.add(function() {
14548                                         if (bm) {
14549                                                 ed.selection.moveToBookmark(bm);
14550                                                 bm = 0;
14551                                         }
14552                                 });
14553                         }
14554
14555                         return t.add(c);
14556                 },
14557
14558                 createToolbar : function(id, s, cc) {
14559                         var c, t = this, cls;
14560
14561                         id = t.prefix + id;
14562                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
14563                         c = new cls(id, s, t.editor);
14564
14565                         if (t.get(id))
14566                                 return null;
14567
14568                         return t.add(c);
14569                 },
14570                 
14571                 createToolbarGroup : function(id, s, cc) {
14572                         var c, t = this, cls;
14573                         id = t.prefix + id;
14574                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14575                         c = new cls(id, s, t.editor);
14576                         
14577                         if (t.get(id))
14578                                 return null;
14579                         
14580                         return t.add(c);
14581                 },
14582
14583                 createSeparator : function(cc) {
14584                         var cls = cc || this._cls.separator || tinymce.ui.Separator;
14585
14586                         return new cls();
14587                 },
14588
14589                 setControlType : function(n, c) {
14590                         return this._cls[n.toLowerCase()] = c;
14591                 },
14592         
14593                 destroy : function() {
14594                         each(this.controls, function(c) {
14595                                 c.destroy();
14596                         });
14597
14598                         this.controls = null;
14599                 }
14600         });
14601 })(tinymce);
14602
14603 (function(tinymce) {
14604         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
14605
14606         tinymce.create('tinymce.WindowManager', {
14607                 WindowManager : function(ed) {
14608                         var t = this;
14609
14610                         t.editor = ed;
14611                         t.onOpen = new Dispatcher(t);
14612                         t.onClose = new Dispatcher(t);
14613                         t.params = {};
14614                         t.features = {};
14615                 },
14616
14617                 open : function(s, p) {
14618                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14619
14620                         // Default some options
14621                         s = s || {};
14622                         p = p || {};
14623                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14624                         sh = isOpera ? vp.h : screen.height;
14625                         s.name = s.name || 'mc_' + new Date().getTime();
14626                         s.width = parseInt(s.width || 320);
14627                         s.height = parseInt(s.height || 240);
14628                         s.resizable = true;
14629                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14630                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14631                         p.inline = false;
14632                         p.mce_width = s.width;
14633                         p.mce_height = s.height;
14634                         p.mce_auto_focus = s.auto_focus;
14635
14636                         if (mo) {
14637                                 if (isIE) {
14638                                         s.center = true;
14639                                         s.help = false;
14640                                         s.dialogWidth = s.width + 'px';
14641                                         s.dialogHeight = s.height + 'px';
14642                                         s.scroll = s.scrollbars || false;
14643                                 }
14644                         }
14645
14646                         // Build features string
14647                         each(s, function(v, k) {
14648                                 if (tinymce.is(v, 'boolean'))
14649                                         v = v ? 'yes' : 'no';
14650
14651                                 if (!/^(name|url)$/.test(k)) {
14652                                         if (isIE && mo)
14653                                                 f += (f ? ';' : '') + k + ':' + v;
14654                                         else
14655                                                 f += (f ? ',' : '') + k + '=' + v;
14656                                 }
14657                         });
14658
14659                         t.features = s;
14660                         t.params = p;
14661                         t.onOpen.dispatch(t, s, p);
14662
14663                         u = s.url || s.file;
14664                         u = tinymce._addVer(u);
14665
14666                         try {
14667                                 if (isIE && mo) {
14668                                         w = 1;
14669                                         window.showModalDialog(u, window, f);
14670                                 } else
14671                                         w = window.open(u, s.name, f);
14672                         } catch (ex) {
14673                                 // Ignore
14674                         }
14675
14676                         if (!w)
14677                                 alert(t.editor.getLang('popup_blocked'));
14678                 },
14679
14680                 close : function(w) {
14681                         w.close();
14682                         this.onClose.dispatch(this);
14683                 },
14684
14685                 createInstance : function(cl, a, b, c, d, e) {
14686                         var f = tinymce.resolve(cl);
14687
14688                         return new f(a, b, c, d, e);
14689                 },
14690
14691                 confirm : function(t, cb, s, w) {
14692                         w = w || window;
14693
14694                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
14695                 },
14696
14697                 alert : function(tx, cb, s, w) {
14698                         var t = this;
14699
14700                         w = w || window;
14701                         w.alert(t._decode(t.editor.getLang(tx, tx)));
14702
14703                         if (cb)
14704                                 cb.call(s || t);
14705                 },
14706
14707                 resizeBy : function(dw, dh, win) {
14708                         win.resizeBy(dw, dh);
14709                 },
14710
14711                 // Internal functions
14712
14713                 _decode : function(s) {
14714                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14715                 }
14716         });
14717 }(tinymce));
14718 (function(tinymce) {
14719         tinymce.Formatter = function(ed) {
14720                 var formats = {},
14721                         each = tinymce.each,
14722                         dom = ed.dom,
14723                         selection = ed.selection,
14724                         TreeWalker = tinymce.dom.TreeWalker,
14725                         rangeUtils = new tinymce.dom.RangeUtils(dom),
14726                         isValid = ed.schema.isValidChild,
14727                         isBlock = dom.isBlock,
14728                         forcedRootBlock = ed.settings.forced_root_block,
14729                         nodeIndex = dom.nodeIndex,
14730                         INVISIBLE_CHAR = '\uFEFF',
14731                         MCE_ATTR_RE = /^(src|href|style)$/,
14732                         FALSE = false,
14733                         TRUE = true,
14734                         undefined,
14735                         pendingFormats = {apply : [], remove : []};
14736
14737                 function isArray(obj) {
14738                         return obj instanceof Array;
14739                 };
14740
14741                 function getParents(node, selector) {
14742                         return dom.getParents(node, selector, dom.getRoot());
14743                 };
14744
14745                 function isCaretNode(node) {
14746                         return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
14747                 };
14748
14749                 // Public functions
14750
14751                 function get(name) {
14752                         return name ? formats[name] : formats;
14753                 };
14754
14755                 function register(name, format) {
14756                         if (name) {
14757                                 if (typeof(name) !== 'string') {
14758                                         each(name, function(format, name) {
14759                                                 register(name, format);
14760                                         });
14761                                 } else {
14762                                         // Force format into array and add it to internal collection
14763                                         format = format.length ? format : [format];
14764
14765                                         each(format, function(format) {
14766                                                 // Set deep to false by default on selector formats this to avoid removing
14767                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs
14768                                                 if (format.deep === undefined)
14769                                                         format.deep = !format.selector;
14770
14771                                                 // Default to true
14772                                                 if (format.split === undefined)
14773                                                         format.split = !format.selector || format.inline;
14774
14775                                                 // Default to true
14776                                                 if (format.remove === undefined && format.selector && !format.inline)
14777                                                         format.remove = 'none';
14778
14779                                                 // Mark format as a mixed format inline + block level
14780                                                 if (format.selector && format.inline) {
14781                                                         format.mixed = true;
14782                                                         format.block_expand = true;
14783                                                 }
14784
14785                                                 // Split classes if needed
14786                                                 if (typeof(format.classes) === 'string')
14787                                                         format.classes = format.classes.split(/\s+/);
14788                                         });
14789
14790                                         formats[name] = format;
14791                                 }
14792                         }
14793                 };
14794
14795                 var getTextDecoration = function(node) {
14796                         var decoration;
14797
14798                         ed.dom.getParent(node, function(n) {
14799                                 decoration = ed.dom.getStyle(n, 'text-decoration');
14800                                 return decoration && decoration !== 'none';
14801                         });
14802
14803                         return decoration;
14804                 };
14805
14806                 var processUnderlineAndColor = function(node) {
14807                         var textDecoration;
14808                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14809                                 textDecoration = getTextDecoration(node.parentNode);
14810                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
14811                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);
14812                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14813                                         ed.dom.setStyle(node, 'text-decoration', null);
14814                                 }
14815                         }
14816                 };
14817
14818                 function apply(name, vars, node) {
14819                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
14820
14821                         function moveStart(rng) {
14822                                 var container = rng.startContainer,
14823                                         offset = rng.startOffset,
14824                                         walker, node;
14825
14826                                 // Move startContainer/startOffset in to a suitable node
14827                                 if (container.nodeType == 1 || container.nodeValue === "") {
14828                                         container = container.nodeType == 1 ? container.childNodes[offset] : container;
14829
14830                                         // Might fail if the offset is behind the last element in it's container
14831                                         if (container) {
14832                                                 walker = new TreeWalker(container, container.parentNode);
14833                                                 for (node = walker.current(); node; node = walker.next()) {
14834                                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14835                                                                 rng.setStart(node, 0);
14836                                                                 break;
14837                                                         }
14838                                                 }
14839                                         }
14840                                 }
14841
14842                                 return rng;
14843                         };
14844
14845                         function setElementFormat(elm, fmt) {
14846                                 fmt = fmt || format;
14847
14848                                 if (elm) {
14849                                         each(fmt.styles, function(value, name) {
14850                                                 dom.setStyle(elm, name, replaceVars(value, vars));
14851                                         });
14852
14853                                         each(fmt.attributes, function(value, name) {
14854                                                 dom.setAttrib(elm, name, replaceVars(value, vars));
14855                                         });
14856
14857                                         each(fmt.classes, function(value) {
14858                                                 value = replaceVars(value, vars);
14859
14860                                                 if (!dom.hasClass(elm, value))
14861                                                         dom.addClass(elm, value);
14862                                         });
14863                                 }
14864                         };
14865                         function adjustSelectionToVisibleSelection() {
14866
14867                                 function findSelectionEnd(start, end) {
14868                                         var walker = new TreeWalker(end);
14869                                         for (node = walker.current(); node; node = walker.prev()) {
14870                                                 if (node.childNodes.length > 1 || node == start) {
14871                                                         return node;
14872                                                 }
14873                                         }
14874                                 }
14875
14876                                 // Adjust selection so that a end container with a end offset of zero is not included in the selection
14877                                 // as this isn't visible to the user.
14878                                 var rng = ed.selection.getRng();
14879                                 var start = rng.startContainer;
14880                                 var end = rng.endContainer;
14881                                 if (start != end && rng.endOffset == 0) {
14882                                         var newEnd = findSelectionEnd(start, end);
14883                                         var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
14884                                         rng.setEnd(newEnd, endOffset);
14885                                 }
14886                                 return rng;
14887                         }
14888                         
14889                         function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
14890                                 var nodes =[], listIndex =-1, list, startIndex = -1, endIndex = -1, currentWrapElm;
14891                                 
14892                                 // find the index of the first child list.
14893                                 each(node.childNodes, function(n, index) {
14894                                         if (n.nodeName==="UL"||n.nodeName==="OL") {listIndex = index; list=n; return false; }
14895                                 });
14896                                 
14897                                 // get the index of the bookmarks
14898                                 each(node.childNodes, function(n, index) {
14899                                         if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_start") {startIndex=index}
14900                                         if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_end") {endIndex=index}
14901                                 });
14902                                 
14903                                 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
14904                                 if (listIndex<=0 || (startIndex<listIndex&&endIndex>listIndex)) {
14905                                         each(tinymce.grep(node.childNodes), process);
14906                                         return 0;
14907                                 } else {
14908                                         currentWrapElm = wrapElm.cloneNode(FALSE);
14909                                         
14910                                         // create a list of the nodes on the same side of the list as the selection
14911                                         each(tinymce.grep(node.childNodes), function(n, index) {
14912                                                 if ((startIndex<listIndex && index <listIndex) || (startIndex>listIndex && index >listIndex)) {
14913                                                         nodes.push(n); 
14914                                                         n.parentNode.removeChild(n); 
14915                                                 }
14916                                         });
14917                                         
14918                                         // insert the wrapping element either before or after the list.
14919                                         if (startIndex<listIndex) {
14920                                                 node.insertBefore(currentWrapElm, list);
14921                                         } else if (startIndex>listIndex) {
14922                                                 node.insertBefore(currentWrapElm, list.nextSibling);
14923                                         }
14924                                         
14925                                         // add the new nodes to the list.
14926                                         newWrappers.push(currentWrapElm);
14927                                         each(nodes, function(node){currentWrapElm.appendChild(node)});
14928                                         return currentWrapElm;
14929                                 }
14930                         };
14931                         
14932                         function applyRngStyle(rng, bookmark) {
14933                                 var newWrappers = [], wrapName, wrapElm;
14934
14935                                 // Setup wrapper element
14936                                 wrapName = format.inline || format.block;
14937                                 wrapElm = dom.create(wrapName);
14938                                 setElementFormat(wrapElm);
14939
14940                                 rangeUtils.walk(rng, function(nodes) {
14941                                         var currentWrapElm;
14942
14943                                         function process(node) {
14944                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
14945
14946                                                 // Stop wrapping on br elements
14947                                                 if (isEq(nodeName, 'br')) {
14948                                                         currentWrapElm = 0;
14949
14950                                                         // Remove any br elements when we wrap things
14951                                                         if (format.block)
14952                                                                 dom.remove(node);
14953
14954                                                         return;
14955                                                 }
14956
14957                                                 // If node is wrapper type
14958                                                 if (format.wrapper && matchNode(node, name, vars)) {
14959                                                         currentWrapElm = 0;
14960                                                         return;
14961                                                 }
14962
14963                                                 // Can we rename the block
14964                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
14965                                                         node = dom.rename(node, wrapName);
14966                                                         setElementFormat(node);
14967                                                         newWrappers.push(node);
14968                                                         currentWrapElm = 0;
14969                                                         return;
14970                                                 }
14971
14972                                                 // Handle selector patterns
14973                                                 if (format.selector) {
14974                                                         // Look for matching formats
14975                                                         each(formatList, function(format) {
14976                                                                 // Check collapsed state if it exists
14977                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
14978                                                                         return;
14979                                                                 }
14980
14981                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
14982                                                                         setElementFormat(node, format);
14983                                                                         found = true;
14984                                                                 }
14985                                                         });
14986
14987                                                         // Continue processing if a selector match wasn't found and a inline element is defined
14988                                                         if (!format.inline || found) {
14989                                                                 currentWrapElm = 0;
14990                                                                 return;
14991                                                         }
14992                                                 }
14993
14994                                                 // Is it valid to wrap this item
14995                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
14996                                                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
14997                                                         // Start wrapping
14998                                                         if (!currentWrapElm) {
14999                                                                 // Wrap the node
15000                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
15001                                                                 node.parentNode.insertBefore(currentWrapElm, node);
15002                                                                 newWrappers.push(currentWrapElm);
15003                                                         }
15004
15005                                                         currentWrapElm.appendChild(node);
15006                                                 } else if (nodeName == 'li' && bookmark) {
15007                                                         // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
15008                                                         currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
15009                                                 } else {
15010                                                         // Start a new wrapper for possible children
15011                                                         currentWrapElm = 0;
15012
15013                                                         each(tinymce.grep(node.childNodes), process);
15014
15015                                                         // End the last wrapper
15016                                                         currentWrapElm = 0;
15017                                                 }
15018                                         };
15019
15020                                         // Process siblings from range
15021                                         each(nodes, process);
15022                                 });
15023
15024                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
15025                                 if (format.wrap_links === false) {
15026                                         each(newWrappers, function(node) {
15027                                                 function process(node) {
15028                                                         var i, currentWrapElm, children;
15029
15030                                                         if (node.nodeName === 'A') {
15031                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
15032                                                                 newWrappers.push(currentWrapElm);
15033
15034                                                                 children = tinymce.grep(node.childNodes);
15035                                                                 for (i = 0; i < children.length; i++)
15036                                                                         currentWrapElm.appendChild(children[i]);
15037
15038                                                                 node.appendChild(currentWrapElm);
15039                                                         }
15040
15041                                                         each(tinymce.grep(node.childNodes), process);
15042                                                 };
15043
15044                                                 process(node);
15045                                         });
15046                                 }
15047
15048                                 // Cleanup
15049                                 each(newWrappers, function(node) {
15050                                         var childCount;
15051
15052                                         function getChildCount(node) {
15053                                                 var count = 0;
15054
15055                                                 each(node.childNodes, function(node) {
15056                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
15057                                                                 count++;
15058                                                 });
15059
15060                                                 return count;
15061                                         };
15062
15063                                         function mergeStyles(node) {
15064                                                 var child, clone;
15065
15066                                                 each(node.childNodes, function(node) {
15067                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
15068                                                                 child = node;
15069                                                                 return FALSE; // break loop
15070                                                         }
15071                                                 });
15072
15073                                                 // If child was found and of the same type as the current node
15074                                                 if (child && matchName(child, format)) {
15075                                                         clone = child.cloneNode(FALSE);
15076                                                         setElementFormat(clone);
15077
15078                                                         dom.replace(clone, node, TRUE);
15079                                                         dom.remove(child, 1);
15080                                                 }
15081
15082                                                 return clone || node;
15083                                         };
15084
15085                                         childCount = getChildCount(node);
15086
15087                                         // Remove empty nodes but only if there is multiple wrappers and they are not block
15088                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
15089                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
15090                                                 dom.remove(node, 1);
15091                                                 return;
15092                                         }
15093
15094                                         if (format.inline || format.wrapper) {
15095                                                 // Merges the current node with it's children of similar type to reduce the number of elements
15096                                                 if (!format.exact && childCount === 1)
15097                                                         node = mergeStyles(node);
15098
15099                                                 // Remove/merge children
15100                                                 each(formatList, function(format) {
15101                                                         // Merge all children of similar type will move styles from child to parent
15102                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
15103                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
15104                                                         each(dom.select(format.inline, node), function(child) {
15105                                                                 var parent;
15106
15107                                                                 // When wrap_links is set to false we don't want
15108                                                                 // to remove the format on children within links
15109                                                                 if (format.wrap_links === false) {
15110                                                                         parent = child.parentNode;
15111
15112                                                                         do {
15113                                                                                 if (parent.nodeName === 'A')
15114                                                                                         return;
15115                                                                         } while (parent = parent.parentNode);
15116                                                                 }
15117
15118                                                                 removeFormat(format, vars, child, format.exact ? child : null);
15119                                                         });
15120                                                 });
15121
15122                                                 // Remove child if direct parent is of same type
15123                                                 if (matchNode(node.parentNode, name, vars)) {
15124                                                         dom.remove(node, 1);
15125                                                         node = 0;
15126                                                         return TRUE;
15127                                                 }
15128
15129                                                 // Look for parent with similar style format
15130                                                 if (format.merge_with_parents) {
15131                                                         dom.getParent(node.parentNode, function(parent) {
15132                                                                 if (matchNode(parent, name, vars)) {
15133                                                                         dom.remove(node, 1);
15134                                                                         node = 0;
15135                                                                         return TRUE;
15136                                                                 }
15137                                                         });
15138                                                 }
15139
15140                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
15141                                                 if (node) {
15142                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
15143                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
15144                                                 }
15145                                         }
15146                                 });
15147                         };
15148
15149                         if (format) {
15150                                 if (node) {
15151                                         rng = dom.createRng();
15152
15153                                         rng.setStartBefore(node);
15154                                         rng.setEndAfter(node);
15155
15156                                         applyRngStyle(expandRng(rng, formatList));
15157                                 } else {
15158                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15159                                                 // Obtain selection node before selection is unselected by applyRngStyle()
15160                                                 var curSelNode = ed.selection.getNode();
15161
15162                                                 // Apply formatting to selection
15163                                                 ed.selection.setRng(adjustSelectionToVisibleSelection());
15164                                                 bookmark = selection.getBookmark();
15165                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
15166
15167                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.
15168                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
15169                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
15170                                                         processUnderlineAndColor(curSelNode);
15171                                                 }
15172
15173                                                 selection.moveToBookmark(bookmark);
15174                                                 selection.setRng(moveStart(selection.getRng(TRUE)));
15175                                                 ed.nodeChanged();
15176                                         } else
15177                                                 performCaretAction('apply', name, vars);
15178                                 }
15179                         }
15180                 };
15181
15182                 function remove(name, vars, node) {
15183                         var formatList = get(name), format = formatList[0], bookmark, i, rng;
15184                         function moveStart(rng) {
15185                                 var container = rng.startContainer,
15186                                         offset = rng.startOffset,
15187                                         walker, node, nodes, tmpNode;
15188
15189                                 // Convert text node into index if possible
15190                                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
15191                                         container = container.parentNode;
15192                                         offset = nodeIndex(container) + 1;
15193                                 }
15194
15195                                 // Move startContainer/startOffset in to a suitable node
15196                                 if (container.nodeType == 1) {
15197                                         nodes = container.childNodes;
15198                                         container = nodes[Math.min(offset, nodes.length - 1)];
15199                                         walker = new TreeWalker(container);
15200
15201                                         // If offset is at end of the parent node walk to the next one
15202                                         if (offset > nodes.length - 1)
15203                                                 walker.next();
15204
15205                                         for (node = walker.current(); node; node = walker.next()) {
15206                                                 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
15207                                                         // IE has a "neat" feature where it moves the start node into the closest element
15208                                                         // we can avoid this by inserting an element before it and then remove it after we set the selection
15209                                                         tmpNode = dom.create('a', null, INVISIBLE_CHAR);
15210                                                         node.parentNode.insertBefore(tmpNode, node);
15211
15212                                                         // Set selection and remove tmpNode
15213                                                         rng.setStart(node, 0);
15214                                                         selection.setRng(rng);
15215                                                         dom.remove(tmpNode);
15216
15217                                                         return;
15218                                                 }
15219                                         }
15220                                 }
15221                         };
15222
15223                         // Merges the styles for each node
15224                         function process(node) {
15225                                 var children, i, l;
15226
15227                                 // Grab the children first since the nodelist might be changed
15228                                 children = tinymce.grep(node.childNodes);
15229
15230                                 // Process current node
15231                                 for (i = 0, l = formatList.length; i < l; i++) {
15232                                         if (removeFormat(formatList[i], vars, node, node))
15233                                                 break;
15234                                 }
15235
15236                                 // Process the children
15237                                 if (format.deep) {
15238                                         for (i = 0, l = children.length; i < l; i++)
15239                                                 process(children[i]);
15240                                 }
15241                         };
15242
15243                         function findFormatRoot(container) {
15244                                 var formatRoot;
15245
15246                                 // Find format root
15247                                 each(getParents(container.parentNode).reverse(), function(parent) {
15248                                         var format;
15249
15250                                         // Find format root element
15251                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
15252                                                 // Is the node matching the format we are looking for
15253                                                 format = matchNode(parent, name, vars);
15254                                                 if (format && format.split !== false)
15255                                                         formatRoot = parent;
15256                                         }
15257                                 });
15258
15259                                 return formatRoot;
15260                         };
15261
15262                         function wrapAndSplit(format_root, container, target, split) {
15263                                 var parent, clone, lastClone, firstClone, i, formatRootParent;
15264
15265                                 // Format root found then clone formats and split it
15266                                 if (format_root) {
15267                                         formatRootParent = format_root.parentNode;
15268
15269                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
15270                                                 clone = parent.cloneNode(FALSE);
15271
15272                                                 for (i = 0; i < formatList.length; i++) {
15273                                                         if (removeFormat(formatList[i], vars, clone, clone)) {
15274                                                                 clone = 0;
15275                                                                 break;
15276                                                         }
15277                                                 }
15278
15279                                                 // Build wrapper node
15280                                                 if (clone) {
15281                                                         if (lastClone)
15282                                                                 clone.appendChild(lastClone);
15283
15284                                                         if (!firstClone)
15285                                                                 firstClone = clone;
15286
15287                                                         lastClone = clone;
15288                                                 }
15289                                         }
15290
15291                                         // Never split block elements if the format is mixed
15292                                         if (split && (!format.mixed || !isBlock(format_root)))
15293                                                 container = dom.split(format_root, container);
15294
15295                                         // Wrap container in cloned formats
15296                                         if (lastClone) {
15297                                                 target.parentNode.insertBefore(lastClone, target);
15298                                                 firstClone.appendChild(target);
15299                                         }
15300                                 }
15301
15302                                 return container;
15303                         };
15304
15305                         function splitToFormatRoot(container) {
15306                                 return wrapAndSplit(findFormatRoot(container), container, container, true);
15307                         };
15308
15309                         function unwrap(start) {
15310                                 var node = dom.get(start ? '_start' : '_end'),
15311                                         out = node[start ? 'firstChild' : 'lastChild'];
15312
15313                                 // If the end is placed within the start the result will be removed
15314                                 // So this checks if the out node is a bookmark node if it is it
15315                                 // checks for another more suitable node
15316                                 if (isBookmarkNode(out))
15317                                         out = out[start ? 'firstChild' : 'lastChild'];
15318
15319                                 dom.remove(node, true);
15320
15321                                 return out;
15322                         };
15323
15324                         function removeRngStyle(rng) {
15325                                 var startContainer, endContainer;
15326
15327                                 rng = expandRng(rng, formatList, TRUE);
15328
15329                                 if (format.split) {
15330                                         startContainer = getContainer(rng, TRUE);
15331                                         endContainer = getContainer(rng);
15332
15333                                         if (startContainer != endContainer) {
15334                                                 // Wrap start/end nodes in span element since these might be cloned/moved
15335                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
15336                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
15337
15338                                                 // Split start/end
15339                                                 splitToFormatRoot(startContainer);
15340                                                 splitToFormatRoot(endContainer);
15341
15342                                                 // Unwrap start/end to get real elements again
15343                                                 startContainer = unwrap(TRUE);
15344                                                 endContainer = unwrap();
15345                                         } else
15346                                                 startContainer = endContainer = splitToFormatRoot(startContainer);
15347
15348                                         // Update range positions since they might have changed after the split operations
15349                                         rng.startContainer = startContainer.parentNode;
15350                                         rng.startOffset = nodeIndex(startContainer);
15351                                         rng.endContainer = endContainer.parentNode;
15352                                         rng.endOffset = nodeIndex(endContainer) + 1;
15353                                 }
15354
15355                                 // Remove items between start/end
15356                                 rangeUtils.walk(rng, function(nodes) {
15357                                         each(nodes, function(node) {
15358                                                 process(node);
15359
15360                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
15361                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
15362                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
15363                                                 }
15364                                         });
15365                                 });
15366                         };
15367
15368                         // Handle node
15369                         if (node) {
15370                                 rng = dom.createRng();
15371                                 rng.setStartBefore(node);
15372                                 rng.setEndAfter(node);
15373                                 removeRngStyle(rng);
15374                                 return;
15375                         }
15376
15377                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15378                                 bookmark = selection.getBookmark();
15379                                 removeRngStyle(selection.getRng(TRUE));
15380                                 selection.moveToBookmark(bookmark);
15381
15382                                 // 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
15383                                 if (match(name, vars, selection.getStart())) {
15384                                         moveStart(selection.getRng(true));
15385                                 }
15386
15387                                 ed.nodeChanged();
15388                         } else
15389                                 performCaretAction('remove', name, vars);
15390                 };
15391
15392                 function toggle(name, vars, node) {
15393                         var fmt = get(name);
15394
15395                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
15396                                 remove(name, vars, node);
15397                         else
15398                                 apply(name, vars, node);
15399                 };
15400
15401                 function matchNode(node, name, vars, similar) {
15402                         var formatList = get(name), format, i, classes;
15403
15404                         function matchItems(node, format, item_name) {
15405                                 var key, value, items = format[item_name], i;
15406
15407                                 // Check all items
15408                                 if (items) {
15409                                         // Non indexed object
15410                                         if (items.length === undefined) {
15411                                                 for (key in items) {
15412                                                         if (items.hasOwnProperty(key)) {
15413                                                                 if (item_name === 'attributes')
15414                                                                         value = dom.getAttrib(node, key);
15415                                                                 else
15416                                                                         value = getStyle(node, key);
15417
15418                                                                 if (similar && !value && !format.exact)
15419                                                                         return;
15420
15421                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
15422                                                                         return;
15423                                                         }
15424                                                 }
15425                                         } else {
15426                                                 // Only one match needed for indexed arrays
15427                                                 for (i = 0; i < items.length; i++) {
15428                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
15429                                                                 return format;
15430                                                 }
15431                                         }
15432                                 }
15433
15434                                 return format;
15435                         };
15436
15437                         if (formatList && node) {
15438                                 // Check each format in list
15439                                 for (i = 0; i < formatList.length; i++) {
15440                                         format = formatList[i];
15441
15442                                         // Name name, attributes, styles and classes
15443                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
15444                                                 // Match classes
15445                                                 if (classes = format.classes) {
15446                                                         for (i = 0; i < classes.length; i++) {
15447                                                                 if (!dom.hasClass(node, classes[i]))
15448                                                                         return;
15449                                                         }
15450                                                 }
15451
15452                                                 return format;
15453                                         }
15454                                 }
15455                         }
15456                 };
15457
15458                 function match(name, vars, node) {
15459                         var startNode, i;
15460
15461                         function matchParents(node) {
15462                                 // Find first node with similar format settings
15463                                 node = dom.getParent(node, function(node) {
15464                                         return !!matchNode(node, name, vars, true);
15465                                 });
15466
15467                                 // Do an exact check on the similar format element
15468                                 return matchNode(node, name, vars);
15469                         };
15470
15471                         // Check specified node
15472                         if (node)
15473                                 return matchParents(node);
15474
15475                         // Check pending formats
15476                         if (selection.isCollapsed()) {
15477                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15478                                         if (pendingFormats.apply[i].name == name)
15479                                                 return true;
15480                                 }
15481
15482                                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15483                                         if (pendingFormats.remove[i].name == name)
15484                                                 return false;
15485                                 }
15486
15487                                 return matchParents(selection.getNode());
15488                         }
15489
15490                         // Check selected node
15491                         node = selection.getNode();
15492                         if (matchParents(node))
15493                                 return TRUE;
15494
15495                         // Check start node if it's different
15496                         startNode = selection.getStart();
15497                         if (startNode != node) {
15498                                 if (matchParents(startNode))
15499                                         return TRUE;
15500                         }
15501
15502                         return FALSE;
15503                 };
15504
15505                 function matchAll(names, vars) {
15506                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15507
15508                         // If the selection is collapsed then check pending formats
15509                         if (selection.isCollapsed()) {
15510                                 for (ni = 0; ni < names.length; ni++) {
15511                                         // If the name is to be removed, then stop it from being added
15512                                         for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15513                                                 name = names[ni];
15514
15515                                                 if (pendingFormats.remove[i].name == name) {
15516                                                         checkedMap[name] = true;
15517                                                         break;
15518                                                 }
15519                                         }
15520                                 }
15521
15522                                 // If the format is to be applied
15523                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15524                                         for (ni = 0; ni < names.length; ni++) {
15525                                                 name = names[ni];
15526
15527                                                 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
15528                                                         checkedMap[name] = true;
15529                                                         matchedFormatNames.push(name);
15530                                                 }
15531                                         }
15532                                 }
15533                         }
15534
15535                         // Check start of selection for formats
15536                         startElement = selection.getStart();
15537                         dom.getParent(startElement, function(node) {
15538                                 var i, name;
15539
15540                                 for (i = 0; i < names.length; i++) {
15541                                         name = names[i];
15542
15543                                         if (!checkedMap[name] && matchNode(node, name, vars)) {
15544                                                 checkedMap[name] = true;
15545                                                 matchedFormatNames.push(name);
15546                                         }
15547                                 }
15548                         });
15549
15550                         return matchedFormatNames;
15551                 };
15552
15553                 function canApply(name) {
15554                         var formatList = get(name), startNode, parents, i, x, selector;
15555
15556                         if (formatList) {
15557                                 startNode = selection.getStart();
15558                                 parents = getParents(startNode);
15559
15560                                 for (x = formatList.length - 1; x >= 0; x--) {
15561                                         selector = formatList[x].selector;
15562
15563                                         // Format is not selector based, then always return TRUE
15564                                         if (!selector)
15565                                                 return TRUE;
15566
15567                                         for (i = parents.length - 1; i >= 0; i--) {
15568                                                 if (dom.is(parents[i], selector))
15569                                                         return TRUE;
15570                                         }
15571                                 }
15572                         }
15573
15574                         return FALSE;
15575                 };
15576
15577                 // Expose to public
15578                 tinymce.extend(this, {
15579                         get : get,
15580                         register : register,
15581                         apply : apply,
15582                         remove : remove,
15583                         toggle : toggle,
15584                         match : match,
15585                         matchAll : matchAll,
15586                         matchNode : matchNode,
15587                         canApply : canApply
15588                 });
15589
15590                 // Private functions
15591
15592                 function matchName(node, format) {
15593                         // Check for inline match
15594                         if (isEq(node, format.inline))
15595                                 return TRUE;
15596
15597                         // Check for block match
15598                         if (isEq(node, format.block))
15599                                 return TRUE;
15600
15601                         // Check for selector match
15602                         if (format.selector)
15603                                 return dom.is(node, format.selector);
15604                 };
15605
15606                 function isEq(str1, str2) {
15607                         str1 = str1 || '';
15608                         str2 = str2 || '';
15609
15610                         str1 = '' + (str1.nodeName || str1);
15611                         str2 = '' + (str2.nodeName || str2);
15612
15613                         return str1.toLowerCase() == str2.toLowerCase();
15614                 };
15615
15616                 function getStyle(node, name) {
15617                         var styleVal = dom.getStyle(node, name);
15618
15619                         // Force the format to hex
15620                         if (name == 'color' || name == 'backgroundColor')
15621                                 styleVal = dom.toHex(styleVal);
15622
15623                         // Opera will return bold as 700
15624                         if (name == 'fontWeight' && styleVal == 700)
15625                                 styleVal = 'bold';
15626
15627                         return '' + styleVal;
15628                 };
15629
15630                 function replaceVars(value, vars) {
15631                         if (typeof(value) != "string")
15632                                 value = value(vars);
15633                         else if (vars) {
15634                                 value = value.replace(/%(\w+)/g, function(str, name) {
15635                                         return vars[name] || str;
15636                                 });
15637                         }
15638
15639                         return value;
15640                 };
15641
15642                 function isWhiteSpaceNode(node) {
15643                         return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
15644                 };
15645
15646                 function wrap(node, name, attrs) {
15647                         var wrapper = dom.create(name, attrs);
15648
15649                         node.parentNode.insertBefore(wrapper, node);
15650                         wrapper.appendChild(node);
15651
15652                         return wrapper;
15653                 };
15654
15655                 function expandRng(rng, format, remove) {
15656                         var startContainer = rng.startContainer,
15657                                 startOffset = rng.startOffset,
15658                                 endContainer = rng.endContainer,
15659                                 endOffset = rng.endOffset, sibling, lastIdx, leaf;
15660
15661                         // This function walks up the tree if there is no siblings before/after the node
15662                         function findParentContainer(container, child_name, sibling_name, root) {
15663                                 var parent, child;
15664
15665                                 root = root || dom.getRoot();
15666
15667                                 for (;;) {
15668                                         // Check if we can move up are we at root level or body level
15669                                         parent = container.parentNode;
15670
15671                                         // Stop expanding on block elements or root depending on format
15672                                         if (parent == root || (!format[0].block_expand && isBlock(parent)))
15673                                                 return container;
15674
15675                                         for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
15676                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15677                                                         return container;
15678
15679                                                 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
15680                                                         return container;
15681                                         }
15682
15683                                         container = container.parentNode;
15684                                 }
15685
15686                                 return container;
15687                         };
15688
15689                         // This function walks down the tree to find the leaf at the selection.
15690                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15691                         function findLeaf(node, offset) {
15692                                 if (offset === undefined)
15693                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15694                                 while (node && node.hasChildNodes()) {
15695                                         node = node.childNodes[offset];
15696                                         if (node)
15697                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15698                                 }
15699                                 return { node: node, offset: offset };
15700                         }
15701
15702                         // If index based start position then resolve it
15703                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15704                                 lastIdx = startContainer.childNodes.length - 1;
15705                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15706
15707                                 if (startContainer.nodeType == 3)
15708                                         startOffset = 0;
15709                         }
15710
15711                         // If index based end position then resolve it
15712                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15713                                 lastIdx = endContainer.childNodes.length - 1;
15714                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15715
15716                                 if (endContainer.nodeType == 3)
15717                                         endOffset = endContainer.nodeValue.length;
15718                         }
15719
15720                         // Exclude bookmark nodes if possible
15721                         if (isBookmarkNode(startContainer.parentNode))
15722                                 startContainer = startContainer.parentNode;
15723
15724                         if (isBookmarkNode(startContainer))
15725                                 startContainer = startContainer.nextSibling || startContainer;
15726
15727                         if (isBookmarkNode(endContainer.parentNode)) {
15728                                 endOffset = dom.nodeIndex(endContainer);
15729                                 endContainer = endContainer.parentNode;
15730                         }
15731
15732                         if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
15733                                 endContainer = endContainer.previousSibling;
15734                                 endOffset = endContainer.length;
15735                         }
15736
15737                         if (format[0].inline) {
15738                                 // Avoid applying formatting to a trailing space.
15739                                 leaf = findLeaf(endContainer, endOffset);
15740                                 if (leaf.node) {
15741                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
15742                                                 leaf = findLeaf(leaf.node.previousSibling);
15743
15744                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15745                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15746
15747                                                 if (leaf.offset > 1) {
15748                                                         endContainer = leaf.node;
15749                                                         endContainer.splitText(leaf.offset - 1);
15750                                                 } else if (leaf.node.previousSibling) {
15751                                                         endContainer = leaf.node.previousSibling;
15752                                                 }
15753                                         }
15754                                 }
15755                         }
15756                         
15757                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers
15758                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15759                         // This will reduce the number of wrapper elements that needs to be created
15760                         // Move start point up the tree
15761                         if (format[0].inline || format[0].block_expand) {
15762                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15763                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15764                         }
15765
15766                         // Expand start/end container to matching selector
15767                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15768                                 function findSelectorEndPoint(container, sibling_name) {
15769                                         var parents, i, y, curFormat;
15770
15771                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
15772                                                 container = container[sibling_name];
15773
15774                                         parents = getParents(container);
15775                                         for (i = 0; i < parents.length; i++) {
15776                                                 for (y = 0; y < format.length; y++) {
15777                                                         curFormat = format[y];
15778
15779                                                         // If collapsed state is set then skip formats that doesn't match that
15780                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
15781                                                                 continue;
15782
15783                                                         if (dom.is(parents[i], curFormat.selector))
15784                                                                 return parents[i];
15785                                                 }
15786                                         }
15787
15788                                         return container;
15789                                 };
15790
15791                                 // Find new startContainer/endContainer if there is better one
15792                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15793                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15794                         }
15795
15796                         // Expand start/end container to matching block element or text node
15797                         if (format[0].block || format[0].selector) {
15798                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {
15799                                         var node;
15800
15801                                         // Expand to block of similar type
15802                                         if (!format[0].wrapper)
15803                                                 node = dom.getParent(container, format[0].block);
15804
15805                                         // Expand to first wrappable block element or any block element
15806                                         if (!node)
15807                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
15808
15809                                         // Exclude inner lists from wrapping
15810                                         if (node && format[0].wrapper)
15811                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;
15812
15813                                         // Didn't find a block element look for first/last wrappable element
15814                                         if (!node) {
15815                                                 node = container;
15816
15817                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {
15818                                                         node = node[sibling_name];
15819
15820                                                         // Break on BR but include it will be removed later on
15821                                                         // we can't remove it now since we need to check if it can be wrapped
15822                                                         if (isEq(node, 'br'))
15823                                                                 break;
15824                                                 }
15825                                         }
15826
15827                                         return node || container;
15828                                 };
15829
15830                                 // Find new startContainer/endContainer if there is better one
15831                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15832                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15833
15834                                 // Non block element then try to expand up the leaf
15835                                 if (format[0].block) {
15836                                         if (!isBlock(startContainer))
15837                                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15838
15839                                         if (!isBlock(endContainer))
15840                                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15841                                 }
15842                         }
15843
15844                         // Setup index for startContainer
15845                         if (startContainer.nodeType == 1) {
15846                                 startOffset = nodeIndex(startContainer);
15847                                 startContainer = startContainer.parentNode;
15848                         }
15849
15850                         // Setup index for endContainer
15851                         if (endContainer.nodeType == 1) {
15852                                 endOffset = nodeIndex(endContainer) + 1;
15853                                 endContainer = endContainer.parentNode;
15854                         }
15855
15856                         // Return new range like object
15857                         return {
15858                                 startContainer : startContainer,
15859                                 startOffset : startOffset,
15860                                 endContainer : endContainer,
15861                                 endOffset : endOffset
15862                         };
15863                 }
15864
15865                 function removeFormat(format, vars, node, compare_node) {
15866                         var i, attrs, stylesModified;
15867
15868                         // Check if node matches format
15869                         if (!matchName(node, format))
15870                                 return FALSE;
15871
15872                         // Should we compare with format attribs and styles
15873                         if (format.remove != 'all') {
15874                                 // Remove styles
15875                                 each(format.styles, function(value, name) {
15876                                         value = replaceVars(value, vars);
15877
15878                                         // Indexed array
15879                                         if (typeof(name) === 'number') {
15880                                                 name = value;
15881                                                 compare_node = 0;
15882                                         }
15883
15884                                         if (!compare_node || isEq(getStyle(compare_node, name), value))
15885                                                 dom.setStyle(node, name, '');
15886
15887                                         stylesModified = 1;
15888                                 });
15889
15890                                 // Remove style attribute if it's empty
15891                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {
15892                                         node.removeAttribute('style');
15893                                         node.removeAttribute('data-mce-style');
15894                                 }
15895
15896                                 // Remove attributes
15897                                 each(format.attributes, function(value, name) {
15898                                         var valueOut;
15899
15900                                         value = replaceVars(value, vars);
15901
15902                                         // Indexed array
15903                                         if (typeof(name) === 'number') {
15904                                                 name = value;
15905                                                 compare_node = 0;
15906                                         }
15907
15908                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15909                                                 // Keep internal classes
15910                                                 if (name == 'class') {
15911                                                         value = dom.getAttrib(node, name);
15912                                                         if (value) {
15913                                                                 // Build new class value where everything is removed except the internal prefixed classes
15914                                                                 valueOut = '';
15915                                                                 each(value.split(/\s+/), function(cls) {
15916                                                                         if (/mce\w+/.test(cls))
15917                                                                                 valueOut += (valueOut ? ' ' : '') + cls;
15918                                                                 });
15919
15920                                                                 // We got some internal classes left
15921                                                                 if (valueOut) {
15922                                                                         dom.setAttrib(node, name, valueOut);
15923                                                                         return;
15924                                                                 }
15925                                                         }
15926                                                 }
15927
15928                                                 // IE6 has a bug where the attribute doesn't get removed correctly
15929                                                 if (name == "class")
15930                                                         node.removeAttribute('className');
15931
15932                                                 // Remove mce prefixed attributes
15933                                                 if (MCE_ATTR_RE.test(name))
15934                                                         node.removeAttribute('data-mce-' + name);
15935
15936                                                 node.removeAttribute(name);
15937                                         }
15938                                 });
15939
15940                                 // Remove classes
15941                                 each(format.classes, function(value) {
15942                                         value = replaceVars(value, vars);
15943
15944                                         if (!compare_node || dom.hasClass(compare_node, value))
15945                                                 dom.removeClass(node, value);
15946                                 });
15947
15948                                 // Check for non internal attributes
15949                                 attrs = dom.getAttribs(node);
15950                                 for (i = 0; i < attrs.length; i++) {
15951                                         if (attrs[i].nodeName.indexOf('_') !== 0)
15952                                                 return FALSE;
15953                                 }
15954                         }
15955
15956                         // Remove the inline child if it's empty for example <b> or <span>
15957                         if (format.remove != 'none') {
15958                                 removeNode(node, format);
15959                                 return TRUE;
15960                         }
15961                 };
15962
15963                 function removeNode(node, format) {
15964                         var parentNode = node.parentNode, rootBlockElm;
15965
15966                         if (format.block) {
15967                                 if (!forcedRootBlock) {
15968                                         function find(node, next, inc) {
15969                                                 node = getNonWhiteSpaceSibling(node, next, inc);
15970
15971                                                 return !node || (node.nodeName == 'BR' || isBlock(node));
15972                                         };
15973
15974                                         // Append BR elements if needed before we remove the block
15975                                         if (isBlock(node) && !isBlock(parentNode)) {
15976                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
15977                                                         node.insertBefore(dom.create('br'), node.firstChild);
15978
15979                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
15980                                                         node.appendChild(dom.create('br'));
15981                                         }
15982                                 } else {
15983                                         // Wrap the block in a forcedRootBlock if we are at the root of document
15984                                         if (parentNode == dom.getRoot()) {
15985                                                 if (!format.list_block || !isEq(node, format.list_block)) {
15986                                                         each(tinymce.grep(node.childNodes), function(node) {
15987                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15988                                                                         if (!rootBlockElm)
15989                                                                                 rootBlockElm = wrap(node, forcedRootBlock);
15990                                                                         else
15991                                                                                 rootBlockElm.appendChild(node);
15992                                                                 } else
15993                                                                         rootBlockElm = 0;
15994                                                         });
15995                                                 }
15996                                         }
15997                                 }
15998                         }
15999
16000                         // Never remove nodes that isn't the specified inline element if a selector is specified too
16001                         if (format.selector && format.inline && !isEq(format.inline, node))
16002                                 return;
16003
16004                         dom.remove(node, 1);
16005                 };
16006
16007                 function getNonWhiteSpaceSibling(node, next, inc) {
16008                         if (node) {
16009                                 next = next ? 'nextSibling' : 'previousSibling';
16010
16011                                 for (node = inc ? node : node[next]; node; node = node[next]) {
16012                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))
16013                                                 return node;
16014                                 }
16015                         }
16016                 };
16017
16018                 function isBookmarkNode(node) {
16019                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
16020                 };
16021
16022                 function mergeSiblings(prev, next) {
16023                         var marker, sibling, tmpSibling;
16024
16025                         function compareElements(node1, node2) {
16026                                 // Not the same name
16027                                 if (node1.nodeName != node2.nodeName)
16028                                         return FALSE;
16029
16030                                 function getAttribs(node) {
16031                                         var attribs = {};
16032
16033                                         each(dom.getAttribs(node), function(attr) {
16034                                                 var name = attr.nodeName.toLowerCase();
16035
16036                                                 // Don't compare internal attributes or style
16037                                                 if (name.indexOf('_') !== 0 && name !== 'style')
16038                                                         attribs[name] = dom.getAttrib(node, name);
16039                                         });
16040
16041                                         return attribs;
16042                                 };
16043
16044                                 function compareObjects(obj1, obj2) {
16045                                         var value, name;
16046
16047                                         for (name in obj1) {
16048                                                 // Obj1 has item obj2 doesn't have
16049                                                 if (obj1.hasOwnProperty(name)) {
16050                                                         value = obj2[name];
16051
16052                                                         // Obj2 doesn't have obj1 item
16053                                                         if (value === undefined)
16054                                                                 return FALSE;
16055
16056                                                         // Obj2 item has a different value
16057                                                         if (obj1[name] != value)
16058                                                                 return FALSE;
16059
16060                                                         // Delete similar value
16061                                                         delete obj2[name];
16062                                                 }
16063                                         }
16064
16065                                         // Check if obj 2 has something obj 1 doesn't have
16066                                         for (name in obj2) {
16067                                                 // Obj2 has item obj1 doesn't have
16068                                                 if (obj2.hasOwnProperty(name))
16069                                                         return FALSE;
16070                                         }
16071
16072                                         return TRUE;
16073                                 };
16074
16075                                 // Attribs are not the same
16076                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
16077                                         return FALSE;
16078
16079                                 // Styles are not the same
16080                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
16081                                         return FALSE;
16082
16083                                 return TRUE;
16084                         };
16085
16086                         // Check if next/prev exists and that they are elements
16087                         if (prev && next) {
16088                                 function findElementSibling(node, sibling_name) {
16089                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {
16090                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
16091                                                         return node;
16092
16093                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
16094                                                         return sibling;
16095                                         }
16096
16097                                         return node;
16098                                 };
16099
16100                                 // If previous sibling is empty then jump over it
16101                                 prev = findElementSibling(prev, 'previousSibling');
16102                                 next = findElementSibling(next, 'nextSibling');
16103
16104                                 // Compare next and previous nodes
16105                                 if (compareElements(prev, next)) {
16106                                         // Append nodes between
16107                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {
16108                                                 tmpSibling = sibling;
16109                                                 sibling = sibling.nextSibling;
16110                                                 prev.appendChild(tmpSibling);
16111                                         }
16112
16113                                         // Remove next node
16114                                         dom.remove(next);
16115
16116                                         // Move children into prev node
16117                                         each(tinymce.grep(next.childNodes), function(node) {
16118                                                 prev.appendChild(node);
16119                                         });
16120
16121                                         return prev;
16122                                 }
16123                         }
16124
16125                         return next;
16126                 };
16127
16128                 function isTextBlock(name) {
16129                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
16130                 };
16131
16132                 function getContainer(rng, start) {
16133                         var container, offset, lastIdx;
16134
16135                         container = rng[start ? 'startContainer' : 'endContainer'];
16136                         offset = rng[start ? 'startOffset' : 'endOffset'];
16137
16138                         if (container.nodeType == 1) {
16139                                 lastIdx = container.childNodes.length - 1;
16140
16141                                 if (!start && offset)
16142                                         offset--;
16143
16144                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
16145                         }
16146
16147                         return container;
16148                 };
16149
16150                 function performCaretAction(type, name, vars) {
16151                         var i, currentPendingFormats = pendingFormats[type],
16152                                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
16153
16154                         function hasPending() {
16155                                 return pendingFormats.apply.length || pendingFormats.remove.length;
16156                         };
16157
16158                         function resetPending() {
16159                                 pendingFormats.apply = [];
16160                                 pendingFormats.remove = [];
16161                         };
16162
16163                         function perform(caret_node) {
16164                                 // Apply pending formats
16165                                 each(pendingFormats.apply.reverse(), function(item) {
16166                                         apply(item.name, item.vars, caret_node);
16167
16168                                         // Colored nodes should be underlined so that the color of the underline matches the text color.
16169                                         if (item.name === 'forecolor' && item.vars.value)
16170                                                 processUnderlineAndColor(caret_node.parentNode);
16171                                 });
16172
16173                                 // Remove pending formats
16174                                 each(pendingFormats.remove.reverse(), function(item) {
16175                                         remove(item.name, item.vars, caret_node);
16176                                 });
16177
16178                                 dom.remove(caret_node, 1);
16179                                 resetPending();
16180                         };
16181
16182                         // Check if it already exists then ignore it
16183                         for (i = currentPendingFormats.length - 1; i >= 0; i--) {
16184                                 if (currentPendingFormats[i].name == name)
16185                                         return;
16186                         }
16187
16188                         currentPendingFormats.push({name : name, vars : vars});
16189
16190                         // Check if it's in the other type, then remove it
16191                         for (i = otherPendingFormats.length - 1; i >= 0; i--) {
16192                                 if (otherPendingFormats[i].name == name)
16193                                         otherPendingFormats.splice(i, 1);
16194                         }
16195
16196                         // Pending apply or remove formats
16197                         if (hasPending()) {
16198                                 ed.getDoc().execCommand('FontName', false, 'mceinline');
16199                                 pendingFormats.lastRng = selection.getRng();
16200
16201                                 // IE will convert the current word
16202                                 each(dom.select('font,span'), function(node) {
16203                                         var bookmark;
16204
16205                                         if (isCaretNode(node)) {
16206                                                 bookmark = selection.getBookmark();
16207                                                 perform(node);
16208                                                 selection.moveToBookmark(bookmark);
16209                                                 ed.nodeChanged();
16210                                         }
16211                                 });
16212
16213                                 // Only register listeners once if we need to
16214                                 if (!pendingFormats.isListening && hasPending()) {
16215                                         pendingFormats.isListening = true;
16216                                         function performPendingFormat(node, textNode) {
16217                                                 var rng = dom.createRng();
16218                                                 perform(node);
16219
16220                                                 rng.setStart(textNode, textNode.nodeValue.length);
16221                                                 rng.setEnd(textNode, textNode.nodeValue.length);
16222                                                 selection.setRng(rng);
16223                                                 ed.nodeChanged();
16224                                         }
16225                                         var enterKeyPressed = false;
16226
16227                                         each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
16228                                                 ed[event].addToTop(function(ed, e) {
16229                                                         if (e.keyCode==13 && !e.shiftKey) {
16230                                                                 enterKeyPressed = true;
16231                                                                 return;
16232                                                         }
16233                                                         // Do we have pending formats and is the selection moved has moved
16234                                                         if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
16235                                                                 var foundCaret = false;
16236                                                                 each(dom.select('font,span'), function(node) {
16237                                                                         var textNode, rng;
16238
16239                                                                         // Look for marker
16240                                                                         if (isCaretNode(node)) {
16241                                                                                 foundCaret = true;
16242                                                                                 textNode = node.firstChild;
16243
16244                                                                                 // Find the first text node within node
16245                                                                                 while (textNode && textNode.nodeType != 3)
16246                                                                                         textNode = textNode.firstChild;
16247
16248                                                                                 if (textNode) 
16249                                                                                         performPendingFormat(node, textNode);
16250                                                                                 else
16251                                                                                         dom.remove(node);
16252                                                                         }
16253                                                                 });
16254                                                                 
16255                                                                 // no caret - so we are 
16256                                                                 if (enterKeyPressed && !foundCaret) {
16257                                                                         var node = selection.getNode();
16258                                                                         var textNode = node;
16259
16260                                                                         // Find the first text node within node
16261                                                                         while (textNode && textNode.nodeType != 3)
16262                                                                                 textNode = textNode.firstChild;
16263                                                                         if (textNode) {
16264                                                                                 node=textNode.parentNode;
16265                                                                                 while (!isBlock(node)){
16266                                                                                         node=node.parentNode;
16267                                                                                 }
16268                                                                                 performPendingFormat(node, textNode);
16269                                                                         }
16270                                                                 }
16271
16272                                                                 // Always unbind and clear pending styles on keyup
16273                                                                 if (e.type == 'keyup' || e.type == 'mouseup') {
16274                                                                         resetPending();
16275                                                                         enterKeyPressed=false;
16276                                                                 }
16277                                                         }
16278                                                 });
16279                                         });
16280                                 }
16281                         }
16282                 };
16283         };
16284 })(tinymce);
16285
16286 tinymce.onAddEditor.add(function(tinymce, ed) {
16287         var filters, fontSizes, dom, settings = ed.settings;
16288
16289         if (settings.inline_styles) {
16290                 fontSizes = tinymce.explode(settings.font_size_style_values);
16291
16292                 function replaceWithSpan(node, styles) {
16293                         tinymce.each(styles, function(value, name) {
16294                                 if (value)
16295                                         dom.setStyle(node, name, value);
16296                         });
16297
16298                         dom.rename(node, 'span');
16299                 };
16300
16301                 filters = {
16302                         font : function(dom, node) {
16303                                 replaceWithSpan(node, {
16304                                         backgroundColor : node.style.backgroundColor,
16305                                         color : node.color,
16306                                         fontFamily : node.face,
16307                                         fontSize : fontSizes[parseInt(node.size) - 1]
16308                                 });
16309                         },
16310
16311                         u : function(dom, node) {
16312                                 replaceWithSpan(node, {
16313                                         textDecoration : 'underline'
16314                                 });
16315                         },
16316
16317                         strike : function(dom, node) {
16318                                 replaceWithSpan(node, {
16319                                         textDecoration : 'line-through'
16320                                 });
16321                         }
16322                 };
16323
16324                 function convert(editor, params) {
16325                         dom = editor.dom;
16326
16327                         if (settings.convert_fonts_to_spans) {
16328                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
16329                                         filters[node.nodeName.toLowerCase()](ed.dom, node);
16330                                 });
16331                         }
16332                 };
16333
16334                 ed.onPreProcess.add(convert);
16335                 ed.onSetContent.add(convert);
16336
16337                 ed.onInit.add(function() {
16338                         ed.selection.onSetContent.add(convert);
16339                 });
16340         }
16341 });
16342