]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/tiny_mce_jquery_src.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / tiny_mce / tiny_mce_jquery_src.js
1 (function(win) {
2         var whiteSpaceRe = /^\s*|\s*$/g,
3                 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
4
5         var tinymce = {
6                 majorVersion : '3',
7
8                 minorVersion : '4.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                 trim : function(s) {
161                         return (s ? '' + s : '').replace(whiteSpaceRe, '');
162                 },
163
164                 create : function(s, p, root) {
165                         var t = this, sp, ns, cn, scn, c, de = 0;
166
167                         // Parse : <prefix> <class>:<super class>
168                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
169                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
170
171                         // Create namespace for new class
172                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
173
174                         // Class already exists
175                         if (ns[cn])
176                                 return;
177
178                         // Make pure static class
179                         if (s[2] == 'static') {
180                                 ns[cn] = p;
181
182                                 if (this.onCreate)
183                                         this.onCreate(s[2], s[3], ns[cn]);
184
185                                 return;
186                         }
187
188                         // Create default constructor
189                         if (!p[cn]) {
190                                 p[cn] = function() {};
191                                 de = 1;
192                         }
193
194                         // Add constructor and methods
195                         ns[cn] = p[cn];
196                         t.extend(ns[cn].prototype, p);
197
198                         // Extend
199                         if (s[5]) {
200                                 sp = t.resolve(s[5]).prototype;
201                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
202
203                                 // Extend constructor
204                                 c = ns[cn];
205                                 if (de) {
206                                         // Add passthrough constructor
207                                         ns[cn] = function() {
208                                                 return sp[scn].apply(this, arguments);
209                                         };
210                                 } else {
211                                         // Add inherit constructor
212                                         ns[cn] = function() {
213                                                 this.parent = sp[scn];
214                                                 return c.apply(this, arguments);
215                                         };
216                                 }
217                                 ns[cn].prototype[cn] = ns[cn];
218
219                                 // Add super methods
220                                 t.each(sp, function(f, n) {
221                                         ns[cn].prototype[n] = sp[n];
222                                 });
223
224                                 // Add overridden methods
225                                 t.each(p, function(f, n) {
226                                         // Extend methods if needed
227                                         if (sp[n]) {
228                                                 ns[cn].prototype[n] = function() {
229                                                         this.parent = sp[n];
230                                                         return f.apply(this, arguments);
231                                                 };
232                                         } else {
233                                                 if (n != cn)
234                                                         ns[cn].prototype[n] = f;
235                                         }
236                                 });
237                         }
238
239                         // Add static methods
240                         t.each(p['static'], function(f, n) {
241                                 ns[cn][n] = f;
242                         });
243
244                         if (this.onCreate)
245                                 this.onCreate(s[2], s[3], ns[cn].prototype);
246                 },
247
248                 walk : function(o, f, n, s) {
249                         s = s || this;
250
251                         if (o) {
252                                 if (n)
253                                         o = o[n];
254
255                                 tinymce.each(o, function(o, i) {
256                                         if (f.call(s, o, i, n) === false)
257                                                 return false;
258
259                                         tinymce.walk(o, f, n, s);
260                                 });
261                         }
262                 },
263
264                 createNS : function(n, o) {
265                         var i, v;
266
267                         o = o || win;
268
269                         n = n.split('.');
270                         for (i=0; i<n.length; i++) {
271                                 v = n[i];
272
273                                 if (!o[v])
274                                         o[v] = {};
275
276                                 o = o[v];
277                         }
278
279                         return o;
280                 },
281
282                 resolve : function(n, o) {
283                         var i, l;
284
285                         o = o || win;
286
287                         n = n.split('.');
288                         for (i = 0, l = n.length; i < l; i++) {
289                                 o = o[n[i]];
290
291                                 if (!o)
292                                         break;
293                         }
294
295                         return o;
296                 },
297
298                 addUnload : function(f, s) {
299                         var t = this;
300
301                         f = {func : f, scope : s || this};
302
303                         if (!t.unloads) {
304                                 function unload() {
305                                         var li = t.unloads, o, n;
306
307                                         if (li) {
308                                                 // Call unload handlers
309                                                 for (n in li) {
310                                                         o = li[n];
311
312                                                         if (o && o.func)
313                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
314                                                 }
315
316                                                 // Detach unload function
317                                                 if (win.detachEvent) {
318                                                         win.detachEvent('onbeforeunload', fakeUnload);
319                                                         win.detachEvent('onunload', unload);
320                                                 } else if (win.removeEventListener)
321                                                         win.removeEventListener('unload', unload, false);
322
323                                                 // Destroy references
324                                                 t.unloads = o = li = w = unload = 0;
325
326                                                 // Run garbarge collector on IE
327                                                 if (win.CollectGarbage)
328                                                         CollectGarbage();
329                                         }
330                                 };
331
332                                 function fakeUnload() {
333                                         var d = document;
334
335                                         // Is there things still loading, then do some magic
336                                         if (d.readyState == 'interactive') {
337                                                 function stop() {
338                                                         // Prevent memory leak
339                                                         d.detachEvent('onstop', stop);
340
341                                                         // Call unload handler
342                                                         if (unload)
343                                                                 unload();
344
345                                                         d = 0;
346                                                 };
347
348                                                 // Fire unload when the currently loading page is stopped
349                                                 if (d)
350                                                         d.attachEvent('onstop', stop);
351
352                                                 // Remove onstop listener after a while to prevent the unload function
353                                                 // to execute if the user presses cancel in an onbeforeunload
354                                                 // confirm dialog and then presses the browser stop button
355                                                 win.setTimeout(function() {
356                                                         if (d)
357                                                                 d.detachEvent('onstop', stop);
358                                                 }, 0);
359                                         }
360                                 };
361
362                                 // Attach unload handler
363                                 if (win.attachEvent) {
364                                         win.attachEvent('onunload', unload);
365                                         win.attachEvent('onbeforeunload', fakeUnload);
366                                 } else if (win.addEventListener)
367                                         win.addEventListener('unload', unload, false);
368
369                                 // Setup initial unload handler array
370                                 t.unloads = [f];
371                         } else
372                                 t.unloads.push(f);
373
374                         return f;
375                 },
376
377                 removeUnload : function(f) {
378                         var u = this.unloads, r = null;
379
380                         tinymce.each(u, function(o, i) {
381                                 if (o && o.func == f) {
382                                         u.splice(i, 1);
383                                         r = f;
384                                         return false;
385                                 }
386                         });
387
388                         return r;
389                 },
390
391                 explode : function(s, d) {
392                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
393                 },
394
395                 _addVer : function(u) {
396                         var v;
397
398                         if (!this.query)
399                                 return u;
400
401                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
402
403                         if (u.indexOf('#') == -1)
404                                 return u + v;
405
406                         return u.replace('#', v + '#');
407                 },
408
409                 // Fix function for IE 9 where regexps isn't working correctly
410                 // Todo: remove me once MS fixes the bug
411                 _replace : function(find, replace, str) {
412                         // On IE9 we have to fake $x replacement
413                         if (isRegExpBroken) {
414                                 return str.replace(find, function() {
415                                         var val = replace, args = arguments, i;
416
417                                         for (i = 0; i < args.length - 2; i++) {
418                                                 if (args[i] === undefined) {
419                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');
420                                                 } else {
421                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
422                                                 }
423                                         }
424
425                                         return val;
426                                 });
427                         }
428
429                         return str.replace(find, replace);
430                 }
431
432                 };
433
434         // Initialize the API
435         tinymce._init();
436
437         // Expose tinymce namespace to the global namespace (window)
438         win.tinymce = win.tinyMCE = tinymce;
439
440         // Describe the different namespaces
441
442         })(window);
443
444
445 (function($, tinymce) {
446         var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined;
447
448         // jQuery is undefined
449         if (!$ && window.console) {
450                 return console.log("Load jQuery first!");
451         }
452
453         // Stick jQuery into the tinymce namespace
454         tinymce.$ = $;
455
456         // Setup adapter
457         tinymce.adapter = {
458                 patchEditor : function(editor) {
459                         var fn = $.fn;
460
461                         // Adapt the css function to make sure that the data-mce-style
462                         // attribute gets updated with the new style information
463                         function css(name, value) {
464                                 var self = this;
465
466                                 // Remove data-mce-style when set operation occurs
467                                 if (value)
468                                         self.removeAttr('data-mce-style');
469
470                                 return fn.css.apply(self, arguments);
471                         };
472
473                         // Apapt the attr function to make sure that it uses the data-mce- prefixed variants
474                         function attr(name, value) {
475                                 var self = this;
476
477                                 // Update/retrive data-mce- attribute variants
478                                 if (attrRegExp.test(name)) {
479                                         if (value !== undefined) {
480                                                 // Use TinyMCE behavior when setting the specifc attributes
481                                                 self.each(function(i, node) {
482                                                         editor.dom.setAttrib(node, name, value);
483                                                 });
484
485                                                 return self;
486                                         } else
487                                                 return self.attr('data-mce-' + name);
488                                 }
489
490                                 // Default behavior
491                                 return fn.attr.apply(self, arguments);
492                         };
493
494                         function htmlPatchFunc(func) {
495                                 // Returns a modified function that processes
496                                 // the HTML before executing the action this makes sure
497                                 // that href/src etc gets moved into the data-mce- variants
498                                 return function(content) {
499                                         if (content)
500                                                 content = editor.dom.processHTML(content);
501
502                                         return func.call(this, content);
503                                 };
504                         };
505
506                         // Patch various jQuery functions to handle tinymce specific attribute and content behavior
507                         // we don't patch the jQuery.fn directly since it will most likely break compatibility
508                         // with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
509                         function patch(jq) {
510                                 // Patch some functions, only patch the object once
511                                 if (jq.css !== css) {
512                                         // Patch css/attr to use the data-mce- prefixed attribute variants
513                                         jq.css = css;
514                                         jq.attr = attr;
515
516                                         // Patch HTML functions to use the DOMUtils.processHTML filter logic
517                                         jq.html = htmlPatchFunc(fn.html);
518                                         jq.append = htmlPatchFunc(fn.append);
519                                         jq.prepend = htmlPatchFunc(fn.prepend);
520                                         jq.after = htmlPatchFunc(fn.after);
521                                         jq.before = htmlPatchFunc(fn.before);
522                                         jq.replaceWith = htmlPatchFunc(fn.replaceWith);
523                                         jq.tinymce = editor;
524
525                                         // Each pushed jQuery instance needs to be patched
526                                         // as well for example when traversing the DOM
527                                         jq.pushStack = function() {
528                                                 return patch(fn.pushStack.apply(this, arguments));
529                                         };
530                                 }
531
532                                 return jq;
533                         };
534
535                         // Add a $ function on each editor instance this one is scoped for the editor document object
536                         // this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
537                         editor.$ = function(selector, scope) {
538                                 var doc = editor.getDoc();
539
540                                 return patch($(selector || doc, doc || scope));
541                         };
542                 }
543         };
544
545         // Patch in core NS functions
546         tinymce.extend = $.extend;
547         tinymce.extend(tinymce, {
548                 map : $.map,
549                 grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
550                 inArray : function(a, v) {return $.inArray(v, a || []);}
551
552                 /* Didn't iterate stylesheets
553                 each : function(o, cb, s) {
554                         if (!o)
555                                 return 0;
556
557                         var r = 1;
558
559                         $.each(o, function(nr, el){
560                                 if (cb.call(s, el, nr, o) === false) {
561                                         r = 0;
562                                         return false;
563                                 }
564                         });
565
566                         return r;
567                 }*/
568         });
569
570         // Patch in functions in various clases
571         // Add a "#ifndefjquery" statement around each core API function you add below
572         var patches = {
573                 'tinymce.dom.DOMUtils' : {
574                         /*
575                         addClass : function(e, c) {
576                                 if (is(e, 'array') && is(e[0], 'string'))
577                                         e = e.join(',#');
578                                 return (e && $(is(e, 'string') ? '#' + e : e)
579                                         .addClass(c)
580                                         .attr('class')) || false;
581                         },
582
583                         hasClass : function(n, c) {
584                                 return $(is(n, 'string') ? '#' + n : n).hasClass(c);
585                         },
586
587                         removeClass : function(e, c) {
588                                 if (!e)
589                                         return false;
590
591                                 var r = [];
592
593                                 $(is(e, 'string') ? '#' + e : e)
594                                         .removeClass(c)
595                                         .each(function(){
596                                                 r.push(this.className);
597                                         });
598
599                                 return r.length == 1 ? r[0] : r;
600                         },
601                         */
602
603                         select : function(pattern, scope) {
604                                 var t = this;
605
606                                 return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
607                         },
608
609                         is : function(n, patt) {
610                                 return $(this.get(n)).is(patt);
611                         }
612
613                         /*
614                         show : function(e) {
615                                 if (is(e, 'array') && is(e[0], 'string'))
616                                         e = e.join(',#');
617
618                                 $(is(e, 'string') ? '#' + e : e).css('display', 'block');
619                         },
620
621                         hide : function(e) {
622                                 if (is(e, 'array') && is(e[0], 'string'))
623                                         e = e.join(',#');
624
625                                 $(is(e, 'string') ? '#' + e : e).css('display', 'none');
626                         },
627
628                         isHidden : function(e) {
629                                 return $(is(e, 'string') ? '#' + e : e).is(':hidden');
630                         },
631
632                         insertAfter : function(n, e) {
633                                 return $(is(e, 'string') ? '#' + e : e).after(n);
634                         },
635
636                         replace : function(o, n, k) {
637                                 n = $(is(n, 'string') ? '#' + n : n);
638
639                                 if (k)
640                                         n.children().appendTo(o);
641
642                                 n.replaceWith(o);
643                         },
644
645                         setStyle : function(n, na, v) {
646                                 if (is(n, 'array') && is(n[0], 'string'))
647                                         n = n.join(',#');
648
649                                 $(is(n, 'string') ? '#' + n : n).css(na, v);
650                         },
651
652                         getStyle : function(n, na, c) {
653                                 return $(is(n, 'string') ? '#' + n : n).css(na);
654                         },
655
656                         setStyles : function(e, o) {
657                                 if (is(e, 'array') && is(e[0], 'string'))
658                                         e = e.join(',#');
659                                 $(is(e, 'string') ? '#' + e : e).css(o);
660                         },
661
662                         setAttrib : function(e, n, v) {
663                                 var t = this, s = t.settings;
664
665                                 if (is(e, 'array') && is(e[0], 'string'))
666                                         e = e.join(',#');
667
668                                 e = $(is(e, 'string') ? '#' + e : e);
669
670                                 switch (n) {
671                                         case "style":
672                                                 e.each(function(i, v){
673                                                         if (s.keep_values)
674                                                                 $(v).attr('data-mce-style', v);
675
676                                                         v.style.cssText = v;
677                                                 });
678                                                 break;
679
680                                         case "class":
681                                                 e.each(function(){
682                                                         this.className = v;
683                                                 });
684                                                 break;
685
686                                         case "src":
687                                         case "href":
688                                                 e.each(function(i, v){
689                                                         if (s.keep_values) {
690                                                                 if (s.url_converter)
691                                                                         v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
692
693                                                                 t.setAttrib(v, 'data-mce-' + n, v);
694                                                         }
695                                                 });
696
697                                                 break;
698                                 }
699
700                                 if (v !== null && v.length !== 0)
701                                         e.attr(n, '' + v);
702                                 else
703                                         e.removeAttr(n);
704                         },
705
706                         setAttribs : function(e, o) {
707                                 var t = this;
708
709                                 $.each(o, function(n, v){
710                                         t.setAttrib(e,n,v);
711                                 });
712                         }
713                         */
714                 }
715
716 /*
717                 'tinymce.dom.Event' : {
718                         add : function (o, n, f, s) {
719                                 var lo, cb;
720
721                                 cb = function(e) {
722                                         e.target = e.target || this;
723                                         f.call(s || this, e);
724                                 };
725
726                                 if (is(o, 'array') && is(o[0], 'string'))
727                                         o = o.join(',#');
728                                 o = $(is(o, 'string') ? '#' + o : o);
729                                 if (n == 'init') {
730                                         o.ready(cb, s);
731                                 } else {
732                                         if (s) {
733                                                 o.bind(n, s, cb);
734                                         } else {
735                                                 o.bind(n, cb);
736                                         }
737                                 }
738
739                                 lo = this._jqLookup || (this._jqLookup = []);
740                                 lo.push({func : f, cfunc : cb});
741
742                                 return cb;
743                         },
744
745                         remove : function(o, n, f) {
746                                 // Find cfunc
747                                 $(this._jqLookup).each(function() {
748                                         if (this.func === f)
749                                                 f = this.cfunc;
750                                 });
751
752                                 if (is(o, 'array') && is(o[0], 'string'))
753                                         o = o.join(',#');
754
755                                 $(is(o, 'string') ? '#' + o : o).unbind(n,f);
756
757                                 return true;
758                         }
759                 }
760 */
761         };
762
763         // Patch functions after a class is created
764         tinymce.onCreate = function(ty, c, p) {
765                 tinymce.extend(p, patches[c]);
766         };
767 })(window.jQuery, tinymce);
768
769
770
771 tinymce.create('tinymce.util.Dispatcher', {
772         scope : null,
773         listeners : null,
774
775         Dispatcher : function(s) {
776                 this.scope = s || this;
777                 this.listeners = [];
778         },
779
780         add : function(cb, s) {
781                 this.listeners.push({cb : cb, scope : s || this.scope});
782
783                 return cb;
784         },
785
786         addToTop : function(cb, s) {
787                 this.listeners.unshift({cb : cb, scope : s || this.scope});
788
789                 return cb;
790         },
791
792         remove : function(cb) {
793                 var l = this.listeners, o = null;
794
795                 tinymce.each(l, function(c, i) {
796                         if (cb == c.cb) {
797                                 o = cb;
798                                 l.splice(i, 1);
799                                 return false;
800                         }
801                 });
802
803                 return o;
804         },
805
806         dispatch : function() {
807                 var s, a = arguments, i, li = this.listeners, c;
808
809                 // Needs to be a real loop since the listener count might change while looping
810                 // And this is also more efficient
811                 for (i = 0; i<li.length; i++) {
812                         c = li[i];
813                         s = c.cb.apply(c.scope, a);
814
815                         if (s === false)
816                                 break;
817                 }
818
819                 return s;
820         }
821
822         });
823
824 (function() {
825         var each = tinymce.each;
826
827         tinymce.create('tinymce.util.URI', {
828                 URI : function(u, s) {
829                         var t = this, o, a, b, base_url;
830
831                         // Trim whitespace
832                         u = tinymce.trim(u);
833
834                         // Default settings
835                         s = t.settings = s || {};
836
837                         // Strange app protocol or local anchor
838                         if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
839                                 t.source = u;
840                                 return;
841                         }
842
843                         // Absolute path with no host, fake host and protocol
844                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
845                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
846
847                         // Relative path http:// or protocol relative //path
848                         if (!/^[\w-]*:?\/\//.test(u)) {
849                                 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
850                                 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
851                         }
852
853                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
854                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
855                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
856                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
857                                 var s = u[i];
858
859                                 // Zope 3 workaround, they use @@something
860                                 if (s)
861                                         s = s.replace(/\(mce_at\)/g, '@@');
862
863                                 t[v] = s;
864                         });
865
866                         if (b = s.base_uri) {
867                                 if (!t.protocol)
868                                         t.protocol = b.protocol;
869
870                                 if (!t.userInfo)
871                                         t.userInfo = b.userInfo;
872
873                                 if (!t.port && t.host == 'mce_host')
874                                         t.port = b.port;
875
876                                 if (!t.host || t.host == 'mce_host')
877                                         t.host = b.host;
878
879                                 t.source = '';
880                         }
881
882                         //t.path = t.path || '/';
883                 },
884
885                 setPath : function(p) {
886                         var t = this;
887
888                         p = /^(.*?)\/?(\w+)?$/.exec(p);
889
890                         // Update path parts
891                         t.path = p[0];
892                         t.directory = p[1];
893                         t.file = p[2];
894
895                         // Rebuild source
896                         t.source = '';
897                         t.getURI();
898                 },
899
900                 toRelative : function(u) {
901                         var t = this, o;
902
903                         if (u === "./")
904                                 return u;
905
906                         u = new tinymce.util.URI(u, {base_uri : t});
907
908                         // Not on same domain/port or protocol
909                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
910                                 return u.getURI();
911
912                         o = t.toRelPath(t.path, u.path);
913
914                         // Add query
915                         if (u.query)
916                                 o += '?' + u.query;
917
918                         // Add anchor
919                         if (u.anchor)
920                                 o += '#' + u.anchor;
921
922                         return o;
923                 },
924         
925                 toAbsolute : function(u, nh) {
926                         var u = new tinymce.util.URI(u, {base_uri : this});
927
928                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
929                 },
930
931                 toRelPath : function(base, path) {
932                         var items, bp = 0, out = '', i, l;
933
934                         // Split the paths
935                         base = base.substring(0, base.lastIndexOf('/'));
936                         base = base.split('/');
937                         items = path.split('/');
938
939                         if (base.length >= items.length) {
940                                 for (i = 0, l = base.length; i < l; i++) {
941                                         if (i >= items.length || base[i] != items[i]) {
942                                                 bp = i + 1;
943                                                 break;
944                                         }
945                                 }
946                         }
947
948                         if (base.length < items.length) {
949                                 for (i = 0, l = items.length; i < l; i++) {
950                                         if (i >= base.length || base[i] != items[i]) {
951                                                 bp = i + 1;
952                                                 break;
953                                         }
954                                 }
955                         }
956
957                         if (bp == 1)
958                                 return path;
959
960                         for (i = 0, l = base.length - (bp - 1); i < l; i++)
961                                 out += "../";
962
963                         for (i = bp - 1, l = items.length; i < l; i++) {
964                                 if (i != bp - 1)
965                                         out += "/" + items[i];
966                                 else
967                                         out += items[i];
968                         }
969
970                         return out;
971                 },
972
973                 toAbsPath : function(base, path) {
974                         var i, nb = 0, o = [], tr, outPath;
975
976                         // Split paths
977                         tr = /\/$/.test(path) ? '/' : '';
978                         base = base.split('/');
979                         path = path.split('/');
980
981                         // Remove empty chunks
982                         each(base, function(k) {
983                                 if (k)
984                                         o.push(k);
985                         });
986
987                         base = o;
988
989                         // Merge relURLParts chunks
990                         for (i = path.length - 1, o = []; i >= 0; i--) {
991                                 // Ignore empty or .
992                                 if (path[i].length == 0 || path[i] == ".")
993                                         continue;
994
995                                 // Is parent
996                                 if (path[i] == '..') {
997                                         nb++;
998                                         continue;
999                                 }
1000
1001                                 // Move up
1002                                 if (nb > 0) {
1003                                         nb--;
1004                                         continue;
1005                                 }
1006
1007                                 o.push(path[i]);
1008                         }
1009
1010                         i = base.length - nb;
1011
1012                         // If /a/b/c or /
1013                         if (i <= 0)
1014                                 outPath = o.reverse().join('/');
1015                         else
1016                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
1017
1018                         // Add front / if it's needed
1019                         if (outPath.indexOf('/') !== 0)
1020                                 outPath = '/' + outPath;
1021
1022                         // Add traling / if it's needed
1023                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
1024                                 outPath += tr;
1025
1026                         return outPath;
1027                 },
1028
1029                 getURI : function(nh) {
1030                         var s, t = this;
1031
1032                         // Rebuild source
1033                         if (!t.source || nh) {
1034                                 s = '';
1035
1036                                 if (!nh) {
1037                                         if (t.protocol)
1038                                                 s += t.protocol + '://';
1039
1040                                         if (t.userInfo)
1041                                                 s += t.userInfo + '@';
1042
1043                                         if (t.host)
1044                                                 s += t.host;
1045
1046                                         if (t.port)
1047                                                 s += ':' + t.port;
1048                                 }
1049
1050                                 if (t.path)
1051                                         s += t.path;
1052
1053                                 if (t.query)
1054                                         s += '?' + t.query;
1055
1056                                 if (t.anchor)
1057                                         s += '#' + t.anchor;
1058
1059                                 t.source = s;
1060                         }
1061
1062                         return t.source;
1063                 }
1064         });
1065 })();
1066
1067 (function() {
1068         var each = tinymce.each;
1069
1070         tinymce.create('static tinymce.util.Cookie', {
1071                 getHash : function(n) {
1072                         var v = this.get(n), h;
1073
1074                         if (v) {
1075                                 each(v.split('&'), function(v) {
1076                                         v = v.split('=');
1077                                         h = h || {};
1078                                         h[unescape(v[0])] = unescape(v[1]);
1079                                 });
1080                         }
1081
1082                         return h;
1083                 },
1084
1085                 setHash : function(n, v, e, p, d, s) {
1086                         var o = '';
1087
1088                         each(v, function(v, k) {
1089                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
1090                         });
1091
1092                         this.set(n, o, e, p, d, s);
1093                 },
1094
1095                 get : function(n) {
1096                         var c = document.cookie, e, p = n + "=", b;
1097
1098                         // Strict mode
1099                         if (!c)
1100                                 return;
1101
1102                         b = c.indexOf("; " + p);
1103
1104                         if (b == -1) {
1105                                 b = c.indexOf(p);
1106
1107                                 if (b != 0)
1108                                         return null;
1109                         } else
1110                                 b += 2;
1111
1112                         e = c.indexOf(";", b);
1113
1114                         if (e == -1)
1115                                 e = c.length;
1116
1117                         return unescape(c.substring(b + p.length, e));
1118                 },
1119
1120                 set : function(n, v, e, p, d, s) {
1121                         document.cookie = n + "=" + escape(v) +
1122                                 ((e) ? "; expires=" + e.toGMTString() : "") +
1123                                 ((p) ? "; path=" + escape(p) : "") +
1124                                 ((d) ? "; domain=" + d : "") +
1125                                 ((s) ? "; secure" : "");
1126                 },
1127
1128                 remove : function(n, p) {
1129                         var d = new Date();
1130
1131                         d.setTime(d.getTime() - 1000);
1132
1133                         this.set(n, '', d, p, d);
1134                 }
1135         });
1136 })();
1137
1138 (function() {
1139         function serialize(o, quote) {
1140                 var i, v, t;
1141
1142                 quote = quote || '"';
1143
1144                 if (o == null)
1145                         return 'null';
1146
1147                 t = typeof o;
1148
1149                 if (t == 'string') {
1150                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
1151
1152                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
1153                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
1154                                 if (quote === '"' && a === "'")
1155                                         return a;
1156
1157                                 i = v.indexOf(b);
1158
1159                                 if (i + 1)
1160                                         return '\\' + v.charAt(i + 1);
1161
1162                                 a = b.charCodeAt().toString(16);
1163
1164                                 return '\\u' + '0000'.substring(a.length) + a;
1165                         }) + quote;
1166                 }
1167
1168                 if (t == 'object') {
1169                         if (o.hasOwnProperty && o instanceof Array) {
1170                                         for (i=0, v = '['; i<o.length; i++)
1171                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
1172
1173                                         return v + ']';
1174                                 }
1175
1176                                 v = '{';
1177
1178                                 for (i in o)
1179                                         v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
1180
1181                                 return v + '}';
1182                 }
1183
1184                 return '' + o;
1185         };
1186
1187         tinymce.util.JSON = {
1188                 serialize: serialize,
1189
1190                 parse: function(s) {
1191                         try {
1192                                 return eval('(' + s + ')');
1193                         } catch (ex) {
1194                                 // Ignore
1195                         }
1196                 }
1197
1198                 };
1199 })();
1200 tinymce.create('static tinymce.util.XHR', {
1201         send : function(o) {
1202                 var x, t, w = window, c = 0;
1203
1204                 // Default settings
1205                 o.scope = o.scope || this;
1206                 o.success_scope = o.success_scope || o.scope;
1207                 o.error_scope = o.error_scope || o.scope;
1208                 o.async = o.async === false ? false : true;
1209                 o.data = o.data || '';
1210
1211                 function get(s) {
1212                         x = 0;
1213
1214                         try {
1215                                 x = new ActiveXObject(s);
1216                         } catch (ex) {
1217                         }
1218
1219                         return x;
1220                 };
1221
1222                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1223
1224                 if (x) {
1225                         if (x.overrideMimeType)
1226                                 x.overrideMimeType(o.content_type);
1227
1228                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1229
1230                         if (o.content_type)
1231                                 x.setRequestHeader('Content-Type', o.content_type);
1232
1233                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1234
1235                         x.send(o.data);
1236
1237                         function ready() {
1238                                 if (!o.async || x.readyState == 4 || c++ > 10000) {
1239                                         if (o.success && c < 10000 && x.status == 200)
1240                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);
1241                                         else if (o.error)
1242                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1243
1244                                         x = null;
1245                                 } else
1246                                         w.setTimeout(ready, 10);
1247                         };
1248
1249                         // Syncronous request
1250                         if (!o.async)
1251                                 return ready();
1252
1253                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1254                         t = w.setTimeout(ready, 10);
1255                 }
1256         }
1257 });
1258
1259 (function() {
1260         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1261
1262         tinymce.create('tinymce.util.JSONRequest', {
1263                 JSONRequest : function(s) {
1264                         this.settings = extend({
1265                         }, s);
1266                         this.count = 0;
1267                 },
1268
1269                 send : function(o) {
1270                         var ecb = o.error, scb = o.success;
1271
1272                         o = extend(this.settings, o);
1273
1274                         o.success = function(c, x) {
1275                                 c = JSON.parse(c);
1276
1277                                 if (typeof(c) == 'undefined') {
1278                                         c = {
1279                                                 error : 'JSON Parse error.'
1280                                         };
1281                                 }
1282
1283                                 if (c.error)
1284                                         ecb.call(o.error_scope || o.scope, c.error, x);
1285                                 else
1286                                         scb.call(o.success_scope || o.scope, c.result);
1287                         };
1288
1289                         o.error = function(ty, x) {
1290                                 if (ecb)
1291                                         ecb.call(o.error_scope || o.scope, ty, x);
1292                         };
1293
1294                         o.data = JSON.serialize({
1295                                 id : o.id || 'c' + (this.count++),
1296                                 method : o.method,
1297                                 params : o.params
1298                         });
1299
1300                         // JSON content type for Ruby on rails. Bug: #1883287
1301                         o.content_type = 'application/json';
1302
1303                         XHR.send(o);
1304                 },
1305
1306                 'static' : {
1307                         sendRPC : function(o) {
1308                                 return new tinymce.util.JSONRequest().send(o);
1309                         }
1310                 }
1311         });
1312 }());
1313 (function(tinymce){
1314         tinymce.VK = {
1315                 DELETE:46,
1316                 BACKSPACE:8
1317                 
1318         }
1319
1320 })(tinymce);
1321
1322 (function(tinymce) {
1323         function cleanupStylesWhenDeleting(ed) {
1324                 var dom = ed.dom, selection = ed.selection, VK= tinymce.VK;
1325                         ed.onKeyUp.add(function(ed, e) {
1326                                 if (e.keyCode == VK.DELETE ||e.keyCode == VK.BACKSPACE) {
1327                                         var startContainer = selection.getRng().startContainer;
1328                                         var blockElement = startContainer;
1329                                         while (!dom.isBlock(blockElement)) {
1330                                                 blockElement = blockElement.parentNode;
1331                                         }
1332                                         var spans = dom.select("span.Apple-style-span", blockElement);
1333                                         dom.remove(spans, true);
1334                                 }
1335                         });
1336         }
1337
1338         tinymce.create('tinymce.util.Quirks', {
1339                 Quirks: function(ed) {
1340                         if (tinymce.isWebKit) {
1341                                 cleanupStylesWhenDeleting(ed);
1342                         }
1343                                                 
1344                 }
1345         });
1346 })(tinymce);    
1347
1348 (function(tinymce) {
1349         var namedEntities, baseEntities, reverseEntities,
1350                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1351                 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1352                 rawCharsRegExp = /[<>&\"\']/g,
1353                 entityRegExp = /&(#x|#)?([\w]+);/g,
1354                 asciiMap = {
1355                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1356                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1357                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1358                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1359                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1360                 };
1361
1362         // Raw entities
1363         baseEntities = {
1364                 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
1365                 "'" : '&#39;',
1366                 '<' : '&lt;',
1367                 '>' : '&gt;',
1368                 '&' : '&amp;'
1369         };
1370
1371         // Reverse lookup table for raw entities
1372         reverseEntities = {
1373                 '&lt;' : '<',
1374                 '&gt;' : '>',
1375                 '&amp;' : '&',
1376                 '&quot;' : '"',
1377                 '&apos;' : "'"
1378         };
1379
1380         // Decodes text by using the browser
1381         function nativeDecode(text) {
1382                 var elm;
1383
1384                 elm = document.createElement("div");
1385                 elm.innerHTML = text;
1386
1387                 return elm.textContent || elm.innerText || text;
1388         };
1389
1390         // Build a two way lookup table for the entities
1391         function buildEntitiesLookup(items, radix) {
1392                 var i, chr, entity, lookup = {};
1393
1394                 if (items) {
1395                         items = items.split(',');
1396                         radix = radix || 10;
1397
1398                         // Build entities lookup table
1399                         for (i = 0; i < items.length; i += 2) {
1400                                 chr = String.fromCharCode(parseInt(items[i], radix));
1401
1402                                 // Only add non base entities
1403                                 if (!baseEntities[chr]) {
1404                                         entity = '&' + items[i + 1] + ';';
1405                                         lookup[chr] = entity;
1406                                         lookup[entity] = chr;
1407                                 }
1408                         }
1409
1410                         return lookup;
1411                 }
1412         };
1413
1414         // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1415         namedEntities = buildEntitiesLookup(
1416                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1417                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1418                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1419                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1420                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1421                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1422                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1423                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1424                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1425                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1426                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1427                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1428                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1429                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1430                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1431                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1432                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1433                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1434                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1435                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1436                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1437                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1438                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1439                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1440                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1441         , 32);
1442
1443         tinymce.html = tinymce.html || {};
1444
1445         tinymce.html.Entities = {
1446                 encodeRaw : function(text, attr) {
1447                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1448                                 return baseEntities[chr] || chr;
1449                         });
1450                 },
1451
1452                 encodeAllRaw : function(text) {
1453                         return ('' + text).replace(rawCharsRegExp, function(chr) {
1454                                 return baseEntities[chr] || chr;
1455                         });
1456                 },
1457
1458                 encodeNumeric : function(text, attr) {
1459                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1460                                 // Multi byte sequence convert it to a single entity
1461                                 if (chr.length > 1)
1462                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1463
1464                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1465                         });
1466                 },
1467
1468                 encodeNamed : function(text, attr, entities) {
1469                         entities = entities || namedEntities;
1470
1471                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1472                                 return baseEntities[chr] || entities[chr] || chr;
1473                         });
1474                 },
1475
1476                 getEncodeFunc : function(name, entities) {
1477                         var Entities = tinymce.html.Entities;
1478
1479                         entities = buildEntitiesLookup(entities) || namedEntities;
1480
1481                         function encodeNamedAndNumeric(text, attr) {
1482                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1483                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1484                                 });
1485                         };
1486
1487                         function encodeCustomNamed(text, attr) {
1488                                 return Entities.encodeNamed(text, attr, entities);
1489                         };
1490
1491                         // Replace + with , to be compatible with previous TinyMCE versions
1492                         name = tinymce.makeMap(name.replace(/\+/g, ','));
1493
1494                         // Named and numeric encoder
1495                         if (name.named && name.numeric)
1496                                 return encodeNamedAndNumeric;
1497
1498                         // Named encoder
1499                         if (name.named) {
1500                                 // Custom names
1501                                 if (entities)
1502                                         return encodeCustomNamed;
1503
1504                                 return Entities.encodeNamed;
1505                         }
1506
1507                         // Numeric
1508                         if (name.numeric)
1509                                 return Entities.encodeNumeric;
1510
1511                         // Raw encoder
1512                         return Entities.encodeRaw;
1513                 },
1514
1515                 decode : function(text) {
1516                         return text.replace(entityRegExp, function(all, numeric, value) {
1517                                 if (numeric) {
1518                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);
1519
1520                                         // Support upper UTF
1521                                         if (value > 0xFFFF) {
1522                                                 value -= 0x10000;
1523
1524                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1525                                         } else
1526                                                 return asciiMap[value] || String.fromCharCode(value);
1527                                 }
1528
1529                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1530                         });
1531                 }
1532         };
1533 })(tinymce);
1534
1535 tinymce.html.Styles = function(settings, schema) {
1536         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1537                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1538                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1539                 trimRightRegExp = /\s+$/,
1540                 urlColorRegExp = /rgb/,
1541                 undef, i, encodingLookup = {}, encodingItems;
1542
1543         settings = settings || {};
1544
1545         encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1546         for (i = 0; i < encodingItems.length; i++) {
1547                 encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1548                 encodingLookup['\uFEFF' + i] = encodingItems[i];
1549         }
1550
1551         function toHex(match, r, g, b) {
1552                 function hex(val) {
1553                         val = parseInt(val).toString(16);
1554
1555                         return val.length > 1 ? val : '0' + val; // 0 -> 00
1556                 };
1557
1558                 return '#' + hex(r) + hex(g) + hex(b);
1559         };
1560
1561         return {
1562                 toHex : function(color) {
1563                         return color.replace(rgbRegExp, toHex);
1564                 },
1565
1566                 parse : function(css) {
1567                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1568
1569                         function compress(prefix, suffix) {
1570                                 var top, right, bottom, left;
1571
1572                                 // Get values and check it it needs compressing
1573                                 top = styles[prefix + '-top' + suffix];
1574                                 if (!top)
1575                                         return;
1576
1577                                 right = styles[prefix + '-right' + suffix];
1578                                 if (top != right)
1579                                         return;
1580
1581                                 bottom = styles[prefix + '-bottom' + suffix];
1582                                 if (right != bottom)
1583                                         return;
1584
1585                                 left = styles[prefix + '-left' + suffix];
1586                                 if (bottom != left)
1587                                         return;
1588
1589                                 // Compress
1590                                 styles[prefix + suffix] = left;
1591                                 delete styles[prefix + '-top' + suffix];
1592                                 delete styles[prefix + '-right' + suffix];
1593                                 delete styles[prefix + '-bottom' + suffix];
1594                                 delete styles[prefix + '-left' + suffix];
1595                         };
1596
1597                         function canCompress(key) {
1598                                 var value = styles[key], i;
1599
1600                                 if (!value || value.indexOf(' ') < 0)
1601                                         return;
1602
1603                                 value = value.split(' ');
1604                                 i = value.length;
1605                                 while (i--) {
1606                                         if (value[i] !== value[0])
1607                                                 return false;
1608                                 }
1609
1610                                 styles[key] = value[0];
1611
1612                                 return true;
1613                         };
1614
1615                         function compress2(target, a, b, c) {
1616                                 if (!canCompress(a))
1617                                         return;
1618
1619                                 if (!canCompress(b))
1620                                         return;
1621
1622                                 if (!canCompress(c))
1623                                         return;
1624
1625                                 // Compress
1626                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1627                                 delete styles[a];
1628                                 delete styles[b];
1629                                 delete styles[c];
1630                         };
1631
1632                         // Encodes the specified string by replacing all \" \' ; : with _<num>
1633                         function encode(str) {
1634                                 isEncoded = true;
1635
1636                                 return encodingLookup[str];
1637                         };
1638
1639                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1640                         // It will also decode the \" \' if keep_slashes is set to fale or omitted
1641                         function decode(str, keep_slashes) {
1642                                 if (isEncoded) {
1643                                         str = str.replace(/\uFEFF[0-9]/g, function(str) {
1644                                                 return encodingLookup[str];
1645                                         });
1646                                 }
1647
1648                                 if (!keep_slashes)
1649                                         str = str.replace(/\\([\'\";:])/g, "$1");
1650
1651                                 return str;
1652                         }
1653
1654                         if (css) {
1655                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1656                                 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1657                                         return str.replace(/[;:]/g, encode);
1658                                 });
1659
1660                                 // Parse styles
1661                                 while (matches = styleRegExp.exec(css)) {
1662                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1663                                         value = matches[2].replace(trimRightRegExp, '');
1664
1665                                         if (name && value.length > 0) {
1666                                                 // Opera will produce 700 instead of bold in their style values
1667                                                 if (name === 'font-weight' && value === '700')
1668                                                         value = 'bold';
1669                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1670                                                         value = value.toLowerCase();            
1671
1672                                                 // Convert RGB colors to HEX
1673                                                 value = value.replace(rgbRegExp, toHex);
1674
1675                                                 // Convert URLs and force them into url('value') format
1676                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1677                                                         str = str || str2;
1678
1679                                                         if (str) {
1680                                                                 str = decode(str);
1681
1682                                                                 // Force strings into single quote format
1683                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";
1684                                                         }
1685
1686                                                         url = decode(url || url2 || url3);
1687
1688                                                         // Convert the URL to relative/absolute depending on config
1689                                                         if (urlConverter)
1690                                                                 url = urlConverter.call(urlConverterScope, url, 'style');
1691
1692                                                         // Output new URL format
1693                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";
1694                                                 });
1695
1696                                                 styles[name] = isEncoded ? decode(value, true) : value;
1697                                         }
1698
1699                                         styleRegExp.lastIndex = matches.index + matches[0].length;
1700                                 }
1701
1702                                 // Compress the styles to reduce it's size for example IE will expand styles
1703                                 compress("border", "");
1704                                 compress("border", "-width");
1705                                 compress("border", "-color");
1706                                 compress("border", "-style");
1707                                 compress("padding", "");
1708                                 compress("margin", "");
1709                                 compress2('border', 'border-width', 'border-style', 'border-color');
1710
1711                                 // Remove pointless border, IE produces these
1712                                 if (styles.border === 'medium none')
1713                                         delete styles.border;
1714                         }
1715
1716                         return styles;
1717                 },
1718
1719                 serialize : function(styles, element_name) {
1720                         var css = '', name, value;
1721
1722                         function serializeStyles(name) {
1723                                 var styleList, i, l, value;
1724
1725                                 styleList = schema.styles[name];
1726                                 if (styleList) {
1727                                         for (i = 0, l = styleList.length; i < l; i++) {
1728                                                 name = styleList[i];
1729                                                 value = styles[name];
1730
1731                                                 if (value !== undef && value.length > 0)
1732                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1733                                         }
1734                                 }
1735                         };
1736
1737                         // Serialize styles according to schema
1738                         if (element_name && schema && schema.styles) {
1739                                 // Serialize global styles and element specific styles
1740                                 serializeStyles('*');
1741                                 serializeStyles(element_name);
1742                         } else {
1743                                 // Output the styles in the order they are inside the object
1744                                 for (name in styles) {
1745                                         value = styles[name];
1746
1747                                         if (value !== undef && value.length > 0)
1748                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1749                                 }
1750                         }
1751
1752                         return css;
1753                 }
1754         };
1755 };
1756
1757 (function(tinymce) {
1758         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},
1759                 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1760
1761         function split(str, delim) {
1762                 return str.split(delim || ',');
1763         };
1764
1765         function unpack(lookup, data) {
1766                 var key, elements = {};
1767
1768                 function replace(value) {
1769                         return value.replace(/[A-Z]+/g, function(key) {
1770                                 return replace(lookup[key]);
1771                         });
1772                 };
1773
1774                 // Unpack lookup
1775                 for (key in lookup) {
1776                         if (lookup.hasOwnProperty(key))
1777                                 lookup[key] = replace(lookup[key]);
1778                 }
1779
1780                 // Unpack and parse data into object map
1781                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1782                         attributes = split(attributes, '|');
1783
1784                         elements[name] = {
1785                                 attributes : makeMap(attributes),
1786                                 attributesOrder : attributes,
1787                                 children : makeMap(children, '|', {'#comment' : {}})
1788                         }
1789                 });
1790
1791                 return elements;
1792         };
1793
1794         // Build a lookup table for block elements both lowercase and uppercase
1795         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + 
1796                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + 
1797                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1798         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1799
1800         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1801         transitional = unpack({
1802                 Z : 'H|K|N|O|P',
1803                 Y : 'X|form|R|Q',
1804                 ZG : 'E|span|width|align|char|charoff|valign',
1805                 X : 'p|T|div|U|W|isindex|fieldset|table',
1806                 ZF : 'E|align|char|charoff|valign',
1807                 W : 'pre|hr|blockquote|address|center|noframes',
1808                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1809                 ZD : '[E][S]',
1810                 U : 'ul|ol|dl|menu|dir',
1811                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1812                 T : 'h1|h2|h3|h4|h5|h6',
1813                 ZB : 'X|S|Q',
1814                 S : 'R|P',
1815                 ZA : 'a|G|J|M|O|P',
1816                 R : 'a|H|K|N|O',
1817                 Q : 'noscript|P',
1818                 P : 'ins|del|script',
1819                 O : 'input|select|textarea|label|button',
1820                 N : 'M|L',
1821                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1822                 L : 'sub|sup',
1823                 K : 'J|I',
1824                 J : 'tt|i|b|u|s|strike',
1825                 I : 'big|small|font|basefont',
1826                 H : 'G|F',
1827                 G : 'br|span|bdo',
1828                 F : 'object|applet|img|map|iframe',
1829                 E : 'A|B|C',
1830                 D : 'accesskey|tabindex|onfocus|onblur',
1831                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1832                 B : 'lang|xml:lang|dir',
1833                 A : 'id|class|style|title'
1834         }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
1835                 'style[B|id|type|media|title|xml:space][]' + 
1836                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
1837                 'param[id|name|value|valuetype|type][]' + 
1838                 'p[E|align][#|S]' + 
1839                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
1840                 'br[A|clear][]' + 
1841                 'span[E][#|S]' + 
1842                 'bdo[A|C|B][#|S]' + 
1843                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
1844                 'h1[E|align][#|S]' + 
1845                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
1846                 'map[B|C|A|name][X|form|Q|area]' + 
1847                 'h2[E|align][#|S]' + 
1848                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
1849                 'h3[E|align][#|S]' + 
1850                 'tt[E][#|S]' + 
1851                 'i[E][#|S]' + 
1852                 'b[E][#|S]' + 
1853                 'u[E][#|S]' + 
1854                 's[E][#|S]' + 
1855                 'strike[E][#|S]' + 
1856                 'big[E][#|S]' + 
1857                 'small[E][#|S]' + 
1858                 'font[A|B|size|color|face][#|S]' + 
1859                 'basefont[id|size|color|face][]' + 
1860                 'em[E][#|S]' + 
1861                 'strong[E][#|S]' + 
1862                 'dfn[E][#|S]' + 
1863                 'code[E][#|S]' + 
1864                 'q[E|cite][#|S]' + 
1865                 'samp[E][#|S]' + 
1866                 'kbd[E][#|S]' + 
1867                 'var[E][#|S]' + 
1868                 'cite[E][#|S]' + 
1869                 'abbr[E][#|S]' + 
1870                 'acronym[E][#|S]' + 
1871                 'sub[E][#|S]' + 
1872                 'sup[E][#|S]' + 
1873                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
1874                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
1875                 'optgroup[E|disabled|label][option]' + 
1876                 'option[E|selected|disabled|label|value][]' + 
1877                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
1878                 'label[E|for|accesskey|onfocus|onblur][#|S]' + 
1879                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
1880                 'h4[E|align][#|S]' + 
1881                 'ins[E|cite|datetime][#|Y]' + 
1882                 'h5[E|align][#|S]' + 
1883                 'del[E|cite|datetime][#|Y]' + 
1884                 'h6[E|align][#|S]' + 
1885                 'div[E|align][#|Y]' + 
1886                 'ul[E|type|compact][li]' + 
1887                 'li[E|type|value][#|Y]' + 
1888                 'ol[E|type|compact|start][li]' + 
1889                 'dl[E|compact][dt|dd]' + 
1890                 'dt[E][#|S]' + 
1891                 'dd[E][#|Y]' + 
1892                 'menu[E|compact][li]' + 
1893                 'dir[E|compact][li]' + 
1894                 'pre[E|width|xml:space][#|ZA]' + 
1895                 'hr[E|align|noshade|size|width][]' + 
1896                 'blockquote[E|cite][#|Y]' + 
1897                 'address[E][#|S|p]' + 
1898                 'center[E][#|Y]' + 
1899                 'noframes[E][#|Y]' + 
1900                 'isindex[A|B|prompt][]' + 
1901                 'fieldset[E][#|legend|Y]' + 
1902                 'legend[E|accesskey|align][#|S]' + 
1903                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
1904                 'caption[E|align][#|S]' + 
1905                 'col[ZG][]' + 
1906                 'colgroup[ZG][col]' + 
1907                 'thead[ZF][tr]' + 
1908                 'tr[ZF|bgcolor][th|td]' + 
1909                 'th[E|ZE][#|Y]' + 
1910                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
1911                 'noscript[E][#|Y]' + 
1912                 'td[E|ZE][#|Y]' + 
1913                 'tfoot[ZF][tr]' + 
1914                 'tbody[ZF][tr]' + 
1915                 'area[E|D|shape|coords|href|nohref|alt|target][]' + 
1916                 'base[id|href|target][]' + 
1917                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1918         );
1919
1920         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls');
1921         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1922         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap);
1923         whiteSpaceElementsMap = makeMap('pre,script,style,textarea');
1924         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1925
1926         tinymce.html.Schema = function(settings) {
1927                 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1928
1929                 settings = settings || {};
1930
1931                 // Allow all elements and attributes if verify_html is set to false
1932                 if (settings.verify_html === false)
1933                         settings.valid_elements = '*[*]';
1934
1935                 // Build styles list
1936                 if (settings.valid_styles) {
1937                         validStyles = {};
1938
1939                         // Convert styles into a rule list
1940                         each(settings.valid_styles, function(value, key) {
1941                                 validStyles[key] = tinymce.explode(value);
1942                         });
1943                 }
1944
1945                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1946                 function patternToRegExp(str) {
1947                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1948                 };
1949
1950                 // Parses the specified valid_elements string and adds to the current rules
1951                 // This function is a bit hard to read since it's heavily optimized for speed
1952                 function addValidElements(valid_elements) {
1953                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1954                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1955                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1956                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1957                                 hasPatternsRegExp = /[*?+]/;
1958
1959                         if (valid_elements) {
1960                                 // Split valid elements into an array with rules
1961                                 valid_elements = split(valid_elements);
1962
1963                                 if (elements['@']) {
1964                                         globalAttributes = elements['@'].attributes;
1965                                         globalAttributesOrder = elements['@'].attributesOrder;
1966                                 }
1967
1968                                 // Loop all rules
1969                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1970                                         // Parse element rule
1971                                         matches = elementRuleRegExp.exec(valid_elements[ei]);
1972                                         if (matches) {
1973                                                 // Setup local names for matches
1974                                                 prefix = matches[1];
1975                                                 elementName = matches[2];
1976                                                 outputName = matches[3];
1977                                                 attrData = matches[4];
1978
1979                                                 // Create new attributes and attributesOrder
1980                                                 attributes = {};
1981                                                 attributesOrder = [];
1982
1983                                                 // Create the new element
1984                                                 element = {
1985                                                         attributes : attributes,
1986                                                         attributesOrder : attributesOrder
1987                                                 };
1988
1989                                                 // Padd empty elements prefix
1990                                                 if (prefix === '#')
1991                                                         element.paddEmpty = true;
1992
1993                                                 // Remove empty elements prefix
1994                                                 if (prefix === '-')
1995                                                         element.removeEmpty = true;
1996
1997                                                 // Copy attributes from global rule into current rule
1998                                                 if (globalAttributes) {
1999                                                         for (key in globalAttributes)
2000                                                                 attributes[key] = globalAttributes[key];
2001
2002                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2003                                                 }
2004
2005                                                 // Attributes defined
2006                                                 if (attrData) {
2007                                                         attrData = split(attrData, '|');
2008                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {
2009                                                                 matches = attrRuleRegExp.exec(attrData[ai]);
2010                                                                 if (matches) {
2011                                                                         attr = {};
2012                                                                         attrType = matches[1];
2013                                                                         attrName = matches[2].replace(/::/g, ':');
2014                                                                         prefix = matches[3];
2015                                                                         value = matches[4];
2016
2017                                                                         // Required
2018                                                                         if (attrType === '!') {
2019                                                                                 element.attributesRequired = element.attributesRequired || [];
2020                                                                                 element.attributesRequired.push(attrName);
2021                                                                                 attr.required = true;
2022                                                                         }
2023
2024                                                                         // Denied from global
2025                                                                         if (attrType === '-') {
2026                                                                                 delete attributes[attrName];
2027                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2028                                                                                 continue;
2029                                                                         }
2030
2031                                                                         // Default value
2032                                                                         if (prefix) {
2033                                                                                 // Default value
2034                                                                                 if (prefix === '=') {
2035                                                                                         element.attributesDefault = element.attributesDefault || [];
2036                                                                                         element.attributesDefault.push({name: attrName, value: value});
2037                                                                                         attr.defaultValue = value;
2038                                                                                 }
2039
2040                                                                                 // Forced value
2041                                                                                 if (prefix === ':') {
2042                                                                                         element.attributesForced = element.attributesForced || [];
2043                                                                                         element.attributesForced.push({name: attrName, value: value});
2044                                                                                         attr.forcedValue = value;
2045                                                                                 }
2046
2047                                                                                 // Required values
2048                                                                                 if (prefix === '<')
2049                                                                                         attr.validValues = makeMap(value, '?');
2050                                                                         }
2051
2052                                                                         // Check for attribute patterns
2053                                                                         if (hasPatternsRegExp.test(attrName)) {
2054                                                                                 element.attributePatterns = element.attributePatterns || [];
2055                                                                                 attr.pattern = patternToRegExp(attrName);
2056                                                                                 element.attributePatterns.push(attr);
2057                                                                         } else {
2058                                                                                 // Add attribute to order list if it doesn't already exist
2059                                                                                 if (!attributes[attrName])
2060                                                                                         attributesOrder.push(attrName);
2061
2062                                                                                 attributes[attrName] = attr;
2063                                                                         }
2064                                                                 }
2065                                                         }
2066                                                 }
2067
2068                                                 // Global rule, store away these for later usage
2069                                                 if (!globalAttributes && elementName == '@') {
2070                                                         globalAttributes = attributes;
2071                                                         globalAttributesOrder = attributesOrder;
2072                                                 }
2073
2074                                                 // Handle substitute elements such as b/strong
2075                                                 if (outputName) {
2076                                                         element.outputName = elementName;
2077                                                         elements[outputName] = element;
2078                                                 }
2079
2080                                                 // Add pattern or exact element
2081                                                 if (hasPatternsRegExp.test(elementName)) {
2082                                                         element.pattern = patternToRegExp(elementName);
2083                                                         patternElements.push(element);
2084                                                 } else
2085                                                         elements[elementName] = element;
2086                                         }
2087                                 }
2088                         }
2089                 };
2090
2091                 function setValidElements(valid_elements) {
2092                         elements = {};
2093                         patternElements = [];
2094
2095                         addValidElements(valid_elements);
2096
2097                         each(transitional, function(element, name) {
2098                                 children[name] = element.children;
2099                         });
2100                 };
2101
2102                 // Adds custom non HTML elements to the schema
2103                 function addCustomElements(custom_elements) {
2104                         var customElementRegExp = /^(~)?(.+)$/;
2105
2106                         if (custom_elements) {
2107                                 each(split(custom_elements), function(rule) {
2108                                         var matches = customElementRegExp.exec(rule),
2109                                                 inline = matches[1] === '~',
2110                                                 cloneName = inline ? 'span' : 'div',
2111                                                 name = matches[2];
2112
2113                                         children[name] = children[cloneName];
2114                                         customElementsMap[name] = cloneName;
2115
2116                                         // If it's not marked as inline then add it to valid block elements
2117                                         if (!inline)
2118                                                 blockElementsMap[name] = {};
2119
2120                                         // Add custom elements at span/div positions
2121                                         each(children, function(element, child) {
2122                                                 if (element[cloneName])
2123                                                         element[name] = element[cloneName];
2124                                         });
2125                                 });
2126                         }
2127                 };
2128
2129                 // Adds valid children to the schema object
2130                 function addValidChildren(valid_children) {
2131                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2132
2133                         if (valid_children) {
2134                                 each(split(valid_children), function(rule) {
2135                                         var matches = childRuleRegExp.exec(rule), parent, prefix;
2136
2137                                         if (matches) {
2138                                                 prefix = matches[1];
2139
2140                                                 // Add/remove items from default
2141                                                 if (prefix)
2142                                                         parent = children[matches[2]];
2143                                                 else
2144                                                         parent = children[matches[2]] = {'#comment' : {}};
2145
2146                                                 parent = children[matches[2]];
2147
2148                                                 each(split(matches[3], '|'), function(child) {
2149                                                         if (prefix === '-')
2150                                                                 delete parent[child];
2151                                                         else
2152                                                                 parent[child] = {};
2153                                                 });
2154                                         }
2155                                 });
2156                         }
2157                 };
2158
2159                 function getElementRule(name) {
2160                         var element = elements[name], i;
2161
2162                         // Exact match found
2163                         if (element)
2164                                 return element;
2165
2166                         // No exact match then try the patterns
2167                         i = patternElements.length;
2168                         while (i--) {
2169                                 element = patternElements[i];
2170
2171                                 if (element.pattern.test(name))
2172                                         return element;
2173                         }
2174                 };
2175
2176                 if (!settings.valid_elements) {
2177                         // No valid elements defined then clone the elements from the transitional spec
2178                         each(transitional, function(element, name) {
2179                                 elements[name] = {
2180                                         attributes : element.attributes,
2181                                         attributesOrder : element.attributesOrder
2182                                 };
2183
2184                                 children[name] = element.children;
2185                         });
2186
2187                         // Switch these
2188                         each(split('strong/b,em/i'), function(item) {
2189                                 item = split(item, '/');
2190                                 elements[item[1]].outputName = item[0];
2191                         });
2192
2193                         // Add default alt attribute for images
2194                         elements.img.attributesDefault = [{name: 'alt', value: ''}];
2195
2196                         // Remove these if they are empty by default
2197                         each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {
2198                                 elements[name].removeEmpty = true;
2199                         });
2200
2201                         // Padd these by default
2202                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2203                                 elements[name].paddEmpty = true;
2204                         });
2205                 } else
2206                         setValidElements(settings.valid_elements);
2207
2208                 addCustomElements(settings.custom_elements);
2209                 addValidChildren(settings.valid_children);
2210                 addValidElements(settings.extended_valid_elements);
2211
2212                 // Todo: Remove this when we fix list handling to be valid
2213                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2214
2215                 // If the user didn't allow span only allow internal spans
2216                 if (!getElementRule('span'))
2217                         addValidElements('span[!data-mce-type|*]');
2218
2219                 // Delete invalid elements
2220                 if (settings.invalid_elements) {
2221                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2222                                 if (elements[item])
2223                                         delete elements[item];
2224                         });
2225                 }
2226
2227                 self.children = children;
2228
2229                 self.styles = validStyles;
2230
2231                 self.getBoolAttrs = function() {
2232                         return boolAttrMap;
2233                 };
2234
2235                 self.getBlockElements = function() {
2236                         return blockElementsMap;
2237                 };
2238
2239                 self.getShortEndedElements = function() {
2240                         return shortEndedElementsMap;
2241                 };
2242
2243                 self.getSelfClosingElements = function() {
2244                         return selfClosingElementsMap;
2245                 };
2246
2247                 self.getNonEmptyElements = function() {
2248                         return nonEmptyElementsMap;
2249                 };
2250
2251                 self.getWhiteSpaceElements = function() {
2252                         return whiteSpaceElementsMap;
2253                 };
2254
2255                 self.isValidChild = function(name, child) {
2256                         var parent = children[name];
2257
2258                         return !!(parent && parent[child]);
2259                 };
2260
2261                 self.getElementRule = getElementRule;
2262
2263                 self.getCustomElements = function() {
2264                         return customElementsMap;
2265                 };
2266
2267                 self.addValidElements = addValidElements;
2268
2269                 self.setValidElements = setValidElements;
2270
2271                 self.addCustomElements = addCustomElements;
2272
2273                 self.addValidChildren = addValidChildren;
2274         };
2275
2276         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
2277         tinymce.html.Schema.boolAttrMap = boolAttrMap;
2278         tinymce.html.Schema.blockElementsMap = blockElementsMap;
2279 })(tinymce);
2280
2281 (function(tinymce) {
2282         tinymce.html.SaxParser = function(settings, schema) {
2283                 var self = this, noop = function() {};
2284
2285                 settings = settings || {};
2286                 self.schema = schema = schema || new tinymce.html.Schema();
2287
2288                 if (settings.fix_self_closing !== false)
2289                         settings.fix_self_closing = true;
2290
2291                 // Add handler functions from settings and setup default handlers
2292                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2293                         if (name)
2294                                 self[name] = settings[name] || noop;
2295                 });
2296
2297                 self.parse = function(html) {
2298                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2299                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
2300                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2301                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
2302
2303                         function processEndTag(name) {
2304                                 var pos, i;
2305
2306                                 // Find position of parent of the same type
2307                                 pos = stack.length;
2308                                 while (pos--) {
2309                                         if (stack[pos].name === name)
2310                                                 break;                                          
2311                                 }
2312
2313                                 // Found parent
2314                                 if (pos >= 0) {
2315                                         // Close all the open elements
2316                                         for (i = stack.length - 1; i >= pos; i--) {
2317                                                 name = stack[i];
2318
2319                                                 if (name.valid)
2320                                                         self.end(name.name);
2321                                         }
2322
2323                                         // Remove the open elements from the stack
2324                                         stack.length = pos;
2325                                 }
2326                         };
2327
2328                         // Precompile RegExps and map objects
2329                         tokenRegExp = new RegExp('<(?:' +
2330                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
2331                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2332                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2333                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2334                                 '(?:\\/([^>]+)>)|' + // End element
2335                                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
2336                         ')', 'g');
2337
2338                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2339                         specialElements = {
2340                                 'script' : /<\/script[^>]*>/gi,
2341                                 'style' : /<\/style[^>]*>/gi,
2342                                 'noscript' : /<\/noscript[^>]*>/gi
2343                         };
2344
2345                         // Setup lookup tables for empty elements and boolean attributes
2346                         shortEndedElements = schema.getShortEndedElements();
2347                         selfClosing = schema.getSelfClosingElements();
2348                         fillAttrsMap = schema.getBoolAttrs();
2349                         validate = settings.validate;
2350                         removeInternalElements = settings.remove_internals;
2351                         fixSelfClosing = settings.fix_self_closing;
2352
2353                         while (matches = tokenRegExp.exec(html)) {
2354                                 // Text
2355                                 if (index < matches.index)
2356                                         self.text(decode(html.substr(index, matches.index - index)));
2357
2358                                 if (value = matches[6]) { // End element
2359                                         processEndTag(value.toLowerCase());
2360                                 } else if (value = matches[7]) { // Start element
2361                                         value = value.toLowerCase();
2362                                         isShortEnded = value in shortEndedElements;
2363
2364                                         // Is self closing tag for example an <li> after an open <li>
2365                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2366                                                 processEndTag(value);
2367
2368                                         // Validate element
2369                                         if (!validate || (elementRule = schema.getElementRule(value))) {
2370                                                 isValidElement = true;
2371
2372                                                 // Grab attributes map and patters when validation is enabled
2373                                                 if (validate) {
2374                                                         validAttributesMap = elementRule.attributes;
2375                                                         validAttributePatterns = elementRule.attributePatterns;
2376                                                 }
2377
2378                                                 // Parse attributes
2379                                                 if (attribsValue = matches[8]) {
2380                                                         isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
2381
2382                                                         // If the element has internal attributes then remove it if we are told to do so
2383                                                         if (isInternalElement && removeInternalElements)
2384                                                                 isValidElement = false;
2385
2386                                                         attrList = [];
2387                                                         attrList.map = {};
2388
2389                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2390                                                                 var attrRule, i;
2391
2392                                                                 name = name.toLowerCase();
2393                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2394
2395                                                                 // Validate name and value
2396                                                                 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2397                                                                         attrRule = validAttributesMap[name];
2398
2399                                                                         // Find rule by pattern matching
2400                                                                         if (!attrRule && validAttributePatterns) {
2401                                                                                 i = validAttributePatterns.length;
2402                                                                                 while (i--) {
2403                                                                                         attrRule = validAttributePatterns[i];
2404                                                                                         if (attrRule.pattern.test(name))
2405                                                                                                 break;
2406                                                                                 }
2407
2408                                                                                 // No rule matched
2409                                                                                 if (i === -1)
2410                                                                                         attrRule = null;
2411                                                                         }
2412
2413                                                                         // No attribute rule found
2414                                                                         if (!attrRule)
2415                                                                                 return;
2416
2417                                                                         // Validate value
2418                                                                         if (attrRule.validValues && !(value in attrRule.validValues))
2419                                                                                 return;
2420                                                                 }
2421
2422                                                                 // Add attribute to list and map
2423                                                                 attrList.map[name] = value;
2424                                                                 attrList.push({
2425                                                                         name: name,
2426                                                                         value: value
2427                                                                 });
2428                                                         });
2429                                                 } else {
2430                                                         attrList = [];
2431                                                         attrList.map = {};
2432                                                 }
2433
2434                                                 // Process attributes if validation is enabled
2435                                                 if (validate && !isInternalElement) {
2436                                                         attributesRequired = elementRule.attributesRequired;
2437                                                         attributesDefault = elementRule.attributesDefault;
2438                                                         attributesForced = elementRule.attributesForced;
2439
2440                                                         // Handle forced attributes
2441                                                         if (attributesForced) {
2442                                                                 i = attributesForced.length;
2443                                                                 while (i--) {
2444                                                                         attr = attributesForced[i];
2445                                                                         name = attr.name;
2446                                                                         attrValue = attr.value;
2447
2448                                                                         if (attrValue === '{$uid}')
2449                                                                                 attrValue = 'mce_' + idCount++;
2450
2451                                                                         attrList.map[name] = attrValue;
2452                                                                         attrList.push({name: name, value: attrValue});
2453                                                                 }
2454                                                         }
2455
2456                                                         // Handle default attributes
2457                                                         if (attributesDefault) {
2458                                                                 i = attributesDefault.length;
2459                                                                 while (i--) {
2460                                                                         attr = attributesDefault[i];
2461                                                                         name = attr.name;
2462
2463                                                                         if (!(name in attrList.map)) {
2464                                                                                 attrValue = attr.value;
2465
2466                                                                                 if (attrValue === '{$uid}')
2467                                                                                         attrValue = 'mce_' + idCount++;
2468
2469                                                                                 attrList.map[name] = attrValue;
2470                                                                                 attrList.push({name: name, value: attrValue});
2471                                                                         }
2472                                                                 }
2473                                                         }
2474
2475                                                         // Handle required attributes
2476                                                         if (attributesRequired) {
2477                                                                 i = attributesRequired.length;
2478                                                                 while (i--) {
2479                                                                         if (attributesRequired[i] in attrList.map)
2480                                                                                 break;
2481                                                                 }
2482
2483                                                                 // None of the required attributes where found
2484                                                                 if (i === -1)
2485                                                                         isValidElement = false;
2486                                                         }
2487
2488                                                         // Invalidate element if it's marked as bogus
2489                                                         if (attrList.map['data-mce-bogus'])
2490                                                                 isValidElement = false;
2491                                                 }
2492
2493                                                 if (isValidElement)
2494                                                         self.start(value, attrList, isShortEnded);
2495                                         } else
2496                                                 isValidElement = false;
2497
2498                                         // Treat script, noscript and style a bit different since they may include code that looks like elements
2499                                         if (endRegExp = specialElements[value]) {
2500                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;
2501
2502                                                 if (matches = endRegExp.exec(html)) {
2503                                                         if (isValidElement)
2504                                                                 text = html.substr(index, matches.index - index);
2505
2506                                                         index = matches.index + matches[0].length;
2507                                                 } else {
2508                                                         text = html.substr(index);
2509                                                         index = html.length;
2510                                                 }
2511
2512                                                 if (isValidElement && text.length > 0)
2513                                                         self.text(text, true);
2514
2515                                                 if (isValidElement)
2516                                                         self.end(value);
2517
2518                                                 tokenRegExp.lastIndex = index;
2519                                                 continue;
2520                                         }
2521
2522                                         // Push value on to stack
2523                                         if (!isShortEnded) {
2524                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2525                                                         stack.push({name: value, valid: isValidElement});
2526                                                 else if (isValidElement)
2527                                                         self.end(value);
2528                                         }
2529                                 } else if (value = matches[1]) { // Comment
2530                                         self.comment(value);
2531                                 } else if (value = matches[2]) { // CDATA
2532                                         self.cdata(value);
2533                                 } else if (value = matches[3]) { // DOCTYPE
2534                                         self.doctype(value);
2535                                 } else if (value = matches[4]) { // PI
2536                                         self.pi(value, matches[5]);
2537                                 }
2538
2539                                 index = matches.index + matches[0].length;
2540                         }
2541
2542                         // Text
2543                         if (index < html.length)
2544                                 self.text(decode(html.substr(index)));
2545
2546                         // Close any open elements
2547                         for (i = stack.length - 1; i >= 0; i--) {
2548                                 value = stack[i];
2549
2550                                 if (value.valid)
2551                                         self.end(value.name);
2552                         }
2553                 };
2554         }
2555 })(tinymce);
2556
2557 (function(tinymce) {
2558         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2559                 '#text' : 3,
2560                 '#comment' : 8,
2561                 '#cdata' : 4,
2562                 '#pi' : 7,
2563                 '#doctype' : 10,
2564                 '#document-fragment' : 11
2565         };
2566
2567         // Walks the tree left/right
2568         function walk(node, root_node, prev) {
2569                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2570
2571                 // Walk into nodes if it has a start
2572                 if (node[startName])
2573                         return node[startName];
2574
2575                 // Return the sibling if it has one
2576                 if (node !== root_node) {
2577                         sibling = node[siblingName];
2578
2579                         if (sibling)
2580                                 return sibling;
2581
2582                         // Walk up the parents to look for siblings
2583                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2584                                 sibling = parent[siblingName];
2585
2586                                 if (sibling)
2587                                         return sibling;
2588                         }
2589                 }
2590         };
2591
2592         function Node(name, type) {
2593                 this.name = name;
2594                 this.type = type;
2595
2596                 if (type === 1) {
2597                         this.attributes = [];
2598                         this.attributes.map = {};
2599                 }
2600         }
2601
2602         tinymce.extend(Node.prototype, {
2603                 replace : function(node) {
2604                         var self = this;
2605
2606                         if (node.parent)
2607                                 node.remove();
2608
2609                         self.insert(node, self);
2610                         self.remove();
2611
2612                         return self;
2613                 },
2614
2615                 attr : function(name, value) {
2616                         var self = this, attrs, i, undef;
2617
2618                         if (typeof name !== "string") {
2619                                 for (i in name)
2620                                         self.attr(i, name[i]);
2621
2622                                 return self;
2623                         }
2624
2625                         if (attrs = self.attributes) {
2626                                 if (value !== undef) {
2627                                         // Remove attribute
2628                                         if (value === null) {
2629                                                 if (name in attrs.map) {
2630                                                         delete attrs.map[name];
2631
2632                                                         i = attrs.length;
2633                                                         while (i--) {
2634                                                                 if (attrs[i].name === name) {
2635                                                                         attrs = attrs.splice(i, 1);
2636                                                                         return self;
2637                                                                 }
2638                                                         }
2639                                                 }
2640
2641                                                 return self;
2642                                         }
2643
2644                                         // Set attribute
2645                                         if (name in attrs.map) {
2646                                                 // Set attribute
2647                                                 i = attrs.length;
2648                                                 while (i--) {
2649                                                         if (attrs[i].name === name) {
2650                                                                 attrs[i].value = value;
2651                                                                 break;
2652                                                         }
2653                                                 }
2654                                         } else
2655                                                 attrs.push({name: name, value: value});
2656
2657                                         attrs.map[name] = value;
2658
2659                                         return self;
2660                                 } else {
2661                                         return attrs.map[name];
2662                                 }
2663                         }
2664                 },
2665
2666                 clone : function() {
2667                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2668
2669                         // Clone element attributes
2670                         if (selfAttrs = self.attributes) {
2671                                 cloneAttrs = [];
2672                                 cloneAttrs.map = {};
2673
2674                                 for (i = 0, l = selfAttrs.length; i < l; i++) {
2675                                         selfAttr = selfAttrs[i];
2676
2677                                         // Clone everything except id
2678                                         if (selfAttr.name !== 'id') {
2679                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2680                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2681                                         }
2682                                 }
2683
2684                                 clone.attributes = cloneAttrs;
2685                         }
2686
2687                         clone.value = self.value;
2688                         clone.shortEnded = self.shortEnded;
2689
2690                         return clone;
2691                 },
2692
2693                 wrap : function(wrapper) {
2694                         var self = this;
2695
2696                         self.parent.insert(wrapper, self);
2697                         wrapper.append(self);
2698
2699                         return self;
2700                 },
2701
2702                 unwrap : function() {
2703                         var self = this, node, next;
2704
2705                         for (node = self.firstChild; node; ) {
2706                                 next = node.next;
2707                                 self.insert(node, self, true);
2708                                 node = next;
2709                         }
2710
2711                         self.remove();
2712                 },
2713
2714                 remove : function() {
2715                         var self = this, parent = self.parent, next = self.next, prev = self.prev;
2716
2717                         if (parent) {
2718                                 if (parent.firstChild === self) {
2719                                         parent.firstChild = next;
2720
2721                                         if (next)
2722                                                 next.prev = null;
2723                                 } else {
2724                                         prev.next = next;
2725                                 }
2726
2727                                 if (parent.lastChild === self) {
2728                                         parent.lastChild = prev;
2729
2730                                         if (prev)
2731                                                 prev.next = null;
2732                                 } else {
2733                                         next.prev = prev;
2734                                 }
2735
2736                                 self.parent = self.next = self.prev = null;
2737                         }
2738
2739                         return self;
2740                 },
2741
2742                 append : function(node) {
2743                         var self = this, last;
2744
2745                         if (node.parent)
2746                                 node.remove();
2747
2748                         last = self.lastChild;
2749                         if (last) {
2750                                 last.next = node;
2751                                 node.prev = last;
2752                                 self.lastChild = node;
2753                         } else
2754                                 self.lastChild = self.firstChild = node;
2755
2756                         node.parent = self;
2757
2758                         return node;
2759                 },
2760
2761                 insert : function(node, ref_node, before) {
2762                         var parent;
2763
2764                         if (node.parent)
2765                                 node.remove();
2766
2767                         parent = ref_node.parent || this;
2768
2769                         if (before) {
2770                                 if (ref_node === parent.firstChild)
2771                                         parent.firstChild = node;
2772                                 else
2773                                         ref_node.prev.next = node;
2774
2775                                 node.prev = ref_node.prev;
2776                                 node.next = ref_node;
2777                                 ref_node.prev = node;
2778                         } else {
2779                                 if (ref_node === parent.lastChild)
2780                                         parent.lastChild = node;
2781                                 else
2782                                         ref_node.next.prev = node;
2783
2784                                 node.next = ref_node.next;
2785                                 node.prev = ref_node;
2786                                 ref_node.next = node;
2787                         }
2788
2789                         node.parent = parent;
2790
2791                         return node;
2792                 },
2793
2794                 getAll : function(name) {
2795                         var self = this, node, collection = [];
2796
2797                         for (node = self.firstChild; node; node = walk(node, self)) {
2798                                 if (node.name === name)
2799                                         collection.push(node);
2800                         }
2801
2802                         return collection;
2803                 },
2804
2805                 empty : function() {
2806                         var self = this, nodes, i, node;
2807
2808                         // Remove all children
2809                         if (self.firstChild) {
2810                                 nodes = [];
2811
2812                                 // Collect the children
2813                                 for (node = self.firstChild; node; node = walk(node, self))
2814                                         nodes.push(node);
2815
2816                                 // Remove the children
2817                                 i = nodes.length;
2818                                 while (i--) {
2819                                         node = nodes[i];
2820                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2821                                 }
2822                         }
2823
2824                         self.firstChild = self.lastChild = null;
2825
2826                         return self;
2827                 },
2828
2829                 isEmpty : function(elements) {
2830                         var self = this, node = self.firstChild, i, name;
2831
2832                         if (node) {
2833                                 do {
2834                                         if (node.type === 1) {
2835                                                 // Ignore bogus elements
2836                                                 if (node.attributes.map['data-mce-bogus'])
2837                                                         continue;
2838
2839                                                 // Keep empty elements like <img />
2840                                                 if (elements[node.name])
2841                                                         return false;
2842
2843                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
2844                                                 i = node.attributes.length;
2845                                                 while (i--) {
2846                                                         name = node.attributes[i].name;
2847                                                         if (name === "name" || name.indexOf('data-') === 0)
2848                                                                 return false;
2849                                                 }
2850                                         }
2851
2852                                         // Keep non whitespace text nodes
2853                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2854                                                 return false;
2855                                 } while (node = walk(node, self));
2856                         }
2857
2858                         return true;
2859                 },
2860
2861                 walk : function(prev) {
2862                         return walk(this, null, prev);
2863                 }
2864         });
2865
2866         tinymce.extend(Node, {
2867                 create : function(name, attrs) {
2868                         var node, attrName;
2869
2870                         // Create node
2871                         node = new Node(name, typeLookup[name] || 1);
2872
2873                         // Add attributes if needed
2874                         if (attrs) {
2875                                 for (attrName in attrs)
2876                                         node.attr(attrName, attrs[attrName]);
2877                         }
2878
2879                         return node;
2880                 }
2881         });
2882
2883         tinymce.html.Node = Node;
2884 })(tinymce);
2885
2886 (function(tinymce) {
2887         var Node = tinymce.html.Node;
2888
2889         tinymce.html.DomParser = function(settings, schema) {
2890                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2891
2892                 settings = settings || {};
2893                 settings.validate = "validate" in settings ? settings.validate : true;
2894                 settings.root_name = settings.root_name || 'body';
2895                 self.schema = schema = schema || new tinymce.html.Schema();
2896
2897                 function fixInvalidChildren(nodes) {
2898                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2899                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2900
2901                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2902                         nonEmptyElements = schema.getNonEmptyElements();
2903
2904                         for (ni = 0; ni < nodes.length; ni++) {
2905                                 node = nodes[ni];
2906
2907                                 // Already removed
2908                                 if (!node.parent)
2909                                         continue;
2910
2911                                 // Get list of all parent nodes until we find a valid parent to stick the child into
2912                                 parents = [node];
2913                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2914                                         parents.push(parent);
2915
2916                                 // Found a suitable parent
2917                                 if (parent && parents.length > 1) {
2918                                         // Reverse the array since it makes looping easier
2919                                         parents.reverse();
2920
2921                                         // Clone the related parent and insert that after the moved node
2922                                         newParent = currentNode = self.filterNode(parents[0].clone());
2923
2924                                         // Start cloning and moving children on the left side of the target node
2925                                         for (i = 0; i < parents.length - 1; i++) {
2926                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {
2927                                                         tempNode = self.filterNode(parents[i].clone());
2928                                                         currentNode.append(tempNode);
2929                                                 } else
2930                                                         tempNode = currentNode;
2931
2932                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2933                                                         nextNode = childNode.next;
2934                                                         tempNode.append(childNode);
2935                                                         childNode = nextNode;
2936                                                 }
2937
2938                                                 currentNode = tempNode;
2939                                         }
2940
2941                                         if (!newParent.isEmpty(nonEmptyElements)) {
2942                                                 parent.insert(newParent, parents[0], true);
2943                                                 parent.insert(node, newParent);
2944                                         } else {
2945                                                 parent.insert(node, parents[0], true);
2946                                         }
2947
2948                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2949                                         parent = parents[0];
2950                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2951                                                 parent.empty().remove();
2952                                         }
2953                                 } else if (node.parent) {
2954                                         // If it's an LI try to find a UL/OL for it or wrap it
2955                                         if (node.name === 'li') {
2956                                                 sibling = node.prev;
2957                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2958                                                         sibling.append(node);
2959                                                         continue;
2960                                                 }
2961
2962                                                 sibling = node.next;
2963                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2964                                                         sibling.insert(node, sibling.firstChild, true);
2965                                                         continue;
2966                                                 }
2967
2968                                                 node.wrap(self.filterNode(new Node('ul', 1)));
2969                                                 continue;
2970                                         }
2971
2972                                         // Try wrapping the element in a DIV
2973                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2974                                                 node.wrap(self.filterNode(new Node('div', 1)));
2975                                         } else {
2976                                                 // We failed wrapping it, then remove or unwrap it
2977                                                 if (node.name === 'style' || node.name === 'script')
2978                                                         node.empty().remove();
2979                                                 else
2980                                                         node.unwrap();
2981                                         }
2982                                 }
2983                         }
2984                 };
2985
2986                 self.filterNode = function(node) {
2987                         var i, name, list;
2988
2989                         // Run element filters
2990                         if (name in nodeFilters) {
2991                                 list = matchedNodes[name];
2992
2993                                 if (list)
2994                                         list.push(node);
2995                                 else
2996                                         matchedNodes[name] = [node];
2997                         }
2998
2999                         // Run attribute filters
3000                         i = attributeFilters.length;
3001                         while (i--) {
3002                                 name = attributeFilters[i].name;
3003
3004                                 if (name in node.attributes.map) {
3005                                         list = matchedAttributes[name];
3006
3007                                         if (list)
3008                                                 list.push(node);
3009                                         else
3010                                                 matchedAttributes[name] = [node];
3011                                 }
3012                         }
3013
3014                         return node;
3015                 };
3016
3017                 self.addNodeFilter = function(name, callback) {
3018                         tinymce.each(tinymce.explode(name), function(name) {
3019                                 var list = nodeFilters[name];
3020
3021                                 if (!list)
3022                                         nodeFilters[name] = list = [];
3023
3024                                 list.push(callback);
3025                         });
3026                 };
3027
3028                 self.addAttributeFilter = function(name, callback) {
3029                         tinymce.each(tinymce.explode(name), function(name) {
3030                                 var i;
3031
3032                                 for (i = 0; i < attributeFilters.length; i++) {
3033                                         if (attributeFilters[i].name === name) {
3034                                                 attributeFilters[i].callbacks.push(callback);
3035                                                 return;
3036                                         }
3037                                 }
3038
3039                                 attributeFilters.push({name: name, callbacks: [callback]});
3040                         });
3041                 };
3042
3043                 self.parse = function(html, args) {
3044                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3045                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
3046                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3047
3048                         args = args || {};
3049                         matchedNodes = {};
3050                         matchedAttributes = {};
3051                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3052                         nonEmptyElements = schema.getNonEmptyElements();
3053                         children = schema.children;
3054                         validate = settings.validate;
3055                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3056
3057                         whiteSpaceElements = schema.getWhiteSpaceElements();
3058                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3059                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3060                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3061
3062                         function addRootBlocks() {
3063                                 var node = rootNode.firstChild, next, rootBlockNode;
3064
3065                                 while (node) {
3066                                         next = node.next;
3067
3068                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3069                                                 if (!rootBlockNode) {
3070                                                         // Create a new root block element
3071                                                         rootBlockNode = createNode(rootBlockName, 1);
3072                                                         rootNode.insert(rootBlockNode, node);
3073                                                         rootBlockNode.append(node);
3074                                                 } else
3075                                                         rootBlockNode.append(node);
3076                                         } else {
3077                                                 rootBlockNode = null;
3078                                         }
3079
3080                                         node = next;
3081                                 };
3082                         };
3083
3084                         function createNode(name, type) {
3085                                 var node = new Node(name, type), list;
3086
3087                                 if (name in nodeFilters) {
3088                                         list = matchedNodes[name];
3089
3090                                         if (list)
3091                                                 list.push(node);
3092                                         else
3093                                                 matchedNodes[name] = [node];
3094                                 }
3095
3096                                 return node;
3097                         };
3098
3099                         function removeWhitespaceBefore(node) {
3100                                 var textNode, textVal, sibling;
3101
3102                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
3103                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3104
3105                                         if (textVal.length > 0) {
3106                                                 textNode.value = textVal;
3107                                                 textNode = textNode.prev;
3108                                         } else {
3109                                                 sibling = textNode.prev;
3110                                                 textNode.remove();
3111                                                 textNode = sibling;
3112                                         }
3113                                 }
3114                         };
3115
3116                         parser = new tinymce.html.SaxParser({
3117                                 validate : validate,
3118                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
3119
3120                                 cdata: function(text) {
3121                                         node.append(createNode('#cdata', 4)).value = text;
3122                                 },
3123
3124                                 text: function(text, raw) {
3125                                         var textNode;
3126
3127                                         // Trim all redundant whitespace on non white space elements
3128                                         if (!whiteSpaceElements[node.name]) {
3129                                                 text = text.replace(allWhiteSpaceRegExp, ' ');
3130
3131                                                 if (node.lastChild && blockElements[node.lastChild.name])
3132                                                         text = text.replace(startWhiteSpaceRegExp, '');
3133                                         }
3134
3135                                         // Do we need to create the node
3136                                         if (text.length !== 0) {
3137                                                 textNode = createNode('#text', 3);
3138                                                 textNode.raw = !!raw;
3139                                                 node.append(textNode).value = text;
3140                                         }
3141                                 },
3142
3143                                 comment: function(text) {
3144                                         node.append(createNode('#comment', 8)).value = text;
3145                                 },
3146
3147                                 pi: function(name, text) {
3148                                         node.append(createNode(name, 7)).value = text;
3149                                         removeWhitespaceBefore(node);
3150                                 },
3151
3152                                 doctype: function(text) {
3153                                         var newNode;
3154                 
3155                                         newNode = node.append(createNode('#doctype', 10));
3156                                         newNode.value = text;
3157                                         removeWhitespaceBefore(node);
3158                                 },
3159
3160                                 start: function(name, attrs, empty) {
3161                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3162
3163                                         elementRule = validate ? schema.getElementRule(name) : {};
3164                                         if (elementRule) {
3165                                                 newNode = createNode(elementRule.outputName || name, 1);
3166                                                 newNode.attributes = attrs;
3167                                                 newNode.shortEnded = empty;
3168
3169                                                 node.append(newNode);
3170
3171                                                 // Check if node is valid child of the parent node is the child is
3172                                                 // unknown we don't collect it since it's probably a custom element
3173                                                 parent = children[node.name];
3174                                                 if (parent && children[newNode.name] && !parent[newNode.name])
3175                                                         invalidChildren.push(newNode);
3176
3177                                                 attrFiltersLen = attributeFilters.length;
3178                                                 while (attrFiltersLen--) {
3179                                                         attrName = attributeFilters[attrFiltersLen].name;
3180
3181                                                         if (attrName in attrs.map) {
3182                                                                 list = matchedAttributes[attrName];
3183
3184                                                                 if (list)
3185                                                                         list.push(newNode);
3186                                                                 else
3187                                                                         matchedAttributes[attrName] = [newNode];
3188                                                         }
3189                                                 }
3190
3191                                                 // Trim whitespace before block
3192                                                 if (blockElements[name])
3193                                                         removeWhitespaceBefore(newNode);
3194
3195                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />
3196                                                 if (!empty)
3197                                                         node = newNode;
3198                                         }
3199                                 },
3200
3201                                 end: function(name) {
3202                                         var textNode, elementRule, text, sibling, tempNode;
3203
3204                                         elementRule = validate ? schema.getElementRule(name) : {};
3205                                         if (elementRule) {
3206                                                 if (blockElements[name]) {
3207                                                         if (!whiteSpaceElements[node.name]) {
3208                                                                 // Trim whitespace at beginning of block
3209                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
3210                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');
3211
3212                                                                         if (text.length > 0) {
3213                                                                                 textNode.value = text;
3214                                                                                 textNode = textNode.next;
3215                                                                         } else {
3216                                                                                 sibling = textNode.next;
3217                                                                                 textNode.remove();
3218                                                                                 textNode = sibling;
3219                                                                         }
3220                                                                 }
3221
3222                                                                 // Trim whitespace at end of block
3223                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
3224                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');
3225
3226                                                                         if (text.length > 0) {
3227                                                                                 textNode.value = text;
3228                                                                                 textNode = textNode.prev;
3229                                                                         } else {
3230                                                                                 sibling = textNode.prev;
3231                                                                                 textNode.remove();
3232                                                                                 textNode = sibling;
3233                                                                         }
3234                                                                 }
3235                                                         }
3236
3237                                                         // Trim start white space
3238                                                         textNode = node.prev;
3239                                                         if (textNode && textNode.type === 3) {
3240                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
3241
3242                                                                 if (text.length > 0)
3243                                                                         textNode.value = text;
3244                                                                 else
3245                                                                         textNode.remove();
3246                                                         }
3247                                                 }
3248
3249                                                 // Handle empty nodes
3250                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {
3251                                                         if (node.isEmpty(nonEmptyElements)) {
3252                                                                 if (elementRule.paddEmpty)
3253                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';
3254                                                                 else {
3255                                                                         // Leave nodes that have a name like <a name="name">
3256                                                                         if (!node.attributes.map.name) {
3257                                                                                 tempNode = node.parent;
3258                                                                                 node.empty().remove();
3259                                                                                 node = tempNode;
3260                                                                                 return;
3261                                                                         }
3262                                                                 }
3263                                                         }
3264                                                 }
3265
3266                                                 node = node.parent;
3267                                         }
3268                                 }
3269                         }, schema);
3270
3271                         rootNode = node = new Node(args.context || settings.root_name, 11);
3272
3273                         parser.parse(html);
3274
3275                         // Fix invalid children or report invalid children in a contextual parsing
3276                         if (validate && invalidChildren.length) {
3277                                 if (!args.context)
3278                                         fixInvalidChildren(invalidChildren);
3279                                 else
3280                                         args.invalid = true;
3281                         }
3282
3283                         // Wrap nodes in the root into block elements if the root is body
3284                         if (rootBlockName && rootNode.name == 'body')
3285                                 addRootBlocks();
3286
3287                         // Run filters only when the contents is valid
3288                         if (!args.invalid) {
3289                                 // Run node filters
3290                                 for (name in matchedNodes) {
3291                                         list = nodeFilters[name];
3292                                         nodes = matchedNodes[name];
3293
3294                                         // Remove already removed children
3295                                         fi = nodes.length;
3296                                         while (fi--) {
3297                                                 if (!nodes[fi].parent)
3298                                                         nodes.splice(fi, 1);
3299                                         }
3300
3301                                         for (i = 0, l = list.length; i < l; i++)
3302                                                 list[i](nodes, name, args);
3303                                 }
3304
3305                                 // Run attribute filters
3306                                 for (i = 0, l = attributeFilters.length; i < l; i++) {
3307                                         list = attributeFilters[i];
3308
3309                                         if (list.name in matchedAttributes) {
3310                                                 nodes = matchedAttributes[list.name];
3311
3312                                                 // Remove already removed children
3313                                                 fi = nodes.length;
3314                                                 while (fi--) {
3315                                                         if (!nodes[fi].parent)
3316                                                                 nodes.splice(fi, 1);
3317                                                 }
3318
3319                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3320                                                         list.callbacks[fi](nodes, list.name, args);
3321                                         }
3322                                 }
3323                         }
3324
3325                         return rootNode;
3326                 };
3327
3328                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
3329                 // make it possible to place the caret inside empty blocks. This logic tries to remove
3330                 // these elements and keep br elements that where intended to be there intact
3331                 if (settings.remove_trailing_brs) {
3332                         self.addNodeFilter('br', function(nodes, name) {
3333                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
3334                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
3335
3336                                 // Remove brs from body element as well
3337                                 blockElements.body = 1;
3338
3339                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
3340                                 for (i = 0; i < l; i++) {
3341                                         node = nodes[i];
3342                                         parent = node.parent;
3343
3344                                         if (blockElements[node.parent.name] && node === parent.lastChild) {
3345                                                 // Loop all nodes to the right of the current node and check for other BR elements
3346                                                 // excluding bookmarks since they are invisible
3347                                                 prev = node.prev;
3348                                                 while (prev) {
3349                                                         prevName = prev.name;
3350
3351                                                         // Ignore bookmarks
3352                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
3353                                                                 // Found a non BR element
3354                                                                 if (prevName !== "br")
3355                                                                         break;
3356         
3357                                                                 // Found another br it's a <br><br> structure then don't remove anything
3358                                                                 if (prevName === 'br') {
3359                                                                         node = null;
3360                                                                         break;
3361                                                                 }
3362                                                         }
3363
3364                                                         prev = prev.prev;
3365                                                 }
3366
3367                                                 if (node) {
3368                                                         node.remove();
3369
3370                                                         // Is the parent to be considered empty after we removed the BR
3371                                                         if (parent.isEmpty(nonEmptyElements)) {
3372                                                                 elementRule = schema.getElementRule(parent.name);
3373
3374                                                                 // Remove or padd the element depending on schema rule
3375                                                                 if (elementRule.removeEmpty)
3376                                                                         parent.remove();
3377                                                                 else if (elementRule.paddEmpty) 
3378                                                                         parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3379                                                         }
3380                                                 }
3381                                         }
3382                                 }
3383                         });
3384                 }
3385         }
3386 })(tinymce);
3387
3388 tinymce.html.Writer = function(settings) {
3389         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3390
3391         settings = settings || {};
3392         indent = settings.indent;
3393         indentBefore = tinymce.makeMap(settings.indent_before || '');
3394         indentAfter = tinymce.makeMap(settings.indent_after || '');
3395         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3396         htmlOutput = settings.element_format == "html";
3397
3398         return {
3399                 start: function(name, attrs, empty) {
3400                         var i, l, attr, value;
3401
3402                         if (indent && indentBefore[name] && html.length > 0) {
3403                                 value = html[html.length - 1];
3404
3405                                 if (value.length > 0 && value !== '\n')
3406                                         html.push('\n');
3407                         }
3408
3409                         html.push('<', name);
3410
3411                         if (attrs) {
3412                                 for (i = 0, l = attrs.length; i < l; i++) {
3413                                         attr = attrs[i];
3414                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3415                                 }
3416                         }
3417
3418                         if (!empty || htmlOutput)
3419                                 html[html.length] = '>';
3420                         else
3421                                 html[html.length] = ' />';
3422
3423                         if (empty && indent && indentAfter[name] && html.length > 0) {
3424                                 value = html[html.length - 1];
3425
3426                                 if (value.length > 0 && value !== '\n')
3427                                         html.push('\n');
3428                         }
3429                 },
3430
3431                 end: function(name) {
3432                         var value;
3433
3434                         /*if (indent && indentBefore[name] && html.length > 0) {
3435                                 value = html[html.length - 1];
3436
3437                                 if (value.length > 0 && value !== '\n')
3438                                         html.push('\n');
3439                         }*/
3440
3441                         html.push('</', name, '>');
3442
3443                         if (indent && indentAfter[name] && html.length > 0) {
3444                                 value = html[html.length - 1];
3445
3446                                 if (value.length > 0 && value !== '\n')
3447                                         html.push('\n');
3448                         }
3449                 },
3450
3451                 text: function(text, raw) {
3452                         if (text.length > 0)
3453                                 html[html.length] = raw ? text : encode(text);
3454                 },
3455
3456                 cdata: function(text) {
3457                         html.push('<![CDATA[', text, ']]>');
3458                 },
3459
3460                 comment: function(text) {
3461                         html.push('<!--', text, '-->');
3462                 },
3463
3464                 pi: function(name, text) {
3465                         if (text)
3466                                 html.push('<?', name, ' ', text, '?>');
3467                         else
3468                                 html.push('<?', name, '?>');
3469
3470                         if (indent)
3471                                 html.push('\n');
3472                 },
3473
3474                 doctype: function(text) {
3475                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3476                 },
3477
3478                 reset: function() {
3479                         html.length = 0;
3480                 },
3481
3482                 getContent: function() {
3483                         return html.join('').replace(/\n$/, '');
3484                 }
3485         };
3486 };
3487
3488 (function(tinymce) {
3489         tinymce.html.Serializer = function(settings, schema) {
3490                 var self = this, writer = new tinymce.html.Writer(settings);
3491
3492                 settings = settings || {};
3493                 settings.validate = "validate" in settings ? settings.validate : true;
3494
3495                 self.schema = schema = schema || new tinymce.html.Schema();
3496                 self.writer = writer;
3497
3498                 self.serialize = function(node) {
3499                         var handlers, validate;
3500
3501                         validate = settings.validate;
3502
3503                         handlers = {
3504                                 // #text
3505                                 3: function(node, raw) {
3506                                         writer.text(node.value, node.raw);
3507                                 },
3508
3509                                 // #comment
3510                                 8: function(node) {
3511                                         writer.comment(node.value);
3512                                 },
3513
3514                                 // Processing instruction
3515                                 7: function(node) {
3516                                         writer.pi(node.name, node.value);
3517                                 },
3518
3519                                 // Doctype
3520                                 10: function(node) {
3521                                         writer.doctype(node.value);
3522                                 },
3523
3524                                 // CDATA
3525                                 4: function(node) {
3526                                         writer.cdata(node.value);
3527                                 },
3528
3529                                 // Document fragment
3530                                 11: function(node) {
3531                                         if ((node = node.firstChild)) {
3532                                                 do {
3533                                                         walk(node);
3534                                                 } while (node = node.next);
3535                                         }
3536                                 }
3537                         };
3538
3539                         writer.reset();
3540
3541                         function walk(node) {
3542                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3543
3544                                 if (!handler) {
3545                                         name = node.name;
3546                                         isEmpty = node.shortEnded;
3547                                         attrs = node.attributes;
3548
3549                                         // Sort attributes
3550                                         if (validate && attrs && attrs.length > 1) {
3551                                                 sortedAttrs = [];
3552                                                 sortedAttrs.map = {};
3553
3554                                                 elementRule = schema.getElementRule(node.name);
3555                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3556                                                         attrName = elementRule.attributesOrder[i];
3557
3558                                                         if (attrName in attrs.map) {
3559                                                                 attrValue = attrs.map[attrName];
3560                                                                 sortedAttrs.map[attrName] = attrValue;
3561                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3562                                                         }
3563                                                 }
3564
3565                                                 for (i = 0, l = attrs.length; i < l; i++) {
3566                                                         attrName = attrs[i].name;
3567
3568                                                         if (!(attrName in sortedAttrs.map)) {
3569                                                                 attrValue = attrs.map[attrName];
3570                                                                 sortedAttrs.map[attrName] = attrValue;
3571                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3572                                                         }
3573                                                 }
3574
3575                                                 attrs = sortedAttrs;
3576                                         }
3577
3578                                         writer.start(node.name, attrs, isEmpty);
3579
3580                                         if (!isEmpty) {
3581                                                 if ((node = node.firstChild)) {
3582                                                         do {
3583                                                                 walk(node);
3584                                                         } while (node = node.next);
3585                                                 }
3586
3587                                                 writer.end(name);
3588                                         }
3589                                 } else
3590                                         handler(node);
3591                         }
3592
3593                         // Serialize element and treat all non elements as fragments
3594                         if (node.type == 1 && !settings.inner)
3595                                 walk(node);
3596                         else
3597                                 handlers[11](node);
3598
3599                         return writer.getContent();
3600                 };
3601         }
3602 })(tinymce);
3603
3604 (function(tinymce) {
3605         // Shorten names
3606         var each = tinymce.each,
3607                 is = tinymce.is,
3608                 isWebKit = tinymce.isWebKit,
3609                 isIE = tinymce.isIE,
3610                 Entities = tinymce.html.Entities,
3611                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
3612                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
3613                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
3614
3615         tinymce.create('tinymce.dom.DOMUtils', {
3616                 doc : null,
3617                 root : null,
3618                 files : null,
3619                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3620                 props : {
3621                         "for" : "htmlFor",
3622                         "class" : "className",
3623                         className : "className",
3624                         checked : "checked",
3625                         disabled : "disabled",
3626                         maxlength : "maxLength",
3627                         readonly : "readOnly",
3628                         selected : "selected",
3629                         value : "value",
3630                         id : "id",
3631                         name : "name",
3632                         type : "type"
3633                 },
3634
3635                 DOMUtils : function(d, s) {
3636                         var t = this, globalStyle, name;
3637
3638                         t.doc = d;
3639                         t.win = window;
3640                         t.files = {};
3641                         t.cssFlicker = false;
3642                         t.counter = 0;
3643                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3644                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3645                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
3646
3647                         t.settings = s = tinymce.extend({
3648                                 keep_values : false,
3649                                 hex_colors : 1
3650                         }, s);
3651                         
3652                         t.schema = s.schema;
3653                         t.styles = new tinymce.html.Styles({
3654                                 url_converter : s.url_converter,
3655                                 url_converter_scope : s.url_converter_scope
3656                         }, s.schema);
3657
3658                         // Fix IE6SP2 flicker and check it failed for pre SP2
3659                         if (tinymce.isIE6) {
3660                                 try {
3661                                         d.execCommand('BackgroundImageCache', false, true);
3662                                 } catch (e) {
3663                                         t.cssFlicker = true;
3664                                 }
3665                         }
3666
3667                         if (isIE && s.schema) {
3668                                 // Add missing HTML 4/5 elements to IE
3669                                 ('abbr article aside audio canvas ' +
3670                                 'details figcaption figure footer ' +
3671                                 'header hgroup mark menu meter nav ' +
3672                                 'output progress section summary ' +
3673                                 'time video').replace(/\w+/g, function(name) {
3674                                         d.createElement(name);
3675                                 });
3676
3677                                 // Create all custom elements
3678                                 for (name in s.schema.getCustomElements()) {
3679                                         d.createElement(name);
3680                                 }
3681                         }
3682
3683                         tinymce.addUnload(t.destroy, t);
3684                 },
3685
3686                 getRoot : function() {
3687                         var t = this, s = t.settings;
3688
3689                         return (s && t.get(s.root_element)) || t.doc.body;
3690                 },
3691
3692                 getViewPort : function(w) {
3693                         var d, b;
3694
3695                         w = !w ? this.win : w;
3696                         d = w.document;
3697                         b = this.boxModel ? d.documentElement : d.body;
3698
3699                         // Returns viewport size excluding scrollbars
3700                         return {
3701                                 x : w.pageXOffset || b.scrollLeft,
3702                                 y : w.pageYOffset || b.scrollTop,
3703                                 w : w.innerWidth || b.clientWidth,
3704                                 h : w.innerHeight || b.clientHeight
3705                         };
3706                 },
3707
3708                 getRect : function(e) {
3709                         var p, t = this, sr;
3710
3711                         e = t.get(e);
3712                         p = t.getPos(e);
3713                         sr = t.getSize(e);
3714
3715                         return {
3716                                 x : p.x,
3717                                 y : p.y,
3718                                 w : sr.w,
3719                                 h : sr.h
3720                         };
3721                 },
3722
3723                 getSize : function(e) {
3724                         var t = this, w, h;
3725
3726                         e = t.get(e);
3727                         w = t.getStyle(e, 'width');
3728                         h = t.getStyle(e, 'height');
3729
3730                         // Non pixel value, then force offset/clientWidth
3731                         if (w.indexOf('px') === -1)
3732                                 w = 0;
3733
3734                         // Non pixel value, then force offset/clientWidth
3735                         if (h.indexOf('px') === -1)
3736                                 h = 0;
3737
3738                         return {
3739                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3740                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
3741                         };
3742                 },
3743
3744                 getParent : function(n, f, r) {
3745                         return this.getParents(n, f, r, false);
3746                 },
3747
3748                 getParents : function(n, f, r, c) {
3749                         var t = this, na, se = t.settings, o = [];
3750
3751                         n = t.get(n);
3752                         c = c === undefined;
3753
3754                         if (se.strict_root)
3755                                 r = r || t.getRoot();
3756
3757                         // Wrap node name as func
3758                         if (is(f, 'string')) {
3759                                 na = f;
3760
3761                                 if (f === '*') {
3762                                         f = function(n) {return n.nodeType == 1;};
3763                                 } else {
3764                                         f = function(n) {
3765                                                 return t.is(n, na);
3766                                         };
3767                                 }
3768                         }
3769
3770                         while (n) {
3771                                 if (n == r || !n.nodeType || n.nodeType === 9)
3772                                         break;
3773
3774                                 if (!f || f(n)) {
3775                                         if (c)
3776                                                 o.push(n);
3777                                         else
3778                                                 return n;
3779                                 }
3780
3781                                 n = n.parentNode;
3782                         }
3783
3784                         return c ? o : null;
3785                 },
3786
3787                 get : function(e) {
3788                         var n;
3789
3790                         if (e && this.doc && typeof(e) == 'string') {
3791                                 n = e;
3792                                 e = this.doc.getElementById(e);
3793
3794                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3795                                 if (e && e.id !== n)
3796                                         return this.doc.getElementsByName(n)[1];
3797                         }
3798
3799                         return e;
3800                 },
3801
3802                 getNext : function(node, selector) {
3803                         return this._findSib(node, selector, 'nextSibling');
3804                 },
3805
3806                 getPrev : function(node, selector) {
3807                         return this._findSib(node, selector, 'previousSibling');
3808                 },
3809
3810
3811                 add : function(p, n, a, h, c) {
3812                         var t = this;
3813
3814                         return this.run(p, function(p) {
3815                                 var e, k;
3816
3817                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
3818                                 t.setAttribs(e, a);
3819
3820                                 if (h) {
3821                                         if (h.nodeType)
3822                                                 e.appendChild(h);
3823                                         else
3824                                                 t.setHTML(e, h);
3825                                 }
3826
3827                                 return !c ? p.appendChild(e) : e;
3828                         });
3829                 },
3830
3831                 create : function(n, a, h) {
3832                         return this.add(this.doc.createElement(n), n, a, h, 1);
3833                 },
3834
3835                 createHTML : function(n, a, h) {
3836                         var o = '', t = this, k;
3837
3838                         o += '<' + n;
3839
3840                         for (k in a) {
3841                                 if (a.hasOwnProperty(k))
3842                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
3843                         }
3844
3845                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3846                         if (typeof(h) != "undefined")
3847                                 return o + '>' + h + '</' + n + '>';
3848
3849                         return o + ' />';
3850                 },
3851
3852                 remove : function(node, keep_children) {
3853                         return this.run(node, function(node) {
3854                                 var child, parent = node.parentNode;
3855
3856                                 if (!parent)
3857                                         return null;
3858
3859                                 if (keep_children) {
3860                                         while (child = node.firstChild) {
3861                                                 // IE 8 will crash if you don't remove completely empty text nodes
3862                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3863                                                         parent.insertBefore(child, node);
3864                                                 else
3865                                                         node.removeChild(child);
3866                                         }
3867                                 }
3868
3869                                 return parent.removeChild(node);
3870                         });
3871                 },
3872
3873                 setStyle : function(n, na, v) {
3874                         var t = this;
3875
3876                         return t.run(n, function(e) {
3877                                 var s, i;
3878
3879                                 s = e.style;
3880
3881                                 // Camelcase it, if needed
3882                                 na = na.replace(/-(\D)/g, function(a, b){
3883                                         return b.toUpperCase();
3884                                 });
3885
3886                                 // Default px suffix on these
3887                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3888                                         v += 'px';
3889
3890                                 switch (na) {
3891                                         case 'opacity':
3892                                                 // IE specific opacity
3893                                                 if (isIE) {
3894                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3895
3896                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
3897                                                                 s.display = 'inline-block';
3898                                                 }
3899
3900                                                 // Fix for older browsers
3901                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3902                                                 break;
3903
3904                                         case 'float':
3905                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
3906                                                 break;
3907                                         
3908                                         default:
3909                                                 s[na] = v || '';
3910                                 }
3911
3912                                 // Force update of the style data
3913                                 if (t.settings.update_styles)
3914                                         t.setAttrib(e, 'data-mce-style');
3915                         });
3916                 },
3917
3918                 getStyle : function(n, na, c) {
3919                         n = this.get(n);
3920
3921                         if (!n)
3922                                 return;
3923
3924                         // Gecko
3925                         if (this.doc.defaultView && c) {
3926                                 // Remove camelcase
3927                                 na = na.replace(/[A-Z]/g, function(a){
3928                                         return '-' + a;
3929                                 });
3930
3931                                 try {
3932                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3933                                 } catch (ex) {
3934                                         // Old safari might fail
3935                                         return null;
3936                                 }
3937                         }
3938
3939                         // Camelcase it, if needed
3940                         na = na.replace(/-(\D)/g, function(a, b){
3941                                 return b.toUpperCase();
3942                         });
3943
3944                         if (na == 'float')
3945                                 na = isIE ? 'styleFloat' : 'cssFloat';
3946
3947                         // IE & Opera
3948                         if (n.currentStyle && c)
3949                                 return n.currentStyle[na];
3950
3951                         return n.style ? n.style[na] : undefined;
3952                 },
3953
3954                 setStyles : function(e, o) {
3955                         var t = this, s = t.settings, ol;
3956
3957                         ol = s.update_styles;
3958                         s.update_styles = 0;
3959
3960                         each(o, function(v, n) {
3961                                 t.setStyle(e, n, v);
3962                         });
3963
3964                         // Update style info
3965                         s.update_styles = ol;
3966                         if (s.update_styles)
3967                                 t.setAttrib(e, s.cssText);
3968                 },
3969
3970                 removeAllAttribs: function(e) {
3971                         return this.run(e, function(e) {
3972                                 var i, attrs = e.attributes;
3973                                 for (i = attrs.length - 1; i >= 0; i--) {
3974                                         e.removeAttributeNode(attrs.item(i));
3975                                 }
3976                         });
3977                 },
3978
3979                 setAttrib : function(e, n, v) {
3980                         var t = this;
3981
3982                         // Whats the point
3983                         if (!e || !n)
3984                                 return;
3985
3986                         // Strict XML mode
3987                         if (t.settings.strict)
3988                                 n = n.toLowerCase();
3989
3990                         return this.run(e, function(e) {
3991                                 var s = t.settings;
3992
3993                                 switch (n) {
3994                                         case "style":
3995                                                 if (!is(v, 'string')) {
3996                                                         each(v, function(v, n) {
3997                                                                 t.setStyle(e, n, v);
3998                                                         });
3999
4000                                                         return;
4001                                                 }
4002
4003                                                 // No mce_style for elements with these since they might get resized by the user
4004                                                 if (s.keep_values) {
4005                                                         if (v && !t._isRes(v))
4006                                                                 e.setAttribute('data-mce-style', v, 2);
4007                                                         else
4008                                                                 e.removeAttribute('data-mce-style', 2);
4009                                                 }
4010
4011                                                 e.style.cssText = v;
4012                                                 break;
4013
4014                                         case "class":
4015                                                 e.className = v || ''; // Fix IE null bug
4016                                                 break;
4017
4018                                         case "src":
4019                                         case "href":
4020                                                 if (s.keep_values) {
4021                                                         if (s.url_converter)
4022                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
4023
4024                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
4025                                                 }
4026
4027                                                 break;
4028
4029                                         case "shape":
4030                                                 e.setAttribute('data-mce-style', v);
4031                                                 break;
4032                                 }
4033
4034                                 if (is(v) && v !== null && v.length !== 0)
4035                                         e.setAttribute(n, '' + v, 2);
4036                                 else
4037                                         e.removeAttribute(n, 2);
4038                         });
4039                 },
4040
4041                 setAttribs : function(e, o) {
4042                         var t = this;
4043
4044                         return this.run(e, function(e) {
4045                                 each(o, function(v, n) {
4046                                         t.setAttrib(e, n, v);
4047                                 });
4048                         });
4049                 },
4050
4051                 getAttrib : function(e, n, dv) {
4052                         var v, t = this, undef;
4053
4054                         e = t.get(e);
4055
4056                         if (!e || e.nodeType !== 1)
4057                                 return dv === undef ? false : dv;
4058
4059                         if (!is(dv))
4060                                 dv = '';
4061
4062                         // Try the mce variant for these
4063                         if (/^(src|href|style|coords|shape)$/.test(n)) {
4064                                 v = e.getAttribute("data-mce-" + n);
4065
4066                                 if (v)
4067                                         return v;
4068                         }
4069
4070                         if (isIE && t.props[n]) {
4071                                 v = e[t.props[n]];
4072                                 v = v && v.nodeValue ? v.nodeValue : v;
4073                         }
4074
4075                         if (!v)
4076                                 v = e.getAttribute(n, 2);
4077
4078                         // Check boolean attribs
4079                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
4080                                 if (e[t.props[n]] === true && v === '')
4081                                         return n;
4082
4083                                 return v ? n : '';
4084                         }
4085
4086                         // Inner input elements will override attributes on form elements
4087                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
4088                                 return e.getAttributeNode(n).nodeValue;
4089
4090                         if (n === 'style') {
4091                                 v = v || e.style.cssText;
4092
4093                                 if (v) {
4094                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
4095
4096                                         if (t.settings.keep_values && !t._isRes(v))
4097                                                 e.setAttribute('data-mce-style', v);
4098                                 }
4099                         }
4100
4101                         // Remove Apple and WebKit stuff
4102                         if (isWebKit && n === "class" && v)
4103                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
4104
4105                         // Handle IE issues
4106                         if (isIE) {
4107                                 switch (n) {
4108                                         case 'rowspan':
4109                                         case 'colspan':
4110                                                 // IE returns 1 as default value
4111                                                 if (v === 1)
4112                                                         v = '';
4113
4114                                                 break;
4115
4116                                         case 'size':
4117                                                 // IE returns +0 as default value for size
4118                                                 if (v === '+0' || v === 20 || v === 0)
4119                                                         v = '';
4120
4121                                                 break;
4122
4123                                         case 'width':
4124                                         case 'height':
4125                                         case 'vspace':
4126                                         case 'checked':
4127                                         case 'disabled':
4128                                         case 'readonly':
4129                                                 if (v === 0)
4130                                                         v = '';
4131
4132                                                 break;
4133
4134                                         case 'hspace':
4135                                                 // IE returns -1 as default value
4136                                                 if (v === -1)
4137                                                         v = '';
4138
4139                                                 break;
4140
4141                                         case 'maxlength':
4142                                         case 'tabindex':
4143                                                 // IE returns default value
4144                                                 if (v === 32768 || v === 2147483647 || v === '32768')
4145                                                         v = '';
4146
4147                                                 break;
4148
4149                                         case 'multiple':
4150                                         case 'compact':
4151                                         case 'noshade':
4152                                         case 'nowrap':
4153                                                 if (v === 65535)
4154                                                         return n;
4155
4156                                                 return dv;
4157
4158                                         case 'shape':
4159                                                 v = v.toLowerCase();
4160                                                 break;
4161
4162                                         default:
4163                                                 // IE has odd anonymous function for event attributes
4164                                                 if (n.indexOf('on') === 0 && v)
4165                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
4166                                 }
4167                         }
4168
4169                         return (v !== undef && v !== null && v !== '') ? '' + v : dv;
4170                 },
4171
4172                 getPos : function(n, ro) {
4173                         var t = this, x = 0, y = 0, e, d = t.doc, r;
4174
4175                         n = t.get(n);
4176                         ro = ro || d.body;
4177
4178                         if (n) {
4179                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
4180                                 if (n.getBoundingClientRect) {
4181                                         n = n.getBoundingClientRect();
4182                                         e = t.boxModel ? d.documentElement : d.body;
4183
4184                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
4185                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
4186                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
4187                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
4188
4189                                         return {x : x, y : y};
4190                                 }
4191
4192                                 r = n;
4193                                 while (r && r != ro && r.nodeType) {
4194                                         x += r.offsetLeft || 0;
4195                                         y += r.offsetTop || 0;
4196                                         r = r.offsetParent;
4197                                 }
4198
4199                                 r = n.parentNode;
4200                                 while (r && r != ro && r.nodeType) {
4201                                         x -= r.scrollLeft || 0;
4202                                         y -= r.scrollTop || 0;
4203                                         r = r.parentNode;
4204                                 }
4205                         }
4206
4207                         return {x : x, y : y};
4208                 },
4209
4210                 parseStyle : function(st) {
4211                         return this.styles.parse(st);
4212                 },
4213
4214                 serializeStyle : function(o, name) {
4215                         return this.styles.serialize(o, name);
4216                 },
4217
4218                 loadCSS : function(u) {
4219                         var t = this, d = t.doc, head;
4220
4221                         if (!u)
4222                                 u = '';
4223
4224                         head = t.select('head')[0];
4225
4226                         each(u.split(','), function(u) {
4227                                 var link;
4228
4229                                 if (t.files[u])
4230                                         return;
4231
4232                                 t.files[u] = true;
4233                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
4234
4235                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
4236                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
4237                                 // It's ugly but it seems to work fine.
4238                                 if (isIE && d.documentMode && d.recalc) {
4239                                         link.onload = function() {
4240                                                 if (d.recalc)
4241                                                         d.recalc();
4242
4243                                                 link.onload = null;
4244                                         };
4245                                 }
4246
4247                                 head.appendChild(link);
4248                         });
4249                 },
4250
4251                 addClass : function(e, c) {
4252                         return this.run(e, function(e) {
4253                                 var o;
4254
4255                                 if (!c)
4256                                         return 0;
4257
4258                                 if (this.hasClass(e, c))
4259                                         return e.className;
4260
4261                                 o = this.removeClass(e, c);
4262
4263                                 return e.className = (o != '' ? (o + ' ') : '') + c;
4264                         });
4265                 },
4266
4267                 removeClass : function(e, c) {
4268                         var t = this, re;
4269
4270                         return t.run(e, function(e) {
4271                                 var v;
4272
4273                                 if (t.hasClass(e, c)) {
4274                                         if (!re)
4275                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
4276
4277                                         v = e.className.replace(re, ' ');
4278                                         v = tinymce.trim(v != ' ' ? v : '');
4279
4280                                         e.className = v;
4281
4282                                         // Empty class attr
4283                                         if (!v) {
4284                                                 e.removeAttribute('class');
4285                                                 e.removeAttribute('className');
4286                                         }
4287
4288                                         return v;
4289                                 }
4290
4291                                 return e.className;
4292                         });
4293                 },
4294
4295                 hasClass : function(n, c) {
4296                         n = this.get(n);
4297
4298                         if (!n || !c)
4299                                 return false;
4300
4301                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
4302                 },
4303
4304                 show : function(e) {
4305                         return this.setStyle(e, 'display', 'block');
4306                 },
4307
4308                 hide : function(e) {
4309                         return this.setStyle(e, 'display', 'none');
4310                 },
4311
4312                 isHidden : function(e) {
4313                         e = this.get(e);
4314
4315                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
4316                 },
4317
4318                 uniqueId : function(p) {
4319                         return (!p ? 'mce_' : p) + (this.counter++);
4320                 },
4321
4322                 setHTML : function(element, html) {
4323                         var self = this;
4324
4325                         return self.run(element, function(element) {
4326                                 if (isIE) {
4327                                         // Remove all child nodes, IE keeps empty text nodes in DOM
4328                                         while (element.firstChild)
4329                                                 element.removeChild(element.firstChild);
4330
4331                                         try {
4332                                                 // IE will remove comments from the beginning
4333                                                 // unless you padd the contents with something
4334                                                 element.innerHTML = '<br />' + html;
4335                                                 element.removeChild(element.firstChild);
4336                                         } catch (ex) {
4337                                                 // 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
4338                                                 // This seems to fix this problem
4339
4340                                                 // Create new div with HTML contents and a BR infront to keep comments
4341                                                 element = self.create('div');
4342                                                 element.innerHTML = '<br />' + html;
4343
4344                                                 // Add all children from div to target
4345                                                 each (element.childNodes, function(node, i) {
4346                                                         // Skip br element
4347                                                         if (i)
4348                                                                 element.appendChild(node);
4349                                                 });
4350                                         }
4351                                 } else
4352                                         element.innerHTML = html;
4353
4354                                 return html;
4355                         });
4356                 },
4357
4358                 getOuterHTML : function(elm) {
4359                         var doc, self = this;
4360
4361                         elm = self.get(elm);
4362
4363                         if (!elm)
4364                                 return null;
4365
4366                         if (elm.nodeType === 1 && self.hasOuterHTML)
4367                                 return elm.outerHTML;
4368
4369                         doc = (elm.ownerDocument || self.doc).createElement("body");
4370                         doc.appendChild(elm.cloneNode(true));
4371
4372                         return doc.innerHTML;
4373                 },
4374
4375                 setOuterHTML : function(e, h, d) {
4376                         var t = this;
4377
4378                         function setHTML(e, h, d) {
4379                                 var n, tp;
4380
4381                                 tp = d.createElement("body");
4382                                 tp.innerHTML = h;
4383
4384                                 n = tp.lastChild;
4385                                 while (n) {
4386                                         t.insertAfter(n.cloneNode(true), e);
4387                                         n = n.previousSibling;
4388                                 }
4389
4390                                 t.remove(e);
4391                         };
4392
4393                         return this.run(e, function(e) {
4394                                 e = t.get(e);
4395
4396                                 // Only set HTML on elements
4397                                 if (e.nodeType == 1) {
4398                                         d = d || e.ownerDocument || t.doc;
4399
4400                                         if (isIE) {
4401                                                 try {
4402                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
4403                                                         if (isIE && e.nodeType == 1)
4404                                                                 e.outerHTML = h;
4405                                                         else
4406                                                                 setHTML(e, h, d);
4407                                                 } catch (ex) {
4408                                                         // Fix for unknown runtime error
4409                                                         setHTML(e, h, d);
4410                                                 }
4411                                         } else
4412                                                 setHTML(e, h, d);
4413                                 }
4414                         });
4415                 },
4416
4417                 decode : Entities.decode,
4418
4419                 encode : Entities.encodeAllRaw,
4420
4421                 insertAfter : function(node, reference_node) {
4422                         reference_node = this.get(reference_node);
4423
4424                         return this.run(node, function(node) {
4425                                 var parent, nextSibling;
4426
4427                                 parent = reference_node.parentNode;
4428                                 nextSibling = reference_node.nextSibling;
4429
4430                                 if (nextSibling)
4431                                         parent.insertBefore(node, nextSibling);
4432                                 else
4433                                         parent.appendChild(node);
4434
4435                                 return node;
4436                         });
4437                 },
4438
4439                 isBlock : function(node) {
4440                         var type = node.nodeType;
4441
4442                         // If it's a node then check the type and use the nodeName
4443                         if (type)
4444                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
4445
4446                         return !!blockElementsMap[node];
4447                 },
4448
4449                 replace : function(n, o, k) {
4450                         var t = this;
4451
4452                         if (is(o, 'array'))
4453                                 n = n.cloneNode(true);
4454
4455                         return t.run(o, function(o) {
4456                                 if (k) {
4457                                         each(tinymce.grep(o.childNodes), function(c) {
4458                                                 n.appendChild(c);
4459                                         });
4460                                 }
4461
4462                                 return o.parentNode.replaceChild(n, o);
4463                         });
4464                 },
4465
4466                 rename : function(elm, name) {
4467                         var t = this, newElm;
4468
4469                         if (elm.nodeName != name.toUpperCase()) {
4470                                 // Rename block element
4471                                 newElm = t.create(name);
4472
4473                                 // Copy attribs to new block
4474                                 each(t.getAttribs(elm), function(attr_node) {
4475                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4476                                 });
4477
4478                                 // Replace block
4479                                 t.replace(newElm, elm, 1);
4480                         }
4481
4482                         return newElm || elm;
4483                 },
4484
4485                 findCommonAncestor : function(a, b) {
4486                         var ps = a, pe;
4487
4488                         while (ps) {
4489                                 pe = b;
4490
4491                                 while (pe && ps != pe)
4492                                         pe = pe.parentNode;
4493
4494                                 if (ps == pe)
4495                                         break;
4496
4497                                 ps = ps.parentNode;
4498                         }
4499
4500                         if (!ps && a.ownerDocument)
4501                                 return a.ownerDocument.documentElement;
4502
4503                         return ps;
4504                 },
4505
4506                 toHex : function(s) {
4507                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4508
4509                         function hex(s) {
4510                                 s = parseInt(s).toString(16);
4511
4512                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
4513                         };
4514
4515                         if (c) {
4516                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4517
4518                                 return s;
4519                         }
4520
4521                         return s;
4522                 },
4523
4524                 getClasses : function() {
4525                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4526
4527                         if (t.classes)
4528                                 return t.classes;
4529
4530                         function addClasses(s) {
4531                                 // IE style imports
4532                                 each(s.imports, function(r) {
4533                                         addClasses(r);
4534                                 });
4535
4536                                 each(s.cssRules || s.rules, function(r) {
4537                                         // Real type or fake it on IE
4538                                         switch (r.type || 1) {
4539                                                 // Rule
4540                                                 case 1:
4541                                                         if (r.selectorText) {
4542                                                                 each(r.selectorText.split(','), function(v) {
4543                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
4544
4545                                                                         // Is internal or it doesn't contain a class
4546                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4547                                                                                 return;
4548
4549                                                                         // Remove everything but class name
4550                                                                         ov = v;
4551                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
4552
4553                                                                         // Filter classes
4554                                                                         if (f && !(v = f(v, ov)))
4555                                                                                 return;
4556
4557                                                                         if (!lo[v]) {
4558                                                                                 cl.push({'class' : v});
4559                                                                                 lo[v] = 1;
4560                                                                         }
4561                                                                 });
4562                                                         }
4563                                                         break;
4564
4565                                                 // Import
4566                                                 case 3:
4567                                                         addClasses(r.styleSheet);
4568                                                         break;
4569                                         }
4570                                 });
4571                         };
4572
4573                         try {
4574                                 each(t.doc.styleSheets, addClasses);
4575                         } catch (ex) {
4576                                 // Ignore
4577                         }
4578
4579                         if (cl.length > 0)
4580                                 t.classes = cl;
4581
4582                         return cl;
4583                 },
4584
4585                 run : function(e, f, s) {
4586                         var t = this, o;
4587
4588                         if (t.doc && typeof(e) === 'string')
4589                                 e = t.get(e);
4590
4591                         if (!e)
4592                                 return false;
4593
4594                         s = s || this;
4595                         if (!e.nodeType && (e.length || e.length === 0)) {
4596                                 o = [];
4597
4598                                 each(e, function(e, i) {
4599                                         if (e) {
4600                                                 if (typeof(e) == 'string')
4601                                                         e = t.doc.getElementById(e);
4602
4603                                                 o.push(f.call(s, e, i));
4604                                         }
4605                                 });
4606
4607                                 return o;
4608                         }
4609
4610                         return f.call(s, e);
4611                 },
4612
4613                 getAttribs : function(n) {
4614                         var o;
4615
4616                         n = this.get(n);
4617
4618                         if (!n)
4619                                 return [];
4620
4621                         if (isIE) {
4622                                 o = [];
4623
4624                                 // Object will throw exception in IE
4625                                 if (n.nodeName == 'OBJECT')
4626                                         return n.attributes;
4627
4628                                 // IE doesn't keep the selected attribute if you clone option elements
4629                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4630                                         o.push({specified : 1, nodeName : 'selected'});
4631
4632                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
4633                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
4634                                         o.push({specified : 1, nodeName : a});
4635                                 });
4636
4637                                 return o;
4638                         }
4639
4640                         return n.attributes;
4641                 },
4642
4643                 isEmpty : function(node, elements) {
4644                         var self = this, i, attributes, type, walker, name;
4645
4646                         node = node.firstChild;
4647                         if (node) {
4648                                 walker = new tinymce.dom.TreeWalker(node);
4649                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4650
4651                                 do {
4652                                         type = node.nodeType;
4653
4654                                         if (type === 1) {
4655                                                 // Ignore bogus elements
4656                                                 if (node.getAttribute('data-mce-bogus'))
4657                                                         continue;
4658
4659                                                 // Keep empty elements like <img />
4660                                                 if (elements && elements[node.nodeName.toLowerCase()])
4661                                                         return false;
4662
4663                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
4664                                                 attributes = self.getAttribs(node);
4665                                                 i = node.attributes.length;
4666                                                 while (i--) {
4667                                                         name = node.attributes[i].nodeName;
4668                                                         if (name === "name" || name === 'data-mce-bookmark')
4669                                                                 return false;
4670                                                 }
4671                                         }
4672
4673                                         // Keep non whitespace text nodes
4674                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4675                                                 return false;
4676                                 } while (node = walker.next());
4677                         }
4678
4679                         return true;
4680                 },
4681
4682                 destroy : function(s) {
4683                         var t = this;
4684
4685                         if (t.events)
4686                                 t.events.destroy();
4687
4688                         t.win = t.doc = t.root = t.events = null;
4689
4690                         // Manual destroy then remove unload handler
4691                         if (!s)
4692                                 tinymce.removeUnload(t.destroy);
4693                 },
4694
4695                 createRng : function() {
4696                         var d = this.doc;
4697
4698                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4699                 },
4700
4701                 nodeIndex : function(node, normalized) {
4702                         var idx = 0, lastNodeType, lastNode, nodeType;
4703
4704                         if (node) {
4705                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4706                                         nodeType = node.nodeType;
4707
4708                                         // Normalize text nodes
4709                                         if (normalized && nodeType == 3) {
4710                                                 if (nodeType == lastNodeType || !node.nodeValue.length)
4711                                                         continue;
4712                                         }
4713                                         idx++;
4714                                         lastNodeType = nodeType;
4715                                 }
4716                         }
4717
4718                         return idx;
4719                 },
4720
4721                 split : function(pe, e, re) {
4722                         var t = this, r = t.createRng(), bef, aft, pa;
4723
4724                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
4725                         // but we don't want that in our code since it serves no purpose for the end user
4726                         // For example if this is chopped:
4727                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4728                         // would produce:
4729                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4730                         // this function will then trim of empty edges and produce:
4731                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4732                         function trim(node) {
4733                                 var i, children = node.childNodes, type = node.nodeType;
4734
4735                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4736                                         return;
4737
4738                                 for (i = children.length - 1; i >= 0; i--)
4739                                         trim(children[i]);
4740
4741                                 if (type != 9) {
4742                                         // Keep non whitespace text nodes
4743                                         if (type == 3 && node.nodeValue.length > 0) {
4744                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4745                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4746                                                         return;
4747                                         } else if (type == 1) {
4748                                                 // If the only child is a bookmark then move it up
4749                                                 children = node.childNodes;
4750                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
4751                                                         node.parentNode.insertBefore(children[0], node);
4752
4753                                                 // Keep non empty elements or img, hr etc
4754                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4755                                                         return;
4756                                         }
4757
4758                                         t.remove(node);
4759                                 }
4760
4761                                 return node;
4762                         };
4763
4764                         if (pe && e) {
4765                                 // Get before chunk
4766                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
4767                                 r.setEnd(e.parentNode, t.nodeIndex(e));
4768                                 bef = r.extractContents();
4769
4770                                 // Get after chunk
4771                                 r = t.createRng();
4772                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
4773                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
4774                                 aft = r.extractContents();
4775
4776                                 // Insert before chunk
4777                                 pa = pe.parentNode;
4778                                 pa.insertBefore(trim(bef), pe);
4779
4780                                 // Insert middle chunk
4781                                 if (re)
4782                                         pa.replaceChild(re, e);
4783                                 else
4784                                         pa.insertBefore(e, pe);
4785
4786                                 // Insert after chunk
4787                                 pa.insertBefore(trim(aft), pe);
4788                                 t.remove(pe);
4789
4790                                 return re || e;
4791                         }
4792                 },
4793
4794                 bind : function(target, name, func, scope) {
4795                         var t = this;
4796
4797                         if (!t.events)
4798                                 t.events = new tinymce.dom.EventUtils();
4799
4800                         return t.events.add(target, name, func, scope || this);
4801                 },
4802
4803                 unbind : function(target, name, func) {
4804                         var t = this;
4805
4806                         if (!t.events)
4807                                 t.events = new tinymce.dom.EventUtils();
4808
4809                         return t.events.remove(target, name, func);
4810                 },
4811
4812
4813                 _findSib : function(node, selector, name) {
4814                         var t = this, f = selector;
4815
4816                         if (node) {
4817                                 // If expression make a function of it using is
4818                                 if (is(f, 'string')) {
4819                                         f = function(node) {
4820                                                 return t.is(node, selector);
4821                                         };
4822                                 }
4823
4824                                 // Loop all siblings
4825                                 for (node = node[name]; node; node = node[name]) {
4826                                         if (f(node))
4827                                                 return node;
4828                                 }
4829                         }
4830
4831                         return null;
4832                 },
4833
4834                 _isRes : function(c) {
4835                         // Is live resizble element
4836                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
4837                 }
4838
4839                 /*
4840                 walk : function(n, f, s) {
4841                         var d = this.doc, w;
4842
4843                         if (d.createTreeWalker) {
4844                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4845
4846                                 while ((n = w.nextNode()) != null)
4847                                         f.call(s || this, n);
4848                         } else
4849                                 tinymce.walk(n, f, 'childNodes', s);
4850                 }
4851                 */
4852
4853                 /*
4854                 toRGB : function(s) {
4855                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4856
4857                         if (c) {
4858                                 // #FFF -> #FFFFFF
4859                                 if (!is(c[3]))
4860                                         c[3] = c[2] = c[1];
4861
4862                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4863                         }
4864
4865                         return s;
4866                 }
4867                 */
4868         });
4869
4870         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
4871 })(tinymce);
4872
4873 (function(ns) {
4874         // Range constructor
4875         function Range(dom) {
4876                 var t = this,
4877                         doc = dom.doc,
4878                         EXTRACT = 0,
4879                         CLONE = 1,
4880                         DELETE = 2,
4881                         TRUE = true,
4882                         FALSE = false,
4883                         START_OFFSET = 'startOffset',
4884                         START_CONTAINER = 'startContainer',
4885                         END_CONTAINER = 'endContainer',
4886                         END_OFFSET = 'endOffset',
4887                         extend = tinymce.extend,
4888                         nodeIndex = dom.nodeIndex;
4889
4890                 extend(t, {
4891                         // Inital states
4892                         startContainer : doc,
4893                         startOffset : 0,
4894                         endContainer : doc,
4895                         endOffset : 0,
4896                         collapsed : TRUE,
4897                         commonAncestorContainer : doc,
4898
4899                         // Range constants
4900                         START_TO_START : 0,
4901                         START_TO_END : 1,
4902                         END_TO_END : 2,
4903                         END_TO_START : 3,
4904
4905                         // Public methods
4906                         setStart : setStart,
4907                         setEnd : setEnd,
4908                         setStartBefore : setStartBefore,
4909                         setStartAfter : setStartAfter,
4910                         setEndBefore : setEndBefore,
4911                         setEndAfter : setEndAfter,
4912                         collapse : collapse,
4913                         selectNode : selectNode,
4914                         selectNodeContents : selectNodeContents,
4915                         compareBoundaryPoints : compareBoundaryPoints,
4916                         deleteContents : deleteContents,
4917                         extractContents : extractContents,
4918                         cloneContents : cloneContents,
4919                         insertNode : insertNode,
4920                         surroundContents : surroundContents,
4921                         cloneRange : cloneRange
4922                 });
4923
4924                 function setStart(n, o) {
4925                         _setEndPoint(TRUE, n, o);
4926                 };
4927
4928                 function setEnd(n, o) {
4929                         _setEndPoint(FALSE, n, o);
4930                 };
4931
4932                 function setStartBefore(n) {
4933                         setStart(n.parentNode, nodeIndex(n));
4934                 };
4935
4936                 function setStartAfter(n) {
4937                         setStart(n.parentNode, nodeIndex(n) + 1);
4938                 };
4939
4940                 function setEndBefore(n) {
4941                         setEnd(n.parentNode, nodeIndex(n));
4942                 };
4943
4944                 function setEndAfter(n) {
4945                         setEnd(n.parentNode, nodeIndex(n) + 1);
4946                 };
4947
4948                 function collapse(ts) {
4949                         if (ts) {
4950                                 t[END_CONTAINER] = t[START_CONTAINER];
4951                                 t[END_OFFSET] = t[START_OFFSET];
4952                         } else {
4953                                 t[START_CONTAINER] = t[END_CONTAINER];
4954                                 t[START_OFFSET] = t[END_OFFSET];
4955                         }
4956
4957                         t.collapsed = TRUE;
4958                 };
4959
4960                 function selectNode(n) {
4961                         setStartBefore(n);
4962                         setEndAfter(n);
4963                 };
4964
4965                 function selectNodeContents(n) {
4966                         setStart(n, 0);
4967                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
4968                 };
4969
4970                 function compareBoundaryPoints(h, r) {
4971                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
4972                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
4973
4974                         // Check START_TO_START
4975                         if (h === 0)
4976                                 return _compareBoundaryPoints(sc, so, rsc, rso);
4977         
4978                         // Check START_TO_END
4979                         if (h === 1)
4980                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
4981         
4982                         // Check END_TO_END
4983                         if (h === 2)
4984                                 return _compareBoundaryPoints(ec, eo, rec, reo);
4985         
4986                         // Check END_TO_START
4987                         if (h === 3) 
4988                                 return _compareBoundaryPoints(sc, so, rec, reo);
4989                 };
4990
4991                 function deleteContents() {
4992                         _traverse(DELETE);
4993                 };
4994
4995                 function extractContents() {
4996                         return _traverse(EXTRACT);
4997                 };
4998
4999                 function cloneContents() {
5000                         return _traverse(CLONE);
5001                 };
5002
5003                 function insertNode(n) {
5004                         var startContainer = this[START_CONTAINER],
5005                                 startOffset = this[START_OFFSET], nn, o;
5006
5007                         // Node is TEXT_NODE or CDATA
5008                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
5009                                 if (!startOffset) {
5010                                         // At the start of text
5011                                         startContainer.parentNode.insertBefore(n, startContainer);
5012                                 } else if (startOffset >= startContainer.nodeValue.length) {
5013                                         // At the end of text
5014                                         dom.insertAfter(n, startContainer);
5015                                 } else {
5016                                         // Middle, need to split
5017                                         nn = startContainer.splitText(startOffset);
5018                                         startContainer.parentNode.insertBefore(n, nn);
5019                                 }
5020                         } else {
5021                                 // Insert element node
5022                                 if (startContainer.childNodes.length > 0)
5023                                         o = startContainer.childNodes[startOffset];
5024
5025                                 if (o)
5026                                         startContainer.insertBefore(n, o);
5027                                 else
5028                                         startContainer.appendChild(n);
5029                         }
5030                 };
5031
5032                 function surroundContents(n) {
5033                         var f = t.extractContents();
5034
5035                         t.insertNode(n);
5036                         n.appendChild(f);
5037                         t.selectNode(n);
5038                 };
5039
5040                 function cloneRange() {
5041                         return extend(new Range(dom), {
5042                                 startContainer : t[START_CONTAINER],
5043                                 startOffset : t[START_OFFSET],
5044                                 endContainer : t[END_CONTAINER],
5045                                 endOffset : t[END_OFFSET],
5046                                 collapsed : t.collapsed,
5047                                 commonAncestorContainer : t.commonAncestorContainer
5048                         });
5049                 };
5050
5051                 // Private methods
5052
5053                 function _getSelectedNode(container, offset) {
5054                         var child;
5055
5056                         if (container.nodeType == 3 /* TEXT_NODE */)
5057                                 return container;
5058
5059                         if (offset < 0)
5060                                 return container;
5061
5062                         child = container.firstChild;
5063                         while (child && offset > 0) {
5064                                 --offset;
5065                                 child = child.nextSibling;
5066                         }
5067
5068                         if (child)
5069                                 return child;
5070
5071                         return container;
5072                 };
5073
5074                 function _isCollapsed() {
5075                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
5076                 };
5077
5078                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
5079                         var c, offsetC, n, cmnRoot, childA, childB;
5080                         
5081                         // In the first case the boundary-points have the same container. A is before B
5082                         // if its offset is less than the offset of B, A is equal to B if its offset is
5083                         // equal to the offset of B, and A is after B if its offset is greater than the
5084                         // offset of B.
5085                         if (containerA == containerB) {
5086                                 if (offsetA == offsetB)
5087                                         return 0; // equal
5088
5089                                 if (offsetA < offsetB)
5090                                         return -1; // before
5091
5092                                 return 1; // after
5093                         }
5094
5095                         // In the second case a child node C of the container of A is an ancestor
5096                         // container of B. In this case, A is before B if the offset of A is less than or
5097                         // equal to the index of the child node C and A is after B otherwise.
5098                         c = containerB;
5099                         while (c && c.parentNode != containerA)
5100                                 c = c.parentNode;
5101
5102                         if (c) {
5103                                 offsetC = 0;
5104                                 n = containerA.firstChild;
5105
5106                                 while (n != c && offsetC < offsetA) {
5107                                         offsetC++;
5108                                         n = n.nextSibling;
5109                                 }
5110
5111                                 if (offsetA <= offsetC)
5112                                         return -1; // before
5113
5114                                 return 1; // after
5115                         }
5116
5117                         // In the third case a child node C of the container of B is an ancestor container
5118                         // of A. In this case, A is before B if the index of the child node C is less than
5119                         // the offset of B and A is after B otherwise.
5120                         c = containerA;
5121                         while (c && c.parentNode != containerB) {
5122                                 c = c.parentNode;
5123                         }
5124
5125                         if (c) {
5126                                 offsetC = 0;
5127                                 n = containerB.firstChild;
5128
5129                                 while (n != c && offsetC < offsetB) {
5130                                         offsetC++;
5131                                         n = n.nextSibling;
5132                                 }
5133
5134                                 if (offsetC < offsetB)
5135                                         return -1; // before
5136
5137                                 return 1; // after
5138                         }
5139
5140                         // In the fourth case, none of three other cases hold: the containers of A and B
5141                         // are siblings or descendants of sibling nodes. In this case, A is before B if
5142                         // the container of A is before the container of B in a pre-order traversal of the
5143                         // Ranges' context tree and A is after B otherwise.
5144                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
5145                         childA = containerA;
5146
5147                         while (childA && childA.parentNode != cmnRoot)
5148                                 childA = childA.parentNode;
5149
5150                         if (!childA)
5151                                 childA = cmnRoot;
5152
5153                         childB = containerB;
5154                         while (childB && childB.parentNode != cmnRoot)
5155                                 childB = childB.parentNode;
5156
5157                         if (!childB)
5158                                 childB = cmnRoot;
5159
5160                         if (childA == childB)
5161                                 return 0; // equal
5162
5163                         n = cmnRoot.firstChild;
5164                         while (n) {
5165                                 if (n == childA)
5166                                         return -1; // before
5167
5168                                 if (n == childB)
5169                                         return 1; // after
5170
5171                                 n = n.nextSibling;
5172                         }
5173                 };
5174
5175                 function _setEndPoint(st, n, o) {
5176                         var ec, sc;
5177
5178                         if (st) {
5179                                 t[START_CONTAINER] = n;
5180                                 t[START_OFFSET] = o;
5181                         } else {
5182                                 t[END_CONTAINER] = n;
5183                                 t[END_OFFSET] = o;
5184                         }
5185
5186                         // If one boundary-point of a Range is set to have a root container
5187                         // other than the current one for the Range, the Range is collapsed to
5188                         // the new position. This enforces the restriction that both boundary-
5189                         // points of a Range must have the same root container.
5190                         ec = t[END_CONTAINER];
5191                         while (ec.parentNode)
5192                                 ec = ec.parentNode;
5193
5194                         sc = t[START_CONTAINER];
5195                         while (sc.parentNode)
5196                                 sc = sc.parentNode;
5197
5198                         if (sc == ec) {
5199                                 // The start position of a Range is guaranteed to never be after the
5200                                 // end position. To enforce this restriction, if the start is set to
5201                                 // be at a position after the end, the Range is collapsed to that
5202                                 // position.
5203                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
5204                                         t.collapse(st);
5205                         } else
5206                                 t.collapse(st);
5207
5208                         t.collapsed = _isCollapsed();
5209                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
5210                 };
5211
5212                 function _traverse(how) {
5213                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
5214
5215                         if (t[START_CONTAINER] == t[END_CONTAINER])
5216                                 return _traverseSameContainer(how);
5217
5218                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5219                                 if (p == t[START_CONTAINER])
5220                                         return _traverseCommonStartContainer(c, how);
5221
5222                                 ++endContainerDepth;
5223                         }
5224
5225                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5226                                 if (p == t[END_CONTAINER])
5227                                         return _traverseCommonEndContainer(c, how);
5228
5229                                 ++startContainerDepth;
5230                         }
5231
5232                         depthDiff = startContainerDepth - endContainerDepth;
5233
5234                         startNode = t[START_CONTAINER];
5235                         while (depthDiff > 0) {
5236                                 startNode = startNode.parentNode;
5237                                 depthDiff--;
5238                         }
5239
5240                         endNode = t[END_CONTAINER];
5241                         while (depthDiff < 0) {
5242                                 endNode = endNode.parentNode;
5243                                 depthDiff++;
5244                         }
5245
5246                         // ascend the ancestor hierarchy until we have a common parent.
5247                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
5248                                 startNode = sp;
5249                                 endNode = ep;
5250                         }
5251
5252                         return _traverseCommonAncestors(startNode, endNode, how);
5253                 };
5254
5255                  function _traverseSameContainer(how) {
5256                         var frag, s, sub, n, cnt, sibling, xferNode;
5257
5258                         if (how != DELETE)
5259                                 frag = doc.createDocumentFragment();
5260
5261                         // If selection is empty, just return the fragment
5262                         if (t[START_OFFSET] == t[END_OFFSET])
5263                                 return frag;
5264
5265                         // Text node needs special case handling
5266                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
5267                                 // get the substring
5268                                 s = t[START_CONTAINER].nodeValue;
5269                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
5270
5271                                 // set the original text node to its new value
5272                                 if (how != CLONE) {
5273                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
5274
5275                                         // Nothing is partially selected, so collapse to start point
5276                                         t.collapse(TRUE);
5277                                 }
5278
5279                                 if (how == DELETE)
5280                                         return;
5281
5282                                 frag.appendChild(doc.createTextNode(sub));
5283                                 return frag;
5284                         }
5285
5286                         // Copy nodes between the start/end offsets.
5287                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
5288                         cnt = t[END_OFFSET] - t[START_OFFSET];
5289
5290                         while (cnt > 0) {
5291                                 sibling = n.nextSibling;
5292                                 xferNode = _traverseFullySelected(n, how);
5293
5294                                 if (frag)
5295                                         frag.appendChild( xferNode );
5296
5297                                 --cnt;
5298                                 n = sibling;
5299                         }
5300
5301                         // Nothing is partially selected, so collapse to start point
5302                         if (how != CLONE)
5303                                 t.collapse(TRUE);
5304
5305                         return frag;
5306                 };
5307
5308                 function _traverseCommonStartContainer(endAncestor, how) {
5309                         var frag, n, endIdx, cnt, sibling, xferNode;
5310
5311                         if (how != DELETE)
5312                                 frag = doc.createDocumentFragment();
5313
5314                         n = _traverseRightBoundary(endAncestor, how);
5315
5316                         if (frag)
5317                                 frag.appendChild(n);
5318
5319                         endIdx = nodeIndex(endAncestor);
5320                         cnt = endIdx - t[START_OFFSET];
5321
5322                         if (cnt <= 0) {
5323                                 // Collapse to just before the endAncestor, which
5324                                 // is partially selected.
5325                                 if (how != CLONE) {
5326                                         t.setEndBefore(endAncestor);
5327                                         t.collapse(FALSE);
5328                                 }
5329
5330                                 return frag;
5331                         }
5332
5333                         n = endAncestor.previousSibling;
5334                         while (cnt > 0) {
5335                                 sibling = n.previousSibling;
5336                                 xferNode = _traverseFullySelected(n, how);
5337
5338                                 if (frag)
5339                                         frag.insertBefore(xferNode, frag.firstChild);
5340
5341                                 --cnt;
5342                                 n = sibling;
5343                         }
5344
5345                         // Collapse to just before the endAncestor, which
5346                         // is partially selected.
5347                         if (how != CLONE) {
5348                                 t.setEndBefore(endAncestor);
5349                                 t.collapse(FALSE);
5350                         }
5351
5352                         return frag;
5353                 };
5354
5355                 function _traverseCommonEndContainer(startAncestor, how) {
5356                         var frag, startIdx, n, cnt, sibling, xferNode;
5357
5358                         if (how != DELETE)
5359                                 frag = doc.createDocumentFragment();
5360
5361                         n = _traverseLeftBoundary(startAncestor, how);
5362                         if (frag)
5363                                 frag.appendChild(n);
5364
5365                         startIdx = nodeIndex(startAncestor);
5366                         ++startIdx; // Because we already traversed it
5367
5368                         cnt = t[END_OFFSET] - startIdx;
5369                         n = startAncestor.nextSibling;
5370                         while (cnt > 0) {
5371                                 sibling = n.nextSibling;
5372                                 xferNode = _traverseFullySelected(n, how);
5373
5374                                 if (frag)
5375                                         frag.appendChild(xferNode);
5376
5377                                 --cnt;
5378                                 n = sibling;
5379                         }
5380
5381                         if (how != CLONE) {
5382                                 t.setStartAfter(startAncestor);
5383                                 t.collapse(TRUE);
5384                         }
5385
5386                         return frag;
5387                 };
5388
5389                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
5390                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
5391
5392                         if (how != DELETE)
5393                                 frag = doc.createDocumentFragment();
5394
5395                         n = _traverseLeftBoundary(startAncestor, how);
5396                         if (frag)
5397                                 frag.appendChild(n);
5398
5399                         commonParent = startAncestor.parentNode;
5400                         startOffset = nodeIndex(startAncestor);
5401                         endOffset = nodeIndex(endAncestor);
5402                         ++startOffset;
5403
5404                         cnt = endOffset - startOffset;
5405                         sibling = startAncestor.nextSibling;
5406
5407                         while (cnt > 0) {
5408                                 nextSibling = sibling.nextSibling;
5409                                 n = _traverseFullySelected(sibling, how);
5410
5411                                 if (frag)
5412                                         frag.appendChild(n);
5413
5414                                 sibling = nextSibling;
5415                                 --cnt;
5416                         }
5417
5418                         n = _traverseRightBoundary(endAncestor, how);
5419
5420                         if (frag)
5421                                 frag.appendChild(n);
5422
5423                         if (how != CLONE) {
5424                                 t.setStartAfter(startAncestor);
5425                                 t.collapse(TRUE);
5426                         }
5427
5428                         return frag;
5429                 };
5430
5431                 function _traverseRightBoundary(root, how) {
5432                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
5433
5434                         if (next == root)
5435                                 return _traverseNode(next, isFullySelected, FALSE, how);
5436
5437                         parent = next.parentNode;
5438                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
5439
5440                         while (parent) {
5441                                 while (next) {
5442                                         prevSibling = next.previousSibling;
5443                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
5444
5445                                         if (how != DELETE)
5446                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5447
5448                                         isFullySelected = TRUE;
5449                                         next = prevSibling;
5450                                 }
5451
5452                                 if (parent == root)
5453                                         return clonedParent;
5454
5455                                 next = parent.previousSibling;
5456                                 parent = parent.parentNode;
5457
5458                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
5459
5460                                 if (how != DELETE)
5461                                         clonedGrandParent.appendChild(clonedParent);
5462
5463                                 clonedParent = clonedGrandParent;
5464                         }
5465                 };
5466
5467                 function _traverseLeftBoundary(root, how) {
5468                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
5469
5470                         if (next == root)
5471                                 return _traverseNode(next, isFullySelected, TRUE, how);
5472
5473                         parent = next.parentNode;
5474                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
5475
5476                         while (parent) {
5477                                 while (next) {
5478                                         nextSibling = next.nextSibling;
5479                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
5480
5481                                         if (how != DELETE)
5482                                                 clonedParent.appendChild(clonedChild);
5483
5484                                         isFullySelected = TRUE;
5485                                         next = nextSibling;
5486                                 }
5487
5488                                 if (parent == root)
5489                                         return clonedParent;
5490
5491                                 next = parent.nextSibling;
5492                                 parent = parent.parentNode;
5493
5494                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
5495
5496                                 if (how != DELETE)
5497                                         clonedGrandParent.appendChild(clonedParent);
5498
5499                                 clonedParent = clonedGrandParent;
5500                         }
5501                 };
5502
5503                 function _traverseNode(n, isFullySelected, isLeft, how) {
5504                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
5505
5506                         if (isFullySelected)
5507                                 return _traverseFullySelected(n, how);
5508
5509                         if (n.nodeType == 3 /* TEXT_NODE */) {
5510                                 txtValue = n.nodeValue;
5511
5512                                 if (isLeft) {
5513                                         offset = t[START_OFFSET];
5514                                         newNodeValue = txtValue.substring(offset);
5515                                         oldNodeValue = txtValue.substring(0, offset);
5516                                 } else {
5517                                         offset = t[END_OFFSET];
5518                                         newNodeValue = txtValue.substring(0, offset);
5519                                         oldNodeValue = txtValue.substring(offset);
5520                                 }
5521
5522                                 if (how != CLONE)
5523                                         n.nodeValue = oldNodeValue;
5524
5525                                 if (how == DELETE)
5526                                         return;
5527
5528                                 newNode = n.cloneNode(FALSE);
5529                                 newNode.nodeValue = newNodeValue;
5530
5531                                 return newNode;
5532                         }
5533
5534                         if (how == DELETE)
5535                                 return;
5536
5537                         return n.cloneNode(FALSE);
5538                 };
5539
5540                 function _traverseFullySelected(n, how) {
5541                         if (how != DELETE)
5542                                 return how == CLONE ? n.cloneNode(TRUE) : n;
5543
5544                         n.parentNode.removeChild(n);
5545                 };
5546         };
5547
5548         ns.Range = Range;
5549 })(tinymce.dom);
5550
5551 (function() {
5552         function Selection(selection) {
5553                 var self = this, dom = selection.dom, TRUE = true, FALSE = false;
5554
5555                 function getPosition(rng, start) {
5556                         var checkRng, startIndex = 0, endIndex, inside,
5557                                 children, child, offset, index, position = -1, parent;
5558
5559                         // Setup test range, collapse it and get the parent
5560                         checkRng = rng.duplicate();
5561                         checkRng.collapse(start);
5562                         parent = checkRng.parentElement();
5563
5564                         // Check if the selection is within the right document
5565                         if (parent.ownerDocument !== selection.dom.doc)
5566                                 return;
5567
5568                         // IE will report non editable elements as it's parent so look for an editable one
5569                         while (parent.contentEditable === "false") {
5570                                 parent = parent.parentNode;
5571                         }
5572
5573                         // If parent doesn't have any children then return that we are inside the element
5574                         if (!parent.hasChildNodes()) {
5575                                 return {node : parent, inside : 1};
5576                         }
5577
5578                         // Setup node list and endIndex
5579                         children = parent.children;
5580                         endIndex = children.length - 1;
5581
5582                         // Perform a binary search for the position
5583                         while (startIndex <= endIndex) {
5584                                 index = Math.floor((startIndex + endIndex) / 2);
5585
5586                                 // Move selection to node and compare the ranges
5587                                 child = children[index];
5588                                 checkRng.moveToElementText(child);
5589                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
5590
5591                                 // Before/after or an exact match
5592                                 if (position > 0) {
5593                                         endIndex = index - 1;
5594                                 } else if (position < 0) {
5595                                         startIndex = index + 1;
5596                                 } else {
5597                                         return {node : child};
5598                                 }
5599                         }
5600
5601                         // Check if child position is before or we didn't find a position
5602                         if (position < 0) {
5603                                 // No element child was found use the parent element and the offset inside that
5604                                 if (!child) {
5605                                         checkRng.moveToElementText(parent);
5606                                         checkRng.collapse(true);
5607                                         child = parent;
5608                                         inside = true;
5609                                 } else
5610                                         checkRng.collapse(false);
5611
5612                                 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
5613
5614                                 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
5615                                 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
5616                                         checkRng = rng.duplicate();
5617                                         checkRng.collapse(start);
5618
5619                                         offset = -1;
5620                                         while (parent == checkRng.parentElement()) {
5621                                                 if (checkRng.move('character', -1) == 0)
5622                                                         break;
5623
5624                                                 offset++;
5625                                         }
5626                                 }
5627
5628                                 offset = offset || checkRng.text.replace('\r\n', ' ').length;
5629                         } else {
5630                                 // Child position is after the selection endpoint
5631                                 checkRng.collapse(true);
5632                                 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
5633
5634                                 // Get the length of the text to find where the endpoint is relative to it's container
5635                                 offset = checkRng.text.replace('\r\n', ' ').length;
5636                         }
5637
5638                         return {node : child, position : position, offset : offset, inside : inside};
5639                 };
5640
5641                 // Returns a W3C DOM compatible range object by using the IE Range API
5642                 function getRange() {
5643                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
5644
5645                         // If selection is outside the current document just return an empty range
5646                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5647                         if (element.ownerDocument != dom.doc)
5648                                 return domRange;
5649
5650                         collapsed = selection.isCollapsed();
5651
5652                         // Handle control selection
5653                         if (ieRange.item) {
5654                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));
5655                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5656
5657                                 return domRange;
5658                         }
5659
5660                         function findEndPoint(start) {
5661                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
5662
5663                                 container = endPoint.node;
5664                                 offset = endPoint.offset;
5665
5666                                 if (endPoint.inside && !container.hasChildNodes()) {
5667                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);
5668                                         return;
5669                                 }
5670
5671                                 if (offset === undef) {
5672                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
5673                                         return;
5674                                 }
5675
5676                                 if (endPoint.position < 0) {
5677                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;
5678
5679                                         if (!sibling) {
5680                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
5681                                                 return;
5682                                         }
5683
5684                                         if (!offset) {
5685                                                 if (sibling.nodeType == 3)
5686                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
5687                                                 else
5688                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
5689
5690                                                 return;
5691                                         }
5692
5693                                         // Find the text node and offset
5694                                         while (sibling) {
5695                                                 nodeValue = sibling.nodeValue;
5696                                                 textNodeOffset += nodeValue.length;
5697
5698                                                 // We are at or passed the position we where looking for
5699                                                 if (textNodeOffset >= offset) {
5700                                                         container = sibling;
5701                                                         textNodeOffset -= offset;
5702                                                         textNodeOffset = nodeValue.length - textNodeOffset;
5703                                                         break;
5704                                                 }
5705
5706                                                 sibling = sibling.nextSibling;
5707                                         }
5708                                 } else {
5709                                         // Find the text node and offset
5710                                         sibling = container.previousSibling;
5711
5712                                         if (!sibling)
5713                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
5714
5715                                         // If there isn't any text to loop then use the first position
5716                                         if (!offset) {
5717                                                 if (container.nodeType == 3)
5718                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
5719                                                 else
5720                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
5721
5722                                                 return;
5723                                         }
5724
5725                                         while (sibling) {
5726                                                 textNodeOffset += sibling.nodeValue.length;
5727
5728                                                 // We are at or passed the position we where looking for
5729                                                 if (textNodeOffset >= offset) {
5730                                                         container = sibling;
5731                                                         textNodeOffset -= offset;
5732                                                         break;
5733                                                 }
5734
5735                                                 sibling = sibling.previousSibling;
5736                                         }
5737                                 }
5738
5739                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
5740                         };
5741
5742                         try {
5743                                 // Find start point
5744                                 findEndPoint(true);
5745
5746                                 // Find end point if needed
5747                                 if (!collapsed)
5748                                         findEndPoint();
5749                         } catch (ex) {
5750                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you
5751                                 // access the nodeValue or other properties of text nodes. This seems to happend when
5752                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
5753                                 if (ex.number == -2147024809) {
5754                                         // Get the current selection
5755                                         bookmark = self.getBookmark(2);
5756
5757                                         // Get start element
5758                                         tmpRange = ieRange.duplicate();
5759                                         tmpRange.collapse(true);
5760                                         element = tmpRange.parentElement();
5761
5762                                         // Get end element
5763                                         if (!collapsed) {
5764                                                 tmpRange = ieRange.duplicate();
5765                                                 tmpRange.collapse(false);
5766                                                 element2 = tmpRange.parentElement();
5767                                                 element2.innerHTML = element2.innerHTML;
5768                                         }
5769
5770                                         // Remove the broken elements
5771                                         element.innerHTML = element.innerHTML;
5772
5773                                         // Restore the selection
5774                                         self.moveToBookmark(bookmark);
5775
5776                                         // Since the range has moved we need to re-get it
5777                                         ieRange = selection.getRng();
5778
5779                                         // Find start point
5780                                         findEndPoint(true);
5781
5782                                         // Find end point if needed
5783                                         if (!collapsed)
5784                                                 findEndPoint();
5785                                 } else
5786                                         throw ex; // Throw other errors
5787                         }
5788
5789                         return domRange;
5790                 };
5791
5792                 this.getBookmark = function(type) {
5793                         var rng = selection.getRng(), start, end, bookmark = {};
5794
5795                         function getIndexes(node) {
5796                                 var node, parent, root, children, i, indexes = [];
5797
5798                                 parent = node.parentNode;
5799                                 root = dom.getRoot().parentNode;
5800
5801                                 while (parent != root) {
5802                                         children = parent.children;
5803
5804                                         i = children.length;
5805                                         while (i--) {
5806                                                 if (node === children[i]) {
5807                                                         indexes.push(i);
5808                                                         break;
5809                                                 }
5810                                         }
5811
5812                                         node = parent;
5813                                         parent = parent.parentNode;
5814                                 }
5815
5816                                 return indexes;
5817                         };
5818
5819                         function getBookmarkEndPoint(start) {
5820                                 var position;
5821
5822                                 position = getPosition(rng, start);
5823                                 if (position) {
5824                                         return {
5825                                                 position : position.position,
5826                                                 offset : position.offset,
5827                                                 indexes : getIndexes(position.node),
5828                                                 inside : position.inside
5829                                         };
5830                                 }
5831                         };
5832
5833                         // Non ubstructive bookmark
5834                         if (type === 2) {
5835                                 // Handle text selection
5836                                 if (!rng.item) {
5837                                         bookmark.start = getBookmarkEndPoint(true);
5838
5839                                         if (!selection.isCollapsed())
5840                                                 bookmark.end = getBookmarkEndPoint();
5841                                 } else
5842                                         bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
5843                         }
5844
5845                         return bookmark;
5846                 };
5847
5848                 this.moveToBookmark = function(bookmark) {
5849                         var rng, body = dom.doc.body;
5850
5851                         function resolveIndexes(indexes) {
5852                                 var node, i, idx, children;
5853
5854                                 node = dom.getRoot();
5855                                 for (i = indexes.length - 1; i >= 0; i--) {
5856                                         children = node.children;
5857                                         idx = indexes[i];
5858
5859                                         if (idx <= children.length - 1) {
5860                                                 node = children[idx];
5861                                         }
5862                                 }
5863
5864                                 return node;
5865                         };
5866                         
5867                         function setBookmarkEndPoint(start) {
5868                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
5869
5870                                 if (endPoint) {
5871                                         moveLeft = endPoint.position > 0;
5872
5873                                         moveRng = body.createTextRange();
5874                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
5875
5876                                         offset = endPoint.offset;
5877                                         if (offset !== undef) {
5878                                                 moveRng.collapse(endPoint.inside || moveLeft);
5879                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);
5880                                         } else
5881                                                 moveRng.collapse(start);
5882
5883                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
5884
5885                                         if (start)
5886                                                 rng.collapse(true);
5887                                 }
5888                         };
5889
5890                         if (bookmark.start) {
5891                                 if (bookmark.start.ctrl) {
5892                                         rng = body.createControlRange();
5893                                         rng.addElement(resolveIndexes(bookmark.start.indexes));
5894                                         rng.select();
5895                                 } else {
5896                                         rng = body.createTextRange();
5897                                         setBookmarkEndPoint(true);
5898                                         setBookmarkEndPoint();
5899                                         rng.select();
5900                                 }
5901                         }
5902                 };
5903
5904                 this.addRange = function(rng) {
5905                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
5906
5907                         function setEndPoint(start) {
5908                                 var container, offset, marker, tmpRng, nodes;
5909
5910                                 marker = dom.create('a');
5911                                 container = start ? startContainer : endContainer;
5912                                 offset = start ? startOffset : endOffset;
5913                                 tmpRng = ieRng.duplicate();
5914
5915                                 if (container == doc || container == doc.documentElement) {
5916                                         container = body;
5917                                         offset = 0;
5918                                 }
5919
5920                                 if (container.nodeType == 3) {
5921                                         container.parentNode.insertBefore(marker, container);
5922                                         tmpRng.moveToElementText(marker);
5923                                         tmpRng.moveStart('character', offset);
5924                                         dom.remove(marker);
5925                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5926                                 } else {
5927                                         nodes = container.childNodes;
5928
5929                                         if (nodes.length) {
5930                                                 if (offset >= nodes.length) {
5931                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
5932                                                 } else {
5933                                                         container.insertBefore(marker, nodes[offset]);
5934                                                 }
5935
5936                                                 tmpRng.moveToElementText(marker);
5937                                         } else {
5938                                                 // Empty node selection for example <div>|</div>
5939                                                 marker = doc.createTextNode('\uFEFF');
5940                                                 container.appendChild(marker);
5941                                                 tmpRng.moveToElementText(marker.parentNode);
5942                                                 tmpRng.collapse(TRUE);
5943                                         }
5944
5945                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5946                                         dom.remove(marker);
5947                                 }
5948                         }
5949
5950                         // Setup some shorter versions
5951                         startContainer = rng.startContainer;
5952                         startOffset = rng.startOffset;
5953                         endContainer = rng.endContainer;
5954                         endOffset = rng.endOffset;
5955                         ieRng = body.createTextRange();
5956
5957                         // If single element selection then try making a control selection out of it
5958                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
5959                                 if (startOffset == endOffset - 1) {
5960                                         try {
5961                                                 ctrlRng = body.createControlRange();
5962                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);
5963                                                 ctrlRng.select();
5964                                                 return;
5965                                         } catch (ex) {
5966                                                 // Ignore
5967                                         }
5968                                 }
5969                         }
5970
5971                         // Set start/end point of selection
5972                         setEndPoint(true);
5973                         setEndPoint();
5974
5975                         // Select the new range and scroll it into view
5976                         ieRng.select();
5977                 };
5978
5979                 // Expose range method
5980                 this.getRangeAt = getRange;
5981         };
5982
5983         // Expose the selection object
5984         tinymce.dom.TridentSelection = Selection;
5985 })();
5986
5987
5988 (function(tinymce) {
5989         // Shorten names
5990         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
5991
5992         tinymce.create('tinymce.dom.EventUtils', {
5993                 EventUtils : function() {
5994                         this.inits = [];
5995                         this.events = [];
5996                 },
5997
5998                 add : function(o, n, f, s) {
5999                         var cb, t = this, el = t.events, r;
6000
6001                         if (n instanceof Array) {
6002                                 r = [];
6003
6004                                 each(n, function(n) {
6005                                         r.push(t.add(o, n, f, s));
6006                                 });
6007
6008                                 return r;
6009                         }
6010
6011                         // Handle array
6012                         if (o && o.hasOwnProperty && o instanceof Array) {
6013                                 r = [];
6014
6015                                 each(o, function(o) {
6016                                         o = DOM.get(o);
6017                                         r.push(t.add(o, n, f, s));
6018                                 });
6019
6020                                 return r;
6021                         }
6022
6023                         o = DOM.get(o);
6024
6025                         if (!o)
6026                                 return;
6027
6028                         // Setup event callback
6029                         cb = function(e) {
6030                                 // Is all events disabled
6031                                 if (t.disabled)
6032                                         return;
6033
6034                                 e = e || window.event;
6035
6036                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
6037                                 if (e && isIE) {
6038                                         if (!e.target)
6039                                                 e.target = e.srcElement;
6040
6041                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility
6042                                         tinymce.extend(e, t._stoppers);
6043                                 }
6044
6045                                 if (!s)
6046                                         return f(e);
6047
6048                                 return f.call(s, e);
6049                         };
6050
6051                         if (n == 'unload') {
6052                                 tinymce.unloads.unshift({func : cb});
6053                                 return cb;
6054                         }
6055
6056                         if (n == 'init') {
6057                                 if (t.domLoaded)
6058                                         cb();
6059                                 else
6060                                         t.inits.push(cb);
6061
6062                                 return cb;
6063                         }
6064
6065                         // Store away listener reference
6066                         el.push({
6067                                 obj : o,
6068                                 name : n,
6069                                 func : f,
6070                                 cfunc : cb,
6071                                 scope : s
6072                         });
6073
6074                         t._add(o, n, cb);
6075
6076                         return f;
6077                 },
6078
6079                 remove : function(o, n, f) {
6080                         var t = this, a = t.events, s = false, r;
6081
6082                         // Handle array
6083                         if (o && o.hasOwnProperty && o instanceof Array) {
6084                                 r = [];
6085
6086                                 each(o, function(o) {
6087                                         o = DOM.get(o);
6088                                         r.push(t.remove(o, n, f));
6089                                 });
6090
6091                                 return r;
6092                         }
6093
6094                         o = DOM.get(o);
6095
6096                         each(a, function(e, i) {
6097                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
6098                                         a.splice(i, 1);
6099                                         t._remove(o, n, e.cfunc);
6100                                         s = true;
6101                                         return false;
6102                                 }
6103                         });
6104
6105                         return s;
6106                 },
6107
6108                 clear : function(o) {
6109                         var t = this, a = t.events, i, e;
6110
6111                         if (o) {
6112                                 o = DOM.get(o);
6113
6114                                 for (i = a.length - 1; i >= 0; i--) {
6115                                         e = a[i];
6116
6117                                         if (e.obj === o) {
6118                                                 t._remove(e.obj, e.name, e.cfunc);
6119                                                 e.obj = e.cfunc = null;
6120                                                 a.splice(i, 1);
6121                                         }
6122                                 }
6123                         }
6124                 },
6125
6126                 cancel : function(e) {
6127                         if (!e)
6128                                 return false;
6129
6130                         this.stop(e);
6131
6132                         return this.prevent(e);
6133                 },
6134
6135                 stop : function(e) {
6136                         if (e.stopPropagation)
6137                                 e.stopPropagation();
6138                         else
6139                                 e.cancelBubble = true;
6140
6141                         return false;
6142                 },
6143
6144                 prevent : function(e) {
6145                         if (e.preventDefault)
6146                                 e.preventDefault();
6147                         else
6148                                 e.returnValue = false;
6149
6150                         return false;
6151                 },
6152
6153                 destroy : function() {
6154                         var t = this;
6155
6156                         each(t.events, function(e, i) {
6157                                 t._remove(e.obj, e.name, e.cfunc);
6158                                 e.obj = e.cfunc = null;
6159                         });
6160
6161                         t.events = [];
6162                         t = null;
6163                 },
6164
6165                 _add : function(o, n, f) {
6166                         if (o.attachEvent)
6167                                 o.attachEvent('on' + n, f);
6168                         else if (o.addEventListener)
6169                                 o.addEventListener(n, f, false);
6170                         else
6171                                 o['on' + n] = f;
6172                 },
6173
6174                 _remove : function(o, n, f) {
6175                         if (o) {
6176                                 try {
6177                                         if (o.detachEvent)
6178                                                 o.detachEvent('on' + n, f);
6179                                         else if (o.removeEventListener)
6180                                                 o.removeEventListener(n, f, false);
6181                                         else
6182                                                 o['on' + n] = null;
6183                                 } catch (ex) {
6184                                         // Might fail with permission denined on IE so we just ignore that
6185                                 }
6186                         }
6187                 },
6188
6189                 _pageInit : function(win) {
6190                         var t = this;
6191
6192                         // Keep it from running more than once
6193                         if (t.domLoaded)
6194                                 return;
6195
6196                         t.domLoaded = true;
6197
6198                         each(t.inits, function(c) {
6199                                 c();
6200                         });
6201
6202                         t.inits = [];
6203                 },
6204
6205                 _wait : function(win) {
6206                         var t = this, doc = win.document;
6207
6208                         // No need since the document is already loaded
6209                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
6210                                 t.domLoaded = 1;
6211                                 return;
6212                         }
6213
6214                         // Use IE method
6215                         if (doc.attachEvent) {
6216                                 doc.attachEvent("onreadystatechange", function() {
6217                                         if (doc.readyState === "complete") {
6218                                                 doc.detachEvent("onreadystatechange", arguments.callee);
6219                                                 t._pageInit(win);
6220                                         }
6221                                 });
6222
6223                                 if (doc.documentElement.doScroll && win == win.top) {
6224                                         (function() {
6225                                                 if (t.domLoaded)
6226                                                         return;
6227
6228                                                 try {
6229                                                         // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
6230                                                         // http://javascript.nwbox.com/IEContentLoaded/
6231                                                         doc.documentElement.doScroll("left");
6232                                                 } catch (ex) {
6233                                                         setTimeout(arguments.callee, 0);
6234                                                         return;
6235                                                 }
6236
6237                                                 t._pageInit(win);
6238                                         })();
6239                                 }
6240                         } else if (doc.addEventListener) {
6241                                 t._add(win, 'DOMContentLoaded', function() {
6242                                         t._pageInit(win);
6243                                 });
6244                         }
6245
6246                         t._add(win, 'load', function() {
6247                                 t._pageInit(win);
6248                         });
6249                 },
6250
6251                 _stoppers : {
6252                         preventDefault : function() {
6253                                 this.returnValue = false;
6254                         },
6255
6256                         stopPropagation : function() {
6257                                 this.cancelBubble = true;
6258                         }
6259                 }
6260         });
6261
6262         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
6263
6264         // Dispatch DOM content loaded event for IE and Safari
6265         Event._wait(window);
6266
6267         tinymce.addUnload(function() {
6268                 Event.destroy();
6269         });
6270 })(tinymce);
6271
6272 (function(tinymce) {
6273         tinymce.dom.Element = function(id, settings) {
6274                 var t = this, dom, el;
6275
6276                 t.settings = settings = settings || {};
6277                 t.id = id;
6278                 t.dom = dom = settings.dom || tinymce.DOM;
6279
6280                 // Only IE leaks DOM references, this is a lot faster
6281                 if (!tinymce.isIE)
6282                         el = dom.get(t.id);
6283
6284                 tinymce.each(
6285                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
6286                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
6287                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
6288                                 'isHidden,setHTML,get').split(/,/)
6289                         , function(k) {
6290                                 t[k] = function() {
6291                                         var a = [id], i;
6292
6293                                         for (i = 0; i < arguments.length; i++)
6294                                                 a.push(arguments[i]);
6295
6296                                         a = dom[k].apply(dom, a);
6297                                         t.update(k);
6298
6299                                         return a;
6300                                 };
6301                 });
6302
6303                 tinymce.extend(t, {
6304                         on : function(n, f, s) {
6305                                 return tinymce.dom.Event.add(t.id, n, f, s);
6306                         },
6307
6308                         getXY : function() {
6309                                 return {
6310                                         x : parseInt(t.getStyle('left')),
6311                                         y : parseInt(t.getStyle('top'))
6312                                 };
6313                         },
6314
6315                         getSize : function() {
6316                                 var n = dom.get(t.id);
6317
6318                                 return {
6319                                         w : parseInt(t.getStyle('width') || n.clientWidth),
6320                                         h : parseInt(t.getStyle('height') || n.clientHeight)
6321                                 };
6322                         },
6323
6324                         moveTo : function(x, y) {
6325                                 t.setStyles({left : x, top : y});
6326                         },
6327
6328                         moveBy : function(x, y) {
6329                                 var p = t.getXY();
6330
6331                                 t.moveTo(p.x + x, p.y + y);
6332                         },
6333
6334                         resizeTo : function(w, h) {
6335                                 t.setStyles({width : w, height : h});
6336                         },
6337
6338                         resizeBy : function(w, h) {
6339                                 var s = t.getSize();
6340
6341                                 t.resizeTo(s.w + w, s.h + h);
6342                         },
6343
6344                         update : function(k) {
6345                                 var b;
6346
6347                                 if (tinymce.isIE6 && settings.blocker) {
6348                                         k = k || '';
6349
6350                                         // Ignore getters
6351                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
6352                                                 return;
6353
6354                                         // Remove blocker on remove
6355                                         if (k == 'remove') {
6356                                                 dom.remove(t.blocker);
6357                                                 return;
6358                                         }
6359
6360                                         if (!t.blocker) {
6361                                                 t.blocker = dom.uniqueId();
6362                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
6363                                                 dom.setStyle(b, 'opacity', 0);
6364                                         } else
6365                                                 b = dom.get(t.blocker);
6366
6367                                         dom.setStyles(b, {
6368                                                 left : t.getStyle('left', 1),
6369                                                 top : t.getStyle('top', 1),
6370                                                 width : t.getStyle('width', 1),
6371                                                 height : t.getStyle('height', 1),
6372                                                 display : t.getStyle('display', 1),
6373                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
6374                                         });
6375                                 }
6376                         }
6377                 });
6378         };
6379 })(tinymce);
6380
6381 (function(tinymce) {
6382         function trimNl(s) {
6383                 return s.replace(/[\n\r]+/g, '');
6384         };
6385
6386         // Shorten names
6387         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
6388
6389         tinymce.create('tinymce.dom.Selection', {
6390                 Selection : function(dom, win, serializer) {
6391                         var t = this;
6392
6393                         t.dom = dom;
6394                         t.win = win;
6395                         t.serializer = serializer;
6396
6397                         // Add events
6398                         each([
6399                                 'onBeforeSetContent',
6400
6401                                 'onBeforeGetContent',
6402
6403                                 'onSetContent',
6404
6405                                 'onGetContent'
6406                         ], function(e) {
6407                                 t[e] = new tinymce.util.Dispatcher(t);
6408                         });
6409
6410                         // No W3C Range support
6411                         if (!t.win.getSelection)
6412                                 t.tridentSel = new tinymce.dom.TridentSelection(t);
6413
6414                         if (tinymce.isIE && dom.boxModel)
6415                                 this._fixIESelection();
6416
6417                         // Prevent leaks
6418                         tinymce.addUnload(t.destroy, t);
6419                 },
6420
6421                 setCursorLocation: function(node, offset) {
6422                         var t = this; var r = t.dom.createRng();
6423                         r.setStart(node, offset);
6424                         r.setEnd(node, offset);
6425                         t.setRng(r);
6426                         t.collapse(false);
6427                 },
6428                 getContent : function(s) {
6429                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
6430
6431                         s = s || {};
6432                         wb = wa = '';
6433                         s.get = true;
6434                         s.format = s.format || 'html';
6435                         s.forced_root_block = '';
6436                         t.onBeforeGetContent.dispatch(t, s);
6437
6438                         if (s.format == 'text')
6439                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
6440
6441                         if (r.cloneContents) {
6442                                 n = r.cloneContents();
6443
6444                                 if (n)
6445                                         e.appendChild(n);
6446                         } else if (is(r.item) || is(r.htmlText))
6447                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
6448                         else
6449                                 e.innerHTML = r.toString();
6450
6451                         // Keep whitespace before and after
6452                         if (/^\s/.test(e.innerHTML))
6453                                 wb = ' ';
6454
6455                         if (/\s+$/.test(e.innerHTML))
6456                                 wa = ' ';
6457
6458                         s.getInner = true;
6459
6460                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
6461                         t.onGetContent.dispatch(t, s);
6462
6463                         return s.content;
6464                 },
6465
6466                 setContent : function(content, args) {
6467                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
6468
6469                         args = args || {format : 'html'};
6470                         args.set = true;
6471                         content = args.content = content;
6472
6473                         // Dispatch before set content event
6474                         if (!args.no_events)
6475                                 self.onBeforeSetContent.dispatch(self, args);
6476
6477                         content = args.content;
6478
6479                         if (rng.insertNode) {
6480                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
6481                                 content += '<span id="__caret">_</span>';
6482
6483                                 // Delete and insert new node
6484                                 if (rng.startContainer == doc && rng.endContainer == doc) {
6485                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
6486                                         doc.body.innerHTML = content;
6487                                 } else {
6488                                         rng.deleteContents();
6489
6490                                         if (doc.body.childNodes.length == 0) {
6491                                                 doc.body.innerHTML = content;
6492                                         } else {
6493                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
6494                                                 if (rng.createContextualFragment) {
6495                                                         rng.insertNode(rng.createContextualFragment(content));
6496                                                 } else {
6497                                                         // Fake createContextualFragment call in IE 9
6498                                                         frag = doc.createDocumentFragment();
6499                                                         temp = doc.createElement('div');
6500
6501                                                         frag.appendChild(temp);
6502                                                         temp.outerHTML = content;
6503
6504                                                         rng.insertNode(frag);
6505                                                 }
6506                                         }
6507                                 }
6508
6509                                 // Move to caret marker
6510                                 caretNode = self.dom.get('__caret');
6511
6512                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
6513                                 rng = doc.createRange();
6514                                 rng.setStartBefore(caretNode);
6515                                 rng.setEndBefore(caretNode);
6516                                 self.setRng(rng);
6517
6518                                 // Remove the caret position
6519                                 self.dom.remove('__caret');
6520
6521                                 try {
6522                                         self.setRng(rng);
6523                                 } catch (ex) {
6524                                         // Might fail on Opera for some odd reason
6525                                 }
6526                         } else {
6527                                 if (rng.item) {
6528                                         // Delete content and get caret text selection
6529                                         doc.execCommand('Delete', false, null);
6530                                         rng = self.getRng();
6531                                 }
6532
6533                                 rng.pasteHTML(content);
6534                         }
6535
6536                         // Dispatch set content event
6537                         if (!args.no_events)
6538                                 self.onSetContent.dispatch(self, args);
6539                 },
6540
6541                 getStart : function() {
6542                         var rng = this.getRng(), startElement, parentElement, checkRng, node;
6543
6544                         if (rng.duplicate || rng.item) {
6545                                 // Control selection, return first item
6546                                 if (rng.item)
6547                                         return rng.item(0);
6548
6549                                 // Get start element
6550                                 checkRng = rng.duplicate();
6551                                 checkRng.collapse(1);
6552                                 startElement = checkRng.parentElement();
6553
6554                                 // Check if range parent is inside the start element, then return the inner parent element
6555                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
6556                                 parentElement = node = rng.parentElement();
6557                                 while (node = node.parentNode) {
6558                                         if (node == startElement) {
6559                                                 startElement = parentElement;
6560                                                 break;
6561                                         }
6562                                 }
6563
6564                                 return startElement;
6565                         } else {
6566                                 startElement = rng.startContainer;
6567
6568                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
6569                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
6570
6571                                 if (startElement && startElement.nodeType == 3)
6572                                         return startElement.parentNode;
6573
6574                                 return startElement;
6575                         }
6576                 },
6577
6578                 getEnd : function() {
6579                         var t = this, r = t.getRng(), e, eo;
6580
6581                         if (r.duplicate || r.item) {
6582                                 if (r.item)
6583                                         return r.item(0);
6584
6585                                 r = r.duplicate();
6586                                 r.collapse(0);
6587                                 e = r.parentElement();
6588
6589                                 if (e && e.nodeName == 'BODY')
6590                                         return e.lastChild || e;
6591
6592                                 return e;
6593                         } else {
6594                                 e = r.endContainer;
6595                                 eo = r.endOffset;
6596
6597                                 if (e.nodeType == 1 && e.hasChildNodes())
6598                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];
6599
6600                                 if (e && e.nodeType == 3)
6601                                         return e.parentNode;
6602
6603                                 return e;
6604                         }
6605                 },
6606
6607                 getBookmark : function(type, normalized) {
6608                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
6609
6610                         function findIndex(name, element) {
6611                                 var index = 0;
6612
6613                                 each(dom.select(name), function(node, i) {
6614                                         if (node == element)
6615                                                 index = i;
6616                                 });
6617
6618                                 return index;
6619                         };
6620
6621                         if (type == 2) {
6622                                 function getLocation() {
6623                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
6624
6625                                         function getPoint(rng, start) {
6626                                                 var container = rng[start ? 'startContainer' : 'endContainer'],
6627                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
6628
6629                                                 if (container.nodeType == 3) {
6630                                                         if (normalized) {
6631                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
6632                                                                         offset += node.nodeValue.length;
6633                                                         }
6634
6635                                                         point.push(offset);
6636                                                 } else {
6637                                                         childNodes = container.childNodes;
6638
6639                                                         if (offset >= childNodes.length && childNodes.length) {
6640                                                                 after = 1;
6641                                                                 offset = Math.max(0, childNodes.length - 1);
6642                                                         }
6643
6644                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
6645                                                 }
6646
6647                                                 for (; container && container != root; container = container.parentNode)
6648                                                         point.push(t.dom.nodeIndex(container, normalized));
6649
6650                                                 return point;
6651                                         };
6652
6653                                         bookmark.start = getPoint(rng, true);
6654
6655                                         if (!t.isCollapsed())
6656                                                 bookmark.end = getPoint(rng);
6657
6658                                         return bookmark;
6659                                 };
6660
6661                                 if (t.tridentSel)
6662                                         return t.tridentSel.getBookmark(type);
6663
6664                                 return getLocation();
6665                         }
6666
6667                         // Handle simple range
6668                         if (type)
6669                                 return {rng : t.getRng()};
6670
6671                         rng = t.getRng();
6672                         id = dom.uniqueId();
6673                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();
6674                         styles = 'overflow:hidden;line-height:0px';
6675
6676                         // Explorer method
6677                         if (rng.duplicate || rng.item) {
6678                                 // Text selection
6679                                 if (!rng.item) {
6680                                         rng2 = rng.duplicate();
6681
6682                                         try {
6683                                                 // Insert start marker
6684                                                 rng.collapse();
6685                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
6686
6687                                                 // Insert end marker
6688                                                 if (!collapsed) {
6689                                                         rng2.collapse(false);
6690
6691                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
6692                                                         rng.moveToElementText(rng2.parentElement());
6693                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)
6694                                                                 rng2.move('character', -1);
6695
6696                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
6697                                                 }
6698                                         } catch (ex) {
6699                                                 // IE might throw unspecified error so lets ignore it
6700                                                 return null;
6701                                         }
6702                                 } else {
6703                                         // Control selection
6704                                         element = rng.item(0);
6705                                         name = element.nodeName;
6706
6707                                         return {name : name, index : findIndex(name, element)};
6708                                 }
6709                         } else {
6710                                 element = t.getNode();
6711                                 name = element.nodeName;
6712                                 if (name == 'IMG')
6713                                         return {name : name, index : findIndex(name, element)};
6714
6715                                 // W3C method
6716                                 rng2 = rng.cloneRange();
6717
6718                                 // Insert end marker
6719                                 if (!collapsed) {
6720                                         rng2.collapse(false);
6721                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
6722                                 }
6723
6724                                 rng.collapse(true);
6725                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
6726                         }
6727
6728                         t.moveToBookmark({id : id, keep : 1});
6729
6730                         return {id : id};
6731                 },
6732
6733                 moveToBookmark : function(bookmark) {
6734                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
6735
6736                         if (bookmark) {
6737                                 if (bookmark.start) {
6738                                         rng = dom.createRng();
6739                                         root = dom.getRoot();
6740
6741                                         function setEndPoint(start) {
6742                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
6743
6744                                                 if (point) {
6745                                                         offset = point[0];
6746
6747                                                         // Find container node
6748                                                         for (node = root, i = point.length - 1; i >= 1; i--) {
6749                                                                 children = node.childNodes;
6750
6751                                                                 if (point[i] > children.length - 1)
6752                                                                         return;
6753
6754                                                                 node = children[point[i]];
6755                                                         }
6756
6757                                                         // Move text offset to best suitable location
6758                                                         if (node.nodeType === 3)
6759                                                                 offset = Math.min(point[0], node.nodeValue.length);
6760
6761                                                         // Move element offset to best suitable location
6762                                                         if (node.nodeType === 1)
6763                                                                 offset = Math.min(point[0], node.childNodes.length);
6764
6765                                                         // Set offset within container node
6766                                                         if (start)
6767                                                                 rng.setStart(node, offset);
6768                                                         else
6769                                                                 rng.setEnd(node, offset);
6770                                                 }
6771
6772                                                 return true;
6773                                         };
6774
6775                                         if (t.tridentSel)
6776                                                 return t.tridentSel.moveToBookmark(bookmark);
6777
6778                                         if (setEndPoint(true) && setEndPoint()) {
6779                                                 t.setRng(rng);
6780                                         }
6781                                 } else if (bookmark.id) {
6782                                         function restoreEndPoint(suffix) {
6783                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
6784
6785                                                 if (marker) {
6786                                                         node = marker.parentNode;
6787
6788                                                         if (suffix == 'start') {
6789                                                                 if (!keep) {
6790                                                                         idx = dom.nodeIndex(marker);
6791                                                                 } else {
6792                                                                         node = marker.firstChild;
6793                                                                         idx = 1;
6794                                                                 }
6795
6796                                                                 startContainer = endContainer = node;
6797                                                                 startOffset = endOffset = idx;
6798                                                         } else {
6799                                                                 if (!keep) {
6800                                                                         idx = dom.nodeIndex(marker);
6801                                                                 } else {
6802                                                                         node = marker.firstChild;
6803                                                                         idx = 1;
6804                                                                 }
6805
6806                                                                 endContainer = node;
6807                                                                 endOffset = idx;
6808                                                         }
6809
6810                                                         if (!keep) {
6811                                                                 prev = marker.previousSibling;
6812                                                                 next = marker.nextSibling;
6813
6814                                                                 // Remove all marker text nodes
6815                                                                 each(tinymce.grep(marker.childNodes), function(node) {
6816                                                                         if (node.nodeType == 3)
6817                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
6818                                                                 });
6819
6820                                                                 // Remove marker but keep children if for example contents where inserted into the marker
6821                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
6822                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))
6823                                                                         dom.remove(marker, 1);
6824
6825                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
6826                                                                 // 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
6827                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
6828                                                                         idx = prev.nodeValue.length;
6829                                                                         prev.appendData(next.nodeValue);
6830                                                                         dom.remove(next);
6831
6832                                                                         if (suffix == 'start') {
6833                                                                                 startContainer = endContainer = prev;
6834                                                                                 startOffset = endOffset = idx;
6835                                                                         } else {
6836                                                                                 endContainer = prev;
6837                                                                                 endOffset = idx;
6838                                                                         }
6839                                                                 }
6840                                                         }
6841                                                 }
6842                                         };
6843
6844                                         function addBogus(node) {
6845                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
6846                                                 if (dom.isBlock(node) && !node.innerHTML)
6847                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
6848
6849                                                 return node;
6850                                         };
6851
6852                                         // Restore start/end points
6853                                         restoreEndPoint('start');
6854                                         restoreEndPoint('end');
6855
6856                                         if (startContainer) {
6857                                                 rng = dom.createRng();
6858                                                 rng.setStart(addBogus(startContainer), startOffset);
6859                                                 rng.setEnd(addBogus(endContainer), endOffset);
6860                                                 t.setRng(rng);
6861                                         }
6862                                 } else if (bookmark.name) {
6863                                         t.select(dom.select(bookmark.name)[bookmark.index]);
6864                                 } else if (bookmark.rng)
6865                                         t.setRng(bookmark.rng);
6866                         }
6867                 },
6868
6869                 select : function(node, content) {
6870                         var t = this, dom = t.dom, rng = dom.createRng(), idx;
6871
6872                         if (node) {
6873                                 idx = dom.nodeIndex(node);
6874                                 rng.setStart(node.parentNode, idx);
6875                                 rng.setEnd(node.parentNode, idx + 1);
6876
6877                                 // Find first/last text node or BR element
6878                                 if (content) {
6879                                         function setPoint(node, start) {
6880                                                 var walker = new tinymce.dom.TreeWalker(node, node);
6881
6882                                                 do {
6883                                                         // Text node
6884                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
6885                                                                 if (start)
6886                                                                         rng.setStart(node, 0);
6887                                                                 else
6888                                                                         rng.setEnd(node, node.nodeValue.length);
6889
6890                                                                 return;
6891                                                         }
6892
6893                                                         // BR element
6894                                                         if (node.nodeName == 'BR') {
6895                                                                 if (start)
6896                                                                         rng.setStartBefore(node);
6897                                                                 else
6898                                                                         rng.setEndBefore(node);
6899
6900                                                                 return;
6901                                                         }
6902                                                 } while (node = (start ? walker.next() : walker.prev()));
6903                                         };
6904
6905                                         setPoint(node, 1);
6906                                         setPoint(node);
6907                                 }
6908
6909                                 t.setRng(rng);
6910                         }
6911
6912                         return node;
6913                 },
6914
6915                 isCollapsed : function() {
6916                         var t = this, r = t.getRng(), s = t.getSel();
6917
6918                         if (!r || r.item)
6919                                 return false;
6920
6921                         if (r.compareEndPoints)
6922                                 return r.compareEndPoints('StartToEnd', r) === 0;
6923
6924                         return !s || r.collapsed;
6925                 },
6926
6927                 collapse : function(to_start) {
6928                         var self = this, rng = self.getRng(), node;
6929
6930                         // Control range on IE
6931                         if (rng.item) {
6932                                 node = rng.item(0);
6933                                 rng = self.win.document.body.createTextRange();
6934                                 rng.moveToElementText(node);
6935                         }
6936
6937                         rng.collapse(!!to_start);
6938                         self.setRng(rng);
6939                 },
6940
6941                 getSel : function() {
6942                         var t = this, w = this.win;
6943
6944                         return w.getSelection ? w.getSelection() : w.document.selection;
6945                 },
6946
6947                 getRng : function(w3c) {
6948                         var t = this, s, r, elm, doc = t.win.document;
6949
6950                         // Found tridentSel object then we need to use that one
6951                         if (w3c && t.tridentSel)
6952                                 return t.tridentSel.getRangeAt(0);
6953
6954                         try {
6955                                 if (s = t.getSel())
6956                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
6957                         } catch (ex) {
6958                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
6959                         }
6960
6961                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
6962                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
6963                                 elm = doc.selection.createRange().item(0);
6964                                 r = doc.createRange();
6965                                 r.setStartBefore(elm);
6966                                 r.setEndAfter(elm);
6967                         }
6968
6969                         // No range found then create an empty one
6970                         // This can occur when the editor is placed in a hidden container element on Gecko
6971                         // Or on IE when there was an exception
6972                         if (!r)
6973                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
6974
6975                         if (t.selectedRange && t.explicitRange) {
6976                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
6977                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
6978                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
6979                                         r = t.explicitRange;
6980                                 } else {
6981                                         t.selectedRange = null;
6982                                         t.explicitRange = null;
6983                                 }
6984                         }
6985
6986                         return r;
6987                 },
6988
6989                 setRng : function(r) {
6990                         var s, t = this;
6991                         
6992                         if (!t.tridentSel) {
6993                                 s = t.getSel();
6994
6995                                 if (s) {
6996                                         t.explicitRange = r;
6997
6998                                         try {
6999                                                 s.removeAllRanges();
7000                                         } catch (ex) {
7001                                                 // IE9 might throw errors here don't know why
7002                                         }
7003
7004                                         s.addRange(r);
7005                                         t.selectedRange = s.getRangeAt(0);
7006                                 }
7007                         } else {
7008                                 // Is W3C Range
7009                                 if (r.cloneRange) {
7010                                         t.tridentSel.addRange(r);
7011                                         return;
7012                                 }
7013
7014                                 // Is IE specific range
7015                                 try {
7016                                         r.select();
7017                                 } catch (ex) {
7018                                         // Needed for some odd IE bug #1843306
7019                                 }
7020                         }
7021                 },
7022
7023                 setNode : function(n) {
7024                         var t = this;
7025
7026                         t.setContent(t.dom.getOuterHTML(n));
7027
7028                         return n;
7029                 },
7030
7031                 getNode : function() {
7032                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
7033
7034                         // Range maybe lost after the editor is made visible again
7035                         if (!rng)
7036                                 return t.dom.getRoot();
7037
7038                         if (rng.setStart) {
7039                                 elm = rng.commonAncestorContainer;
7040
7041                                 // Handle selection a image or other control like element such as anchors
7042                                 if (!rng.collapsed) {
7043                                         if (rng.startContainer == rng.endContainer) {
7044                                                 if (rng.endOffset - rng.startOffset < 2) {
7045                                                         if (rng.startContainer.hasChildNodes())
7046                                                                 elm = rng.startContainer.childNodes[rng.startOffset];
7047                                                 }
7048                                         }
7049
7050                                         // If the anchor node is a element instead of a text node then return this element
7051                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
7052                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
7053
7054                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
7055                                         // This happens when you double click an underlined word in FireFox.
7056                                         if (start.nodeType === 3 && end.nodeType === 3) {
7057                                                 function skipEmptyTextNodes(n, forwards) {
7058                                                         var orig = n;
7059                                                         while (n && n.nodeType === 3 && n.length === 0) {
7060                                                                 n = forwards ? n.nextSibling : n.previousSibling;
7061                                                         }
7062                                                         return n || orig;
7063                                                 }
7064                                                 if (start.length === rng.startOffset) {
7065                                                         start = skipEmptyTextNodes(start.nextSibling, true);
7066                                                 } else {
7067                                                         start = start.parentNode;
7068                                                 }
7069                                                 if (rng.endOffset === 0) {
7070                                                         end = skipEmptyTextNodes(end.previousSibling, false);
7071                                                 } else {
7072                                                         end = end.parentNode;
7073                                                 }
7074
7075                                                 if (start && start === end)
7076                                                         return start;
7077                                         }
7078                                 }
7079
7080                                 if (elm && elm.nodeType == 3)
7081                                         return elm.parentNode;
7082
7083                                 return elm;
7084                         }
7085
7086                         return rng.item ? rng.item(0) : rng.parentElement();
7087                 },
7088
7089                 getSelectedBlocks : function(st, en) {
7090                         var t = this, dom = t.dom, sb, eb, n, bl = [];
7091
7092                         sb = dom.getParent(st || t.getStart(), dom.isBlock);
7093                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);
7094
7095                         if (sb)
7096                                 bl.push(sb);
7097
7098                         if (sb && eb && sb != eb) {
7099                                 n = sb;
7100
7101                                 while ((n = n.nextSibling) && n != eb) {
7102                                         if (dom.isBlock(n))
7103                                                 bl.push(n);
7104                                 }
7105                         }
7106
7107                         if (eb && sb != eb)
7108                                 bl.push(eb);
7109
7110                         return bl;
7111                 },
7112
7113                 normalize : function() {
7114                         var self = this, rng, normalized;
7115
7116                         // Normalize only on non IE browsers for now
7117                         if (tinymce.isIE)
7118                                 return;
7119
7120                         function normalizeEndPoint(start) {
7121                                 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;
7122
7123                                 container = rng[(start ? 'start' : 'end') + 'Container'];
7124                                 offset = rng[(start ? 'start' : 'end') + 'Offset'];
7125
7126                                 // If the container is a document move it to the body element
7127                                 if (container.nodeType === 9) {
7128                                         container = container.body;
7129                                         offset = 0;
7130                                 }
7131
7132                                 // If the container is body try move it into the closest text node or position
7133                                 // TODO: Add more logic here to handle element selection cases
7134                                 if (container === body) {
7135                                         // Resolve the index
7136                                         if (container.hasChildNodes()) {
7137                                                 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
7138                                                 offset = 0;
7139
7140                                                 // Walk the DOM to find a text node to place the caret at or a BR
7141                                                 node = container;
7142                                                 walker = new tinymce.dom.TreeWalker(container, body);
7143                                                 do {
7144                                                         // Found a text node use that position
7145                                                         if (node.nodeType === 3) {
7146                                                                 offset = start ? 0 : node.nodeValue.length - 1;
7147                                                                 container = node;
7148                                                                 break;
7149                                                         }
7150
7151                                                         // Found a BR element that we can place the caret before
7152                                                         if (node.nodeName === 'BR') {
7153                                                                 offset = dom.nodeIndex(node);
7154                                                                 container = node.parentNode;
7155                                                                 break;
7156                                                         }
7157                                                 } while (node = (start ? walker.next() : walker.prev()));
7158
7159                                                 normalized = true;
7160                                         }
7161                                 }
7162
7163                                 // Set endpoint if it was normalized
7164                                 if (normalized)
7165                                         rng['set' + (start ? 'Start' : 'End')](container, offset);
7166                         };
7167
7168                         rng = self.getRng();
7169
7170                         // Normalize the end points
7171                         normalizeEndPoint(true);
7172                         
7173                         if (rng.collapsed)
7174                                 normalizeEndPoint();
7175
7176                         // Set the selection if it was normalized
7177                         if (normalized) {
7178                                 //console.log(self.dom.dumpRng(rng));
7179                                 self.setRng(rng);
7180                         }
7181                 },
7182
7183                 destroy : function(s) {
7184                         var t = this;
7185
7186                         t.win = null;
7187
7188                         // Manual destroy then remove unload handler
7189                         if (!s)
7190                                 tinymce.removeUnload(t.destroy);
7191                 },
7192
7193                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
7194                 _fixIESelection : function() {
7195                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
7196
7197                         // Make HTML element unselectable since we are going to handle selection by hand
7198                         doc.documentElement.unselectable = true;
7199
7200                         // Return range from point or null if it failed
7201                         function rngFromPoint(x, y) {
7202                                 var rng = body.createTextRange();
7203
7204                                 try {
7205                                         rng.moveToPoint(x, y);
7206                                 } catch (ex) {
7207                                         // IE sometimes throws and exception, so lets just ignore it
7208                                         rng = null;
7209                                 }
7210
7211                                 return rng;
7212                         };
7213
7214                         // Fires while the selection is changing
7215                         function selectionChange(e) {
7216                                 var pointRng;
7217
7218                                 // Check if the button is down or not
7219                                 if (e.button) {
7220                                         // Create range from mouse position
7221                                         pointRng = rngFromPoint(e.x, e.y);
7222
7223                                         if (pointRng) {
7224                                                 // Check if pointRange is before/after selection then change the endPoint
7225                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
7226                                                         pointRng.setEndPoint('StartToStart', startRng);
7227                                                 else
7228                                                         pointRng.setEndPoint('EndToEnd', startRng);
7229
7230                                                 pointRng.select();
7231                                         }
7232                                 } else
7233                                         endSelection();
7234                         }
7235
7236                         // Removes listeners
7237                         function endSelection() {
7238                                 var rng = doc.selection.createRange();
7239
7240                                 // If the range is collapsed then use the last start range
7241                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
7242                                         startRng.select();
7243
7244                                 dom.unbind(doc, 'mouseup', endSelection);
7245                                 dom.unbind(doc, 'mousemove', selectionChange);
7246                                 startRng = started = 0;
7247                         };
7248
7249                         // Detect when user selects outside BODY
7250                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
7251                                 if (e.target.nodeName === 'HTML') {
7252                                         if (started)
7253                                                 endSelection();
7254
7255                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
7256                                         htmlElm = doc.documentElement;
7257                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)
7258                                                 return;
7259
7260                                         started = 1;
7261                                         // Setup start position
7262                                         startRng = rngFromPoint(e.x, e.y);
7263                                         if (startRng) {
7264                                                 // Listen for selection change events
7265                                                 dom.bind(doc, 'mouseup', endSelection);
7266                                                 dom.bind(doc, 'mousemove', selectionChange);
7267
7268                                                 dom.win.focus();
7269                                                 startRng.select();
7270                                         }
7271                                 }
7272                         });
7273                 }
7274         });
7275 })(tinymce);
7276
7277 (function(tinymce) {
7278         tinymce.dom.Serializer = function(settings, dom, schema) {
7279                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
7280
7281                 // Support the old apply_source_formatting option
7282                 if (!settings.apply_source_formatting)
7283                         settings.indent = false;
7284
7285                 settings.remove_trailing_brs = true;
7286
7287                 // Default DOM and Schema if they are undefined
7288                 dom = dom || tinymce.DOM;
7289                 schema = schema || new tinymce.html.Schema(settings);
7290                 settings.entity_encoding = settings.entity_encoding || 'named';
7291
7292                 onPreProcess = new tinymce.util.Dispatcher(self);
7293
7294                 onPostProcess = new tinymce.util.Dispatcher(self);
7295
7296                 htmlParser = new tinymce.html.DomParser(settings, schema);
7297
7298                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
7299                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
7300                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
7301
7302                         while (i--) {
7303                                 node = nodes[i];
7304
7305                                 value = node.attributes.map[internalName];
7306                                 if (value !== undef) {
7307                                         // Set external name to internal value and remove internal
7308                                         node.attr(name, value.length > 0 ? value : null);
7309                                         node.attr(internalName, null);
7310                                 } else {
7311                                         // No internal attribute found then convert the value we have in the DOM
7312                                         value = node.attributes.map[name];
7313
7314                                         if (name === "style")
7315                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
7316                                         else if (urlConverter)
7317                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
7318
7319                                         node.attr(name, value.length > 0 ? value : null);
7320                                 }
7321                         }
7322                 });
7323
7324                 // Remove internal classes mceItem<..>
7325                 htmlParser.addAttributeFilter('class', function(nodes, name) {
7326                         var i = nodes.length, node, value;
7327
7328                         while (i--) {
7329                                 node = nodes[i];
7330                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
7331                                 node.attr('class', value.length > 0 ? value : null);
7332                         }
7333                 });
7334
7335                 // Remove bookmark elements
7336                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
7337                         var i = nodes.length, node;
7338
7339                         while (i--) {
7340                                 node = nodes[i];
7341
7342                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
7343                                         node.remove();
7344                         }
7345                 });
7346
7347                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
7348                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
7349                         var i = nodes.length, node, value;
7350
7351                         function trim(value) {
7352                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
7353                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
7354                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
7355                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
7356                         };
7357
7358                         while (i--) {
7359                                 node = nodes[i];
7360                                 value = node.firstChild ? node.firstChild.value : '';
7361
7362                                 if (name === "script") {
7363                                         // Remove mce- prefix from script elements
7364                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
7365
7366                                         if (value.length > 0)
7367                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
7368                                 } else {
7369                                         if (value.length > 0)
7370                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
7371                                 }
7372                         }
7373                 });
7374
7375                 // Convert comments to cdata and handle protected comments
7376                 htmlParser.addNodeFilter('#comment', function(nodes, name) {
7377                         var i = nodes.length, node;
7378
7379                         while (i--) {
7380                                 node = nodes[i];
7381
7382                                 if (node.value.indexOf('[CDATA[') === 0) {
7383                                         node.name = '#cdata';
7384                                         node.type = 4;
7385                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
7386                                 } else if (node.value.indexOf('mce:protected ') === 0) {
7387                                         node.name = "#text";
7388                                         node.type = 3;
7389                                         node.raw = true;
7390                                         node.value = unescape(node.value).substr(14);
7391                                 }
7392                         }
7393                 });
7394
7395                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
7396                         var i = nodes.length, node;
7397
7398                         while (i--) {
7399                                 node = nodes[i];
7400                                 if (node.type === 7)
7401                                         node.remove();
7402                                 else if (node.type === 1) {
7403                                         if (name === "input" && !("type" in node.attributes.map))
7404                                                 node.attr('type', 'text');
7405                                 }
7406                         }
7407                 });
7408
7409                 // Fix list elements, TODO: Replace this later
7410                 if (settings.fix_list_elements) {
7411                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
7412                                 var i = nodes.length, node, parentNode;
7413
7414                                 while (i--) {
7415                                         node = nodes[i];
7416                                         parentNode = node.parent;
7417
7418                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
7419                                                 if (node.prev && node.prev.name === 'li') {
7420                                                         node.prev.append(node);
7421                                                 }
7422                                         }
7423                                 }
7424                         });
7425                 }
7426
7427                 // Remove internal data attributes
7428                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
7429                         var i = nodes.length;
7430
7431                         while (i--) {
7432                                 nodes[i].attr(name, null);
7433                         }
7434                 });
7435
7436                 // Return public methods
7437                 return {
7438                         schema : schema,
7439
7440                         addNodeFilter : htmlParser.addNodeFilter,
7441
7442                         addAttributeFilter : htmlParser.addAttributeFilter,
7443
7444                         onPreProcess : onPreProcess,
7445
7446                         onPostProcess : onPostProcess,
7447
7448                         serialize : function(node, args) {
7449                                 var impl, doc, oldDoc, htmlSerializer, content;
7450
7451                                 // Explorer won't clone contents of script and style and the
7452                                 // selected index of select elements are cleared on a clone operation.
7453                                 if (isIE && dom.select('script,style,select').length > 0) {
7454                                         content = node.innerHTML;
7455                                         node = node.cloneNode(false);
7456                                         dom.setHTML(node, content);
7457                                 } else
7458                                         node = node.cloneNode(true);
7459
7460                                 // Nodes needs to be attached to something in WebKit/Opera
7461                                 // Older builds of Opera crashes if you attach the node to an document created dynamically
7462                                 // and since we can't feature detect a crash we need to sniff the acutal build number
7463                                 // This fix will make DOM ranges and make Sizzle happy!
7464                                 impl = node.ownerDocument.implementation;
7465                                 if (impl.createHTMLDocument) {
7466                                         // Create an empty HTML document
7467                                         doc = impl.createHTMLDocument("");
7468
7469                                         // Add the element or it's children if it's a body element to the new document
7470                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
7471                                                 doc.body.appendChild(doc.importNode(node, true));
7472                                         });
7473
7474                                         // Grab first child or body element for serialization
7475                                         if (node.nodeName != 'BODY')
7476                                                 node = doc.body.firstChild;
7477                                         else
7478                                                 node = doc.body;
7479
7480                                         // set the new document in DOMUtils so createElement etc works
7481                                         oldDoc = dom.doc;
7482                                         dom.doc = doc;
7483                                 }
7484
7485                                 args = args || {};
7486                                 args.format = args.format || 'html';
7487
7488                                 // Pre process
7489                                 if (!args.no_events) {
7490                                         args.node = node;
7491                                         onPreProcess.dispatch(self, args);
7492                                 }
7493
7494                                 // Setup serializer
7495                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
7496
7497                                 // Parse and serialize HTML
7498                                 args.content = htmlSerializer.serialize(
7499                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
7500                                 );
7501
7502                                 // Replace all BOM characters for now until we can find a better solution
7503                                 if (!args.cleanup)
7504                                         args.content = args.content.replace(/\uFEFF/g, '');
7505
7506                                 // Post process
7507                                 if (!args.no_events)
7508                                         onPostProcess.dispatch(self, args);
7509
7510                                 // Restore the old document if it was changed
7511                                 if (oldDoc)
7512                                         dom.doc = oldDoc;
7513
7514                                 args.node = null;
7515
7516                                 return args.content;
7517                         },
7518
7519                         addRules : function(rules) {
7520                                 schema.addValidElements(rules);
7521                         },
7522
7523                         setRules : function(rules) {
7524                                 schema.setValidElements(rules);
7525                         }
7526                 };
7527         };
7528 })(tinymce);
7529 (function(tinymce) {
7530         tinymce.dom.ScriptLoader = function(settings) {
7531                 var QUEUED = 0,
7532                         LOADING = 1,
7533                         LOADED = 2,
7534                         states = {},
7535                         queue = [],
7536                         scriptLoadedCallbacks = {},
7537                         queueLoadedCallbacks = [],
7538                         loading = 0,
7539                         undefined;
7540
7541                 function loadScript(url, callback) {
7542                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;
7543
7544                         // Execute callback when script is loaded
7545                         function done() {
7546                                 dom.remove(id);
7547
7548                                 if (elm)
7549                                         elm.onreadystatechange = elm.onload = elm = null;
7550
7551                                 callback();
7552                         };
7553                         
7554                         function error() {
7555                                 // Report the error so it's easier for people to spot loading errors
7556                                 if (typeof(console) !== "undefined" && console.log)
7557                                         console.log("Failed to load: " + url);
7558
7559                                 // We can't mark it as done if there is a load error since
7560                                 // A) We don't want to produce 404 errors on the server and
7561                                 // B) the onerror event won't fire on all browsers.
7562                                 // done();
7563                         };
7564
7565                         id = dom.uniqueId();
7566
7567                         if (tinymce.isIE6) {
7568                                 uri = new tinymce.util.URI(url);
7569                                 loc = location;
7570
7571                                 // If script is from same domain and we
7572                                 // use IE 6 then use XHR since it's more reliable
7573                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
7574                                         tinymce.util.XHR.send({
7575                                                 url : tinymce._addVer(uri.getURI()),
7576                                                 success : function(content) {
7577                                                         // Create new temp script element
7578                                                         var script = dom.create('script', {
7579                                                                 type : 'text/javascript'
7580                                                         });
7581
7582                                                         // Evaluate script in global scope
7583                                                         script.text = content;
7584                                                         document.getElementsByTagName('head')[0].appendChild(script);
7585                                                         dom.remove(script);
7586
7587                                                         done();
7588                                                 },
7589                                                 
7590                                                 error : error
7591                                         });
7592
7593                                         return;
7594                                 }
7595                         }
7596
7597                         // Create new script element
7598                         elm = dom.create('script', {
7599                                 id : id,
7600                                 type : 'text/javascript',
7601                                 src : tinymce._addVer(url)
7602                         });
7603
7604                         // Add onload listener for non IE browsers since IE9
7605                         // fires onload event before the script is parsed and executed
7606                         if (!tinymce.isIE)
7607                                 elm.onload = done;
7608
7609                         // Add onerror event will get fired on some browsers but not all of them
7610                         elm.onerror = error;
7611
7612                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
7613                         if (!tinymce.isOpera) {
7614                                 elm.onreadystatechange = function() {
7615                                         var state = elm.readyState;
7616
7617                                         // Loaded state is passed on IE 6 however there
7618                                         // are known issues with this method but we can't use
7619                                         // XHR in a cross domain loading
7620                                         if (state == 'complete' || state == 'loaded')
7621                                                 done();
7622                                 };
7623                         }
7624
7625                         // Most browsers support this feature so we report errors
7626                         // for those at least to help users track their missing plugins etc
7627                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
7628                         /*elm.onerror = function() {
7629                                 alert('Failed to load: ' + url);
7630                         };*/
7631
7632                         // Add script to document
7633                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
7634                 };
7635
7636                 this.isDone = function(url) {
7637                         return states[url] == LOADED;
7638                 };
7639
7640                 this.markDone = function(url) {
7641                         states[url] = LOADED;
7642                 };
7643
7644                 this.add = this.load = function(url, callback, scope) {
7645                         var item, state = states[url];
7646
7647                         // Add url to load queue
7648                         if (state == undefined) {
7649                                 queue.push(url);
7650                                 states[url] = QUEUED;
7651                         }
7652
7653                         if (callback) {
7654                                 // Store away callback for later execution
7655                                 if (!scriptLoadedCallbacks[url])
7656                                         scriptLoadedCallbacks[url] = [];
7657
7658                                 scriptLoadedCallbacks[url].push({
7659                                         func : callback,
7660                                         scope : scope || this
7661                                 });
7662                         }
7663                 };
7664
7665                 this.loadQueue = function(callback, scope) {
7666                         this.loadScripts(queue, callback, scope);
7667                 };
7668
7669                 this.loadScripts = function(scripts, callback, scope) {
7670                         var loadScripts;
7671
7672                         function execScriptLoadedCallbacks(url) {
7673                                 // Execute URL callback functions
7674                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
7675                                         callback.func.call(callback.scope);
7676                                 });
7677
7678                                 scriptLoadedCallbacks[url] = undefined;
7679                         };
7680
7681                         queueLoadedCallbacks.push({
7682                                 func : callback,
7683                                 scope : scope || this
7684                         });
7685
7686                         loadScripts = function() {
7687                                 var loadingScripts = tinymce.grep(scripts);
7688
7689                                 // Current scripts has been handled
7690                                 scripts.length = 0;
7691
7692                                 // Load scripts that needs to be loaded
7693                                 tinymce.each(loadingScripts, function(url) {
7694                                         // Script is already loaded then execute script callbacks directly
7695                                         if (states[url] == LOADED) {
7696                                                 execScriptLoadedCallbacks(url);
7697                                                 return;
7698                                         }
7699
7700                                         // Is script not loading then start loading it
7701                                         if (states[url] != LOADING) {
7702                                                 states[url] = LOADING;
7703                                                 loading++;
7704
7705                                                 loadScript(url, function() {
7706                                                         states[url] = LOADED;
7707                                                         loading--;
7708
7709                                                         execScriptLoadedCallbacks(url);
7710
7711                                                         // Load more scripts if they where added by the recently loaded script
7712                                                         loadScripts();
7713                                                 });
7714                                         }
7715                                 });
7716
7717                                 // No scripts are currently loading then execute all pending queue loaded callbacks
7718                                 if (!loading) {
7719                                         tinymce.each(queueLoadedCallbacks, function(callback) {
7720                                                 callback.func.call(callback.scope);
7721                                         });
7722
7723                                         queueLoadedCallbacks.length = 0;
7724                                 }
7725                         };
7726
7727                         loadScripts();
7728                 };
7729         };
7730
7731         // Global script loader
7732         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
7733 })(tinymce);
7734
7735 tinymce.dom.TreeWalker = function(start_node, root_node) {
7736         var node = start_node;
7737
7738         function findSibling(node, start_name, sibling_name, shallow) {
7739                 var sibling, parent;
7740
7741                 if (node) {
7742                         // Walk into nodes if it has a start
7743                         if (!shallow && node[start_name])
7744                                 return node[start_name];
7745
7746                         // Return the sibling if it has one
7747                         if (node != root_node) {
7748                                 sibling = node[sibling_name];
7749                                 if (sibling)
7750                                         return sibling;
7751
7752                                 // Walk up the parents to look for siblings
7753                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
7754                                         sibling = parent[sibling_name];
7755                                         if (sibling)
7756                                                 return sibling;
7757                                 }
7758                         }
7759                 }
7760         };
7761
7762         this.current = function() {
7763                 return node;
7764         };
7765
7766         this.next = function(shallow) {
7767                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
7768         };
7769
7770         this.prev = function(shallow) {
7771                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
7772         };
7773 };
7774
7775 (function(tinymce) {
7776         tinymce.dom.RangeUtils = function(dom) {
7777                 var INVISIBLE_CHAR = '\uFEFF';
7778
7779                 this.walk = function(rng, callback) {
7780                         var startContainer = rng.startContainer,
7781                                 startOffset = rng.startOffset,
7782                                 endContainer = rng.endContainer,
7783                                 endOffset = rng.endOffset,
7784                                 ancestor, startPoint,
7785                                 endPoint, node, parent, siblings, nodes;
7786
7787                         // Handle table cell selection the table plugin enables
7788                         // you to fake select table cells and perform formatting actions on them
7789                         nodes = dom.select('td.mceSelected,th.mceSelected');
7790                         if (nodes.length > 0) {
7791                                 tinymce.each(nodes, function(node) {
7792                                         callback([node]);
7793                                 });
7794
7795                                 return;
7796                         }
7797
7798                         function collectSiblings(node, name, end_node) {
7799                                 var siblings = [];
7800
7801                                 for (; node && node != end_node; node = node[name])
7802                                         siblings.push(node);
7803
7804                                 return siblings;
7805                         };
7806
7807                         function findEndPoint(node, root) {
7808                                 do {
7809                                         if (node.parentNode == root)
7810                                                 return node;
7811
7812                                         node = node.parentNode;
7813                                 } while(node);
7814                         };
7815
7816                         function walkBoundary(start_node, end_node, next) {
7817                                 var siblingName = next ? 'nextSibling' : 'previousSibling';
7818
7819                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
7820                                         parent = node.parentNode;
7821                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
7822
7823                                         if (siblings.length) {
7824                                                 if (!next)
7825                                                         siblings.reverse();
7826
7827                                                 callback(siblings);
7828                                         }
7829                                 }
7830                         };
7831
7832                         // If index based start position then resolve it
7833                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
7834                                 startContainer = startContainer.childNodes[startOffset];
7835
7836                         // If index based end position then resolve it
7837                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
7838                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
7839
7840                         // Find common ancestor and end points
7841                         ancestor = dom.findCommonAncestor(startContainer, endContainer);
7842
7843                         // Same container
7844                         if (startContainer == endContainer)
7845                                 return callback([startContainer]);
7846
7847                         // Process left side
7848                         for (node = startContainer; node; node = node.parentNode) {
7849                                 if (node == endContainer)
7850                                         return walkBoundary(startContainer, ancestor, true);
7851
7852                                 if (node == ancestor)
7853                                         break;
7854                         }
7855
7856                         // Process right side
7857                         for (node = endContainer; node; node = node.parentNode) {
7858                                 if (node == startContainer)
7859                                         return walkBoundary(endContainer, ancestor);
7860
7861                                 if (node == ancestor)
7862                                         break;
7863                         }
7864
7865                         // Find start/end point
7866                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;
7867                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;
7868
7869                         // Walk left leaf
7870                         walkBoundary(startContainer, startPoint, true);
7871
7872                         // Walk the middle from start to end point
7873                         siblings = collectSiblings(
7874                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
7875                                 'nextSibling',
7876                                 endPoint == endContainer ? endPoint.nextSibling : endPoint
7877                         );
7878
7879                         if (siblings.length)
7880                                 callback(siblings);
7881
7882                         // Walk right leaf
7883                         walkBoundary(endContainer, endPoint);
7884                 };
7885
7886                 /*              this.split = function(rng) {
7887                         var startContainer = rng.startContainer,
7888                                 startOffset = rng.startOffset,
7889                                 endContainer = rng.endContainer,
7890                                 endOffset = rng.endOffset;
7891
7892                         function splitText(node, offset) {
7893                                 if (offset == node.nodeValue.length)
7894                                         node.appendData(INVISIBLE_CHAR);
7895
7896                                 node = node.splitText(offset);
7897
7898                                 if (node.nodeValue === INVISIBLE_CHAR)
7899                                         node.nodeValue = '';
7900
7901                                 return node;
7902                         };
7903
7904                         // Handle single text node
7905                         if (startContainer == endContainer) {
7906                                 if (startContainer.nodeType == 3) {
7907                                         if (startOffset != 0)
7908                                                 startContainer = endContainer = splitText(startContainer, startOffset);
7909
7910                                         if (endOffset - startOffset != startContainer.nodeValue.length)
7911                                                 splitText(startContainer, endOffset - startOffset);
7912                                 }
7913                         } else {
7914                                 // Split startContainer text node if needed
7915                                 if (startContainer.nodeType == 3 && startOffset != 0) {
7916                                         startContainer = splitText(startContainer, startOffset);
7917                                         startOffset = 0;
7918                                 }
7919
7920                                 // Split endContainer text node if needed
7921                                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
7922                                         endContainer = splitText(endContainer, endOffset).previousSibling;
7923                                         endOffset = endContainer.nodeValue.length;
7924                                 }
7925                         }
7926
7927                         return {
7928                                 startContainer : startContainer,
7929                                 startOffset : startOffset,
7930                                 endContainer : endContainer,
7931                                 endOffset : endOffset
7932                         };
7933                 };
7934 */
7935         };
7936
7937         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
7938                 if (rng1 && rng2) {
7939                         // Compare native IE ranges
7940                         if (rng1.item || rng1.duplicate) {
7941                                 // Both are control ranges and the selected element matches
7942                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
7943                                         return true;
7944
7945                                 // Both are text ranges and the range matches
7946                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
7947                                         return true;
7948                         } else {
7949                                 // Compare w3c ranges
7950                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
7951                         }
7952                 }
7953
7954                 return false;
7955         };
7956 })(tinymce);
7957
7958 (function(tinymce) {
7959         var Event = tinymce.dom.Event, each = tinymce.each;
7960
7961         tinymce.create('tinymce.ui.KeyboardNavigation', {
7962                 KeyboardNavigation: function(settings, dom) {
7963                         var t = this, root = settings.root, items = settings.items,
7964                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
7965                                         excludeFromTabOrder = settings.excludeFromTabOrder,
7966                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
7967
7968                         dom = dom || tinymce.DOM;
7969
7970                         itemFocussed = function(evt) {
7971                                 focussedId = evt.target.id;
7972                         };
7973                         
7974                         itemBlurred = function(evt) {
7975                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');
7976                         };
7977                         
7978                         rootFocussed = function(evt) {
7979                                 var item = dom.get(focussedId);
7980                                 dom.setAttrib(item, 'tabindex', '0');
7981                                 item.focus();
7982                         };
7983                         
7984                         t.focus = function() {
7985                                 dom.get(focussedId).focus();
7986                         };
7987
7988                         t.destroy = function() {
7989                                 each(items, function(item) {
7990                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);
7991                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);
7992                                 });
7993
7994                                 dom.unbind(dom.get(root), 'focus', rootFocussed);
7995                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);
7996
7997                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
7998                                 t.destroy = function() {};
7999                         };
8000                         
8001                         t.moveFocus = function(dir, evt) {
8002                                 var idx = -1, controls = t.controls, newFocus;
8003
8004                                 if (!focussedId)
8005                                         return;
8006
8007                                 each(items, function(item, index) {
8008                                         if (item.id === focussedId) {
8009                                                 idx = index;
8010                                                 return false;
8011                                         }
8012                                 });
8013
8014                                 idx += dir;
8015                                 if (idx < 0) {
8016                                         idx = items.length - 1;
8017                                 } else if (idx >= items.length) {
8018                                         idx = 0;
8019                                 }
8020                                 
8021                                 newFocus = items[idx];
8022                                 dom.setAttrib(focussedId, 'tabindex', '-1');
8023                                 dom.setAttrib(newFocus.id, 'tabindex', '0');
8024                                 dom.get(newFocus.id).focus();
8025
8026                                 if (settings.actOnFocus) {
8027                                         settings.onAction(newFocus.id);
8028                                 }
8029
8030                                 if (evt)
8031                                         Event.cancel(evt);
8032                         };
8033                         
8034                         rootKeydown = function(evt) {
8035                                 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;
8036                                 
8037                                 switch (evt.keyCode) {
8038                                         case DOM_VK_LEFT:
8039                                                 if (enableLeftRight) t.moveFocus(-1);
8040                                                 break;
8041         
8042                                         case DOM_VK_RIGHT:
8043                                                 if (enableLeftRight) t.moveFocus(1);
8044                                                 break;
8045         
8046                                         case DOM_VK_UP:
8047                                                 if (enableUpDown) t.moveFocus(-1);
8048                                                 break;
8049
8050                                         case DOM_VK_DOWN:
8051                                                 if (enableUpDown) t.moveFocus(1);
8052                                                 break;
8053
8054                                         case DOM_VK_ESCAPE:
8055                                                 if (settings.onCancel) {
8056                                                         settings.onCancel();
8057                                                         Event.cancel(evt);
8058                                                 }
8059                                                 break;
8060
8061                                         case DOM_VK_ENTER:
8062                                         case DOM_VK_RETURN:
8063                                         case DOM_VK_SPACE:
8064                                                 if (settings.onAction) {
8065                                                         settings.onAction(focussedId);
8066                                                         Event.cancel(evt);
8067                                                 }
8068                                                 break;
8069                                 }
8070                         };
8071
8072                         // Set up state and listeners for each item.
8073                         each(items, function(item, idx) {
8074                                 var tabindex;
8075
8076                                 if (!item.id) {
8077                                         item.id = dom.uniqueId('_mce_item_');
8078                                 }
8079
8080                                 if (excludeFromTabOrder) {
8081                                         dom.bind(item.id, 'blur', itemBlurred);
8082                                         tabindex = '-1';
8083                                 } else {
8084                                         tabindex = (idx === 0 ? '0' : '-1');
8085                                 }
8086
8087                                 dom.setAttrib(item.id, 'tabindex', tabindex);
8088                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);
8089                         });
8090                         
8091                         // Setup initial state for root element.
8092                         if (items[0]){
8093                                 focussedId = items[0].id;
8094                         }
8095
8096                         dom.setAttrib(root, 'tabindex', '-1');
8097                         
8098                         // Setup listeners for root element.
8099                         dom.bind(dom.get(root), 'focus', rootFocussed);
8100                         dom.bind(dom.get(root), 'keydown', rootKeydown);
8101                 }
8102         });
8103 })(tinymce);
8104 (function(tinymce) {
8105         // Shorten class names
8106         var DOM = tinymce.DOM, is = tinymce.is;
8107
8108         tinymce.create('tinymce.ui.Control', {
8109                 Control : function(id, s, editor) {
8110                         this.id = id;
8111                         this.settings = s = s || {};
8112                         this.rendered = false;
8113                         this.onRender = new tinymce.util.Dispatcher(this);
8114                         this.classPrefix = '';
8115                         this.scope = s.scope || this;
8116                         this.disabled = 0;
8117                         this.active = 0;
8118                         this.editor = editor;
8119                 },
8120                 
8121                 setAriaProperty : function(property, value) {
8122                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
8123                         if (element) {
8124                                 DOM.setAttrib(element, 'aria-' + property, !!value);
8125                         }
8126                 },
8127                 
8128                 focus : function() {
8129                         DOM.get(this.id).focus();
8130                 },
8131
8132                 setDisabled : function(s) {
8133                         if (s != this.disabled) {
8134                                 this.setAriaProperty('disabled', s);
8135
8136                                 this.setState('Disabled', s);
8137                                 this.setState('Enabled', !s);
8138                                 this.disabled = s;
8139                         }
8140                 },
8141
8142                 isDisabled : function() {
8143                         return this.disabled;
8144                 },
8145
8146                 setActive : function(s) {
8147                         if (s != this.active) {
8148                                 this.setState('Active', s);
8149                                 this.active = s;
8150                                 this.setAriaProperty('pressed', s);
8151                         }
8152                 },
8153
8154                 isActive : function() {
8155                         return this.active;
8156                 },
8157
8158                 setState : function(c, s) {
8159                         var n = DOM.get(this.id);
8160
8161                         c = this.classPrefix + c;
8162
8163                         if (s)
8164                                 DOM.addClass(n, c);
8165                         else
8166                                 DOM.removeClass(n, c);
8167                 },
8168
8169                 isRendered : function() {
8170                         return this.rendered;
8171                 },
8172
8173                 renderHTML : function() {
8174                 },
8175
8176                 renderTo : function(n) {
8177                         DOM.setHTML(n, this.renderHTML());
8178                 },
8179
8180                 postRender : function() {
8181                         var t = this, b;
8182
8183                         // Set pending states
8184                         if (is(t.disabled)) {
8185                                 b = t.disabled;
8186                                 t.disabled = -1;
8187                                 t.setDisabled(b);
8188                         }
8189
8190                         if (is(t.active)) {
8191                                 b = t.active;
8192                                 t.active = -1;
8193                                 t.setActive(b);
8194                         }
8195                 },
8196
8197                 remove : function() {
8198                         DOM.remove(this.id);
8199                         this.destroy();
8200                 },
8201
8202                 destroy : function() {
8203                         tinymce.dom.Event.clear(this.id);
8204                 }
8205         });
8206 })(tinymce);
8207 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
8208         Container : function(id, s, editor) {
8209                 this.parent(id, s, editor);
8210
8211                 this.controls = [];
8212
8213                 this.lookup = {};
8214         },
8215
8216         add : function(c) {
8217                 this.lookup[c.id] = c;
8218                 this.controls.push(c);
8219
8220                 return c;
8221         },
8222
8223         get : function(n) {
8224                 return this.lookup[n];
8225         }
8226 });
8227
8228
8229 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
8230         Separator : function(id, s) {
8231                 this.parent(id, s);
8232                 this.classPrefix = 'mceSeparator';
8233                 this.setDisabled(true);
8234         },
8235
8236         renderHTML : function() {
8237                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
8238         }
8239 });
8240
8241 (function(tinymce) {
8242         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8243
8244         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
8245                 MenuItem : function(id, s) {
8246                         this.parent(id, s);
8247                         this.classPrefix = 'mceMenuItem';
8248                 },
8249
8250                 setSelected : function(s) {
8251                         this.setState('Selected', s);
8252                         this.setAriaProperty('checked', !!s);
8253                         this.selected = s;
8254                 },
8255
8256                 isSelected : function() {
8257                         return this.selected;
8258                 },
8259
8260                 postRender : function() {
8261                         var t = this;
8262                         
8263                         t.parent();
8264
8265                         // Set pending state
8266                         if (is(t.selected))
8267                                 t.setSelected(t.selected);
8268                 }
8269         });
8270 })(tinymce);
8271
8272 (function(tinymce) {
8273         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8274
8275         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
8276                 Menu : function(id, s) {
8277                         var t = this;
8278
8279                         t.parent(id, s);
8280                         t.items = {};
8281                         t.collapsed = false;
8282                         t.menuCount = 0;
8283                         t.onAddItem = new tinymce.util.Dispatcher(this);
8284                 },
8285
8286                 expand : function(d) {
8287                         var t = this;
8288
8289                         if (d) {
8290                                 walk(t, function(o) {
8291                                         if (o.expand)
8292                                                 o.expand();
8293                                 }, 'items', t);
8294                         }
8295
8296                         t.collapsed = false;
8297                 },
8298
8299                 collapse : function(d) {
8300                         var t = this;
8301
8302                         if (d) {
8303                                 walk(t, function(o) {
8304                                         if (o.collapse)
8305                                                 o.collapse();
8306                                 }, 'items', t);
8307                         }
8308
8309                         t.collapsed = true;
8310                 },
8311
8312                 isCollapsed : function() {
8313                         return this.collapsed;
8314                 },
8315
8316                 add : function(o) {
8317                         if (!o.settings)
8318                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
8319
8320                         this.onAddItem.dispatch(this, o);
8321
8322                         return this.items[o.id] = o;
8323                 },
8324
8325                 addSeparator : function() {
8326                         return this.add({separator : true});
8327                 },
8328
8329                 addMenu : function(o) {
8330                         if (!o.collapse)
8331                                 o = this.createMenu(o);
8332
8333                         this.menuCount++;
8334
8335                         return this.add(o);
8336                 },
8337
8338                 hasMenus : function() {
8339                         return this.menuCount !== 0;
8340                 },
8341
8342                 remove : function(o) {
8343                         delete this.items[o.id];
8344                 },
8345
8346                 removeAll : function() {
8347                         var t = this;
8348
8349                         walk(t, function(o) {
8350                                 if (o.removeAll)
8351                                         o.removeAll();
8352                                 else
8353                                         o.remove();
8354
8355                                 o.destroy();
8356                         }, 'items', t);
8357
8358                         t.items = {};
8359                 },
8360
8361                 createMenu : function(o) {
8362                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
8363
8364                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
8365
8366                         return m;
8367                 }
8368         });
8369 })(tinymce);
8370 (function(tinymce) {
8371         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
8372
8373         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
8374                 DropMenu : function(id, s) {
8375                         s = s || {};
8376                         s.container = s.container || DOM.doc.body;
8377                         s.offset_x = s.offset_x || 0;
8378                         s.offset_y = s.offset_y || 0;
8379                         s.vp_offset_x = s.vp_offset_x || 0;
8380                         s.vp_offset_y = s.vp_offset_y || 0;
8381
8382                         if (is(s.icons) && !s.icons)
8383                                 s['class'] += ' mceNoIcons';
8384
8385                         this.parent(id, s);
8386                         this.onShowMenu = new tinymce.util.Dispatcher(this);
8387                         this.onHideMenu = new tinymce.util.Dispatcher(this);
8388                         this.classPrefix = 'mceMenu';
8389                 },
8390
8391                 createMenu : function(s) {
8392                         var t = this, cs = t.settings, m;
8393
8394                         s.container = s.container || cs.container;
8395                         s.parent = t;
8396                         s.constrain = s.constrain || cs.constrain;
8397                         s['class'] = s['class'] || cs['class'];
8398                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
8399                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
8400                         s.keyboard_focus = cs.keyboard_focus;
8401                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
8402
8403                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
8404
8405                         return m;
8406                 },
8407                 
8408                 focus : function() {
8409                         var t = this;
8410                         if (t.keyboardNav) {
8411                                 t.keyboardNav.focus();
8412                         }
8413                 },
8414
8415                 update : function() {
8416                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
8417
8418                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
8419                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
8420
8421                         if (!DOM.boxModel)
8422                                 t.element.setStyles({width : tw + 2, height : th + 2});
8423                         else
8424                                 t.element.setStyles({width : tw, height : th});
8425
8426                         if (s.max_width)
8427                                 DOM.setStyle(co, 'width', tw);
8428
8429                         if (s.max_height) {
8430                                 DOM.setStyle(co, 'height', th);
8431
8432                                 if (tb.clientHeight < s.max_height)
8433                                         DOM.setStyle(co, 'overflow', 'hidden');
8434                         }
8435                 },
8436
8437                 showMenu : function(x, y, px) {
8438                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
8439
8440                         t.collapse(1);
8441
8442                         if (t.isMenuVisible)
8443                                 return;
8444
8445                         if (!t.rendered) {
8446                                 co = DOM.add(t.settings.container, t.renderNode());
8447
8448                                 each(t.items, function(o) {
8449                                         o.postRender();
8450                                 });
8451
8452                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8453                         } else
8454                                 co = DOM.get('menu_' + t.id);
8455
8456                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
8457                         if (!tinymce.isOpera)
8458                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
8459
8460                         DOM.show(co);
8461                         t.update();
8462
8463                         x += s.offset_x || 0;
8464                         y += s.offset_y || 0;
8465                         vp.w -= 4;
8466                         vp.h -= 4;
8467
8468                         // Move inside viewport if not submenu
8469                         if (s.constrain) {
8470                                 w = co.clientWidth - ot;
8471                                 h = co.clientHeight - ot;
8472                                 mx = vp.x + vp.w;
8473                                 my = vp.y + vp.h;
8474
8475                                 if ((x + s.vp_offset_x + w) > mx)
8476                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
8477
8478                                 if ((y + s.vp_offset_y + h) > my)
8479                                         y = Math.max(0, (my - s.vp_offset_y) - h);
8480                         }
8481
8482                         DOM.setStyles(co, {left : x , top : y});
8483                         t.element.update();
8484
8485                         t.isMenuVisible = 1;
8486                         t.mouseClickFunc = Event.add(co, 'click', function(e) {
8487                                 var m;
8488
8489                                 e = e.target;
8490
8491                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
8492                                         m = t.items[e.id];
8493
8494                                         if (m.isDisabled())
8495                                                 return;
8496
8497                                         dm = t;
8498
8499                                         while (dm) {
8500                                                 if (dm.hideMenu)
8501                                                         dm.hideMenu();
8502
8503                                                 dm = dm.settings.parent;
8504                                         }
8505
8506                                         if (m.settings.onclick)
8507                                                 m.settings.onclick(e);
8508
8509                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
8510                                 }
8511                         });
8512
8513                         if (t.hasMenus()) {
8514                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
8515                                         var m, r, mi;
8516
8517                                         e = e.target;
8518                                         if (e && (e = DOM.getParent(e, 'tr'))) {
8519                                                 m = t.items[e.id];
8520
8521                                                 if (t.lastMenu)
8522                                                         t.lastMenu.collapse(1);
8523
8524                                                 if (m.isDisabled())
8525                                                         return;
8526
8527                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
8528                                                         //p = DOM.getPos(s.container);
8529                                                         r = DOM.getRect(e);
8530                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
8531                                                         t.lastMenu = m;
8532                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
8533                                                 }
8534                                         }
8535                                 });
8536                         }
8537                         
8538                         Event.add(co, 'keydown', t._keyHandler, t);
8539
8540                         t.onShowMenu.dispatch(t);
8541
8542                         if (s.keyboard_focus) { 
8543                                 t._setupKeyboardNav(); 
8544                         }
8545                 },
8546
8547                 hideMenu : function(c) {
8548                         var t = this, co = DOM.get('menu_' + t.id), e;
8549
8550                         if (!t.isMenuVisible)
8551                                 return;
8552
8553                         if (t.keyboardNav) t.keyboardNav.destroy();
8554                         Event.remove(co, 'mouseover', t.mouseOverFunc);
8555                         Event.remove(co, 'click', t.mouseClickFunc);
8556                         Event.remove(co, 'keydown', t._keyHandler);
8557                         DOM.hide(co);
8558                         t.isMenuVisible = 0;
8559
8560                         if (!c)
8561                                 t.collapse(1);
8562
8563                         if (t.element)
8564                                 t.element.hide();
8565
8566                         if (e = DOM.get(t.id))
8567                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
8568
8569                         t.onHideMenu.dispatch(t);
8570                 },
8571
8572                 add : function(o) {
8573                         var t = this, co;
8574
8575                         o = t.parent(o);
8576
8577                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))
8578                                 t._add(DOM.select('tbody', co)[0], o);
8579
8580                         return o;
8581                 },
8582
8583                 collapse : function(d) {
8584                         this.parent(d);
8585                         this.hideMenu(1);
8586                 },
8587
8588                 remove : function(o) {
8589                         DOM.remove(o.id);
8590                         this.destroy();
8591
8592                         return this.parent(o);
8593                 },
8594
8595                 destroy : function() {
8596                         var t = this, co = DOM.get('menu_' + t.id);
8597
8598                         if (t.keyboardNav) t.keyboardNav.destroy();
8599                         Event.remove(co, 'mouseover', t.mouseOverFunc);
8600                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
8601                         Event.remove(co, 'click', t.mouseClickFunc);
8602                         Event.remove(co, 'keydown', t._keyHandler);
8603
8604                         if (t.element)
8605                                 t.element.remove();
8606
8607                         DOM.remove(co);
8608                 },
8609
8610                 renderNode : function() {
8611                         var t = this, s = t.settings, n, tb, co, w;
8612
8613                         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'});
8614                         if (t.settings.parent) {
8615                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
8616                         }
8617                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
8618                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8619
8620                         if (s.menu_line)
8621                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
8622
8623 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
8624                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
8625                         tb = DOM.add(n, 'tbody');
8626
8627                         each(t.items, function(o) {
8628                                 t._add(tb, o);
8629                         });
8630
8631                         t.rendered = true;
8632
8633                         return w;
8634                 },
8635
8636                 // Internal functions
8637                 _setupKeyboardNav : function(){
8638                         var contextMenu, menuItems, t=this; 
8639                         contextMenu = DOM.select('#menu_' + t.id)[0];
8640                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
8641                         menuItems.splice(0,0,contextMenu);
8642                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({
8643                                 root: 'menu_' + t.id,
8644                                 items: menuItems,
8645                                 onCancel: function() {
8646                                         t.hideMenu();
8647                                 },
8648                                 enableUpDown: true
8649                         });
8650                         contextMenu.focus();
8651                 },
8652
8653                 _keyHandler : function(evt) {
8654                         var t = this, e;
8655                         switch (evt.keyCode) {
8656                                 case 37: // Left
8657                                         if (t.settings.parent) {
8658                                                 t.hideMenu();
8659                                                 t.settings.parent.focus();
8660                                                 Event.cancel(evt);
8661                                         }
8662                                         break;
8663                                 case 39: // Right
8664                                         if (t.mouseOverFunc)
8665                                                 t.mouseOverFunc(evt);
8666                                         break;
8667                         }
8668                 },
8669
8670                 _add : function(tb, o) {
8671                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
8672
8673                         if (s.separator) {
8674                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
8675                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
8676
8677                                 if (n = ro.previousSibling)
8678                                         DOM.addClass(n, 'mceLast');
8679
8680                                 return;
8681                         }
8682
8683                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
8684                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
8685                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
8686
8687                         if (s.parent) {
8688                                 DOM.setAttrib(a, 'aria-haspopup', 'true');
8689                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
8690                         }
8691
8692                         DOM.addClass(it, s['class']);
8693 //                      n = DOM.add(n, 'span', {'class' : 'item'});
8694
8695                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
8696
8697                         if (s.icon_src)
8698                                 DOM.add(ic, 'img', {src : s.icon_src});
8699
8700                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
8701
8702                         if (o.settings.style)
8703                                 DOM.setAttrib(n, 'style', o.settings.style);
8704
8705                         if (tb.childNodes.length == 1)
8706                                 DOM.addClass(ro, 'mceFirst');
8707
8708                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
8709                                 DOM.addClass(ro, 'mceFirst');
8710
8711                         if (o.collapse)
8712                                 DOM.addClass(ro, cp + 'ItemSub');
8713
8714                         if (n = ro.previousSibling)
8715                                 DOM.removeClass(n, 'mceLast');
8716
8717                         DOM.addClass(ro, 'mceLast');
8718                 }
8719         });
8720 })(tinymce);
8721 (function(tinymce) {
8722         var DOM = tinymce.DOM;
8723
8724         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
8725                 Button : function(id, s, ed) {
8726                         this.parent(id, s, ed);
8727                         this.classPrefix = 'mceButton';
8728                 },
8729
8730                 renderHTML : function() {
8731                         var cp = this.classPrefix, s = this.settings, h, l;
8732
8733                         l = DOM.encode(s.label || '');
8734                         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) + '">';
8735                         if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
8736                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
8737                         else
8738                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
8739
8740                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
8741                         h += '</a>';
8742                         return h;
8743                 },
8744
8745                 postRender : function() {
8746                         var t = this, s = t.settings;
8747
8748                         tinymce.dom.Event.add(t.id, 'click', function(e) {
8749                                 if (!t.isDisabled())
8750                                         return s.onclick.call(s.scope, e);
8751                         });
8752                 }
8753         });
8754 })(tinymce);
8755
8756 (function(tinymce) {
8757         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
8758
8759         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
8760                 ListBox : function(id, s, ed) {
8761                         var t = this;
8762
8763                         t.parent(id, s, ed);
8764
8765                         t.items = [];
8766
8767                         t.onChange = new Dispatcher(t);
8768
8769                         t.onPostRender = new Dispatcher(t);
8770
8771                         t.onAdd = new Dispatcher(t);
8772
8773                         t.onRenderMenu = new tinymce.util.Dispatcher(this);
8774
8775                         t.classPrefix = 'mceListBox';
8776                 },
8777
8778                 select : function(va) {
8779                         var t = this, fv, f;
8780
8781                         if (va == undefined)
8782                                 return t.selectByIndex(-1);
8783
8784                         // Is string or number make function selector
8785                         if (va && va.call)
8786                                 f = va;
8787                         else {
8788                                 f = function(v) {
8789                                         return v == va;
8790                                 };
8791                         }
8792
8793                         // Do we need to do something?
8794                         if (va != t.selectedValue) {
8795                                 // Find item
8796                                 each(t.items, function(o, i) {
8797                                         if (f(o.value)) {
8798                                                 fv = 1;
8799                                                 t.selectByIndex(i);
8800                                                 return false;
8801                                         }
8802                                 });
8803
8804                                 if (!fv)
8805                                         t.selectByIndex(-1);
8806                         }
8807                 },
8808
8809                 selectByIndex : function(idx) {
8810                         var t = this, e, o;
8811
8812                         if (idx != t.selectedIndex) {
8813                                 e = DOM.get(t.id + '_text');
8814                                 o = t.items[idx];
8815
8816                                 if (o) {
8817                                         t.selectedValue = o.value;
8818                                         t.selectedIndex = idx;
8819                                         DOM.setHTML(e, DOM.encode(o.title));
8820                                         DOM.removeClass(e, 'mceTitle');
8821                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);
8822                                 } else {
8823                                         DOM.setHTML(e, DOM.encode(t.settings.title));
8824                                         DOM.addClass(e, 'mceTitle');
8825                                         t.selectedValue = t.selectedIndex = null;
8826                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
8827                                 }
8828                                 e = 0;
8829                         }
8830                 },
8831
8832                 add : function(n, v, o) {
8833                         var t = this;
8834
8835                         o = o || {};
8836                         o = tinymce.extend(o, {
8837                                 title : n,
8838                                 value : v
8839                         });
8840
8841                         t.items.push(o);
8842                         t.onAdd.dispatch(t, o);
8843                 },
8844
8845                 getLength : function() {
8846                         return this.items.length;
8847                 },
8848
8849                 renderHTML : function() {
8850                         var h = '', t = this, s = t.settings, cp = t.classPrefix;
8851
8852                         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>';
8853                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
8854                         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>';
8855                         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>';
8856                         h += '</tr></tbody></table></span>';
8857
8858                         return h;
8859                 },
8860
8861                 showMenu : function() {
8862                         var t = this, p2, e = DOM.get(this.id), m;
8863
8864                         if (t.isDisabled() || t.items.length == 0)
8865                                 return;
8866
8867                         if (t.menu && t.menu.isMenuVisible)
8868                                 return t.hideMenu();
8869
8870                         if (!t.isMenuRendered) {
8871                                 t.renderMenu();
8872                                 t.isMenuRendered = true;
8873                         }
8874
8875                         p2 = DOM.getPos(e);
8876
8877                         m = t.menu;
8878                         m.settings.offset_x = p2.x;
8879                         m.settings.offset_y = p2.y;
8880                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
8881
8882                         // Select in menu
8883                         if (t.oldID)
8884                                 m.items[t.oldID].setSelected(0);
8885
8886                         each(t.items, function(o) {
8887                                 if (o.value === t.selectedValue) {
8888                                         m.items[o.id].setSelected(1);
8889                                         t.oldID = o.id;
8890                                 }
8891                         });
8892
8893                         m.showMenu(0, e.clientHeight);
8894
8895                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
8896                         DOM.addClass(t.id, t.classPrefix + 'Selected');
8897
8898                         //DOM.get(t.id + '_text').focus();
8899                 },
8900
8901                 hideMenu : function(e) {
8902                         var t = this;
8903
8904                         if (t.menu && t.menu.isMenuVisible) {
8905                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');
8906
8907                                 // Prevent double toogles by canceling the mouse click event to the button
8908                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
8909                                         return;
8910
8911                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
8912                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');
8913                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
8914                                         t.menu.hideMenu();
8915                                 }
8916                         }
8917                 },
8918
8919                 renderMenu : function() {
8920                         var t = this, m;
8921
8922                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
8923                                 menu_line : 1,
8924                                 'class' : t.classPrefix + 'Menu mceNoIcons',
8925                                 max_width : 150,
8926                                 max_height : 150
8927                         });
8928
8929                         m.onHideMenu.add(function() {
8930                                 t.hideMenu();
8931                                 t.focus();
8932                         });
8933
8934                         m.add({
8935                                 title : t.settings.title,
8936                                 'class' : 'mceMenuItemTitle',
8937                                 onclick : function() {
8938                                         if (t.settings.onselect('') !== false)
8939                                                 t.select(''); // Must be runned after
8940                                 }
8941                         });
8942
8943                         each(t.items, function(o) {
8944                                 // No value then treat it as a title
8945                                 if (o.value === undefined) {
8946                                         m.add({
8947                                                 title : o.title,
8948                                                 'class' : 'mceMenuItemTitle',
8949                                                 onclick : function() {
8950                                                         if (t.settings.onselect('') !== false)
8951                                                                 t.select(''); // Must be runned after
8952                                                 }
8953                                         });
8954                                 } else {
8955                                         o.id = DOM.uniqueId();
8956                                         o.onclick = function() {
8957                                                 if (t.settings.onselect(o.value) !== false)
8958                                                         t.select(o.value); // Must be runned after
8959                                         };
8960
8961                                         m.add(o);
8962                                 }
8963                         });
8964
8965                         t.onRenderMenu.dispatch(t, m);
8966                         t.menu = m;
8967                 },
8968
8969                 postRender : function() {
8970                         var t = this, cp = t.classPrefix;
8971
8972                         Event.add(t.id, 'click', t.showMenu, t);
8973                         Event.add(t.id, 'keydown', function(evt) {
8974                                 if (evt.keyCode == 32) { // Space
8975                                         t.showMenu(evt);
8976                                         Event.cancel(evt);
8977                                 }
8978                         });
8979                         Event.add(t.id, 'focus', function() {
8980                                 if (!t._focused) {
8981                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
8982                                                 if (e.keyCode == 40) {
8983                                                         t.showMenu();
8984                                                         Event.cancel(e);
8985                                                 }
8986                                         });
8987                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
8988                                                 var v;
8989                                                 if (e.keyCode == 13) {
8990                                                         // Fake select on enter
8991                                                         v = t.selectedValue;
8992                                                         t.selectedValue = null; // Needs to be null to fake change
8993                                                         Event.cancel(e);
8994                                                         t.settings.onselect(v);
8995                                                 }
8996                                         });
8997                                 }
8998
8999                                 t._focused = 1;
9000                         });
9001                         Event.add(t.id, 'blur', function() {
9002                                 Event.remove(t.id, 'keydown', t.keyDownHandler);
9003                                 Event.remove(t.id, 'keypress', t.keyPressHandler);
9004                                 t._focused = 0;
9005                         });
9006
9007                         // Old IE doesn't have hover on all elements
9008                         if (tinymce.isIE6 || !DOM.boxModel) {
9009                                 Event.add(t.id, 'mouseover', function() {
9010                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9011                                                 DOM.addClass(t.id, cp + 'Hover');
9012                                 });
9013
9014                                 Event.add(t.id, 'mouseout', function() {
9015                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9016                                                 DOM.removeClass(t.id, cp + 'Hover');
9017                                 });
9018                         }
9019
9020                         t.onPostRender.dispatch(t, DOM.get(t.id));
9021                 },
9022
9023                 destroy : function() {
9024                         this.parent();
9025
9026                         Event.clear(this.id + '_text');
9027                         Event.clear(this.id + '_open');
9028                 }
9029         });
9030 })(tinymce);
9031 (function(tinymce) {
9032         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9033
9034         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
9035                 NativeListBox : function(id, s) {
9036                         this.parent(id, s);
9037                         this.classPrefix = 'mceNativeListBox';
9038                 },
9039
9040                 setDisabled : function(s) {
9041                         DOM.get(this.id).disabled = s;
9042                         this.setAriaProperty('disabled', s);
9043                 },
9044
9045                 isDisabled : function() {
9046                         return DOM.get(this.id).disabled;
9047                 },
9048
9049                 select : function(va) {
9050                         var t = this, fv, f;
9051
9052                         if (va == undefined)
9053                                 return t.selectByIndex(-1);
9054
9055                         // Is string or number make function selector
9056                         if (va && va.call)
9057                                 f = va;
9058                         else {
9059                                 f = function(v) {
9060                                         return v == va;
9061                                 };
9062                         }
9063
9064                         // Do we need to do something?
9065                         if (va != t.selectedValue) {
9066                                 // Find item
9067                                 each(t.items, function(o, i) {
9068                                         if (f(o.value)) {
9069                                                 fv = 1;
9070                                                 t.selectByIndex(i);
9071                                                 return false;
9072                                         }
9073                                 });
9074
9075                                 if (!fv)
9076                                         t.selectByIndex(-1);
9077                         }
9078                 },
9079
9080                 selectByIndex : function(idx) {
9081                         DOM.get(this.id).selectedIndex = idx + 1;
9082                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;
9083                 },
9084
9085                 add : function(n, v, a) {
9086                         var o, t = this;
9087
9088                         a = a || {};
9089                         a.value = v;
9090
9091                         if (t.isRendered())
9092                                 DOM.add(DOM.get(this.id), 'option', a, n);
9093
9094                         o = {
9095                                 title : n,
9096                                 value : v,
9097                                 attribs : a
9098                         };
9099
9100                         t.items.push(o);
9101                         t.onAdd.dispatch(t, o);
9102                 },
9103
9104                 getLength : function() {
9105                         return this.items.length;
9106                 },
9107
9108                 renderHTML : function() {
9109                         var h, t = this;
9110
9111                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
9112
9113                         each(t.items, function(it) {
9114                                 h += DOM.createHTML('option', {value : it.value}, it.title);
9115                         });
9116
9117                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
9118                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
9119                         return h;
9120                 },
9121
9122                 postRender : function() {
9123                         var t = this, ch, changeListenerAdded = true;
9124
9125                         t.rendered = true;
9126
9127                         function onChange(e) {
9128                                 var v = t.items[e.target.selectedIndex - 1];
9129
9130                                 if (v && (v = v.value)) {
9131                                         t.onChange.dispatch(t, v);
9132
9133                                         if (t.settings.onselect)
9134                                                 t.settings.onselect(v);
9135                                 }
9136                         };
9137
9138                         Event.add(t.id, 'change', onChange);
9139
9140                         // Accessibility keyhandler
9141                         Event.add(t.id, 'keydown', function(e) {
9142                                 var bf;
9143
9144                                 Event.remove(t.id, 'change', ch);
9145                                 changeListenerAdded = false;
9146
9147                                 bf = Event.add(t.id, 'blur', function() {
9148                                         if (changeListenerAdded) return;
9149                                         changeListenerAdded = true;
9150                                         Event.add(t.id, 'change', onChange);
9151                                         Event.remove(t.id, 'blur', bf);
9152                                 });
9153
9154                                 if (e.keyCode == 13 || e.keyCode == 32) {
9155                                         onChange(e);
9156                                         return Event.cancel(e);
9157                                 }
9158                         });
9159
9160                         t.onPostRender.dispatch(t, DOM.get(t.id));
9161                 }
9162         });
9163 })(tinymce);
9164 (function(tinymce) {
9165         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9166
9167         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
9168                 MenuButton : function(id, s, ed) {
9169                         this.parent(id, s, ed);
9170
9171                         this.onRenderMenu = new tinymce.util.Dispatcher(this);
9172
9173                         s.menu_container = s.menu_container || DOM.doc.body;
9174                 },
9175
9176                 showMenu : function() {
9177                         var t = this, p1, p2, e = DOM.get(t.id), m;
9178
9179                         if (t.isDisabled())
9180                                 return;
9181
9182                         if (!t.isMenuRendered) {
9183                                 t.renderMenu();
9184                                 t.isMenuRendered = true;
9185                         }
9186
9187                         if (t.isMenuVisible)
9188                                 return t.hideMenu();
9189
9190                         p1 = DOM.getPos(t.settings.menu_container);
9191                         p2 = DOM.getPos(e);
9192
9193                         m = t.menu;
9194                         m.settings.offset_x = p2.x;
9195                         m.settings.offset_y = p2.y;
9196                         m.settings.vp_offset_x = p2.x;
9197                         m.settings.vp_offset_y = p2.y;
9198                         m.settings.keyboard_focus = t._focused;
9199                         m.showMenu(0, e.clientHeight);
9200
9201                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9202                         t.setState('Selected', 1);
9203
9204                         t.isMenuVisible = 1;
9205                 },
9206
9207                 renderMenu : function() {
9208                         var t = this, m;
9209
9210                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9211                                 menu_line : 1,
9212                                 'class' : this.classPrefix + 'Menu',
9213                                 icons : t.settings.icons
9214                         });
9215
9216                         m.onHideMenu.add(function() {
9217                                 t.hideMenu();
9218                                 t.focus();
9219                         });
9220
9221                         t.onRenderMenu.dispatch(t, m);
9222                         t.menu = m;
9223                 },
9224
9225                 hideMenu : function(e) {
9226                         var t = this;
9227
9228                         // Prevent double toogles by canceling the mouse click event to the button
9229                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
9230                                 return;
9231
9232                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9233                                 t.setState('Selected', 0);
9234                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9235                                 if (t.menu)
9236                                         t.menu.hideMenu();
9237                         }
9238
9239                         t.isMenuVisible = 0;
9240                 },
9241
9242                 postRender : function() {
9243                         var t = this, s = t.settings;
9244
9245                         Event.add(t.id, 'click', function() {
9246                                 if (!t.isDisabled()) {
9247                                         if (s.onclick)
9248                                                 s.onclick(t.value);
9249
9250                                         t.showMenu();
9251                                 }
9252                         });
9253                 }
9254         });
9255 })(tinymce);
9256
9257 (function(tinymce) {
9258         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9259
9260         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
9261                 SplitButton : function(id, s, ed) {
9262                         this.parent(id, s, ed);
9263                         this.classPrefix = 'mceSplitButton';
9264                 },
9265
9266                 renderHTML : function() {
9267                         var h, t = this, s = t.settings, h1;
9268
9269                         h = '<tbody><tr>';
9270
9271                         if (s.image)
9272                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
9273                         else
9274                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
9275
9276                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
9277                         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>';
9278         
9279                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
9280                         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>';
9281
9282                         h += '</tr></tbody>';
9283                         h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
9284                         return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
9285                 },
9286
9287                 postRender : function() {
9288                         var t = this, s = t.settings, activate;
9289
9290                         if (s.onclick) {
9291                                 activate = function(evt) {
9292                                         if (!t.isDisabled()) {
9293                                                 s.onclick(t.value);
9294                                                 Event.cancel(evt);
9295                                         }
9296                                 };
9297                                 Event.add(t.id + '_action', 'click', activate);
9298                                 Event.add(t.id, ['click', 'keydown'], function(evt) {
9299                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
9300                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
9301                                                 activate();
9302                                                 Event.cancel(evt);
9303                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
9304                                                 t.showMenu();
9305                                                 Event.cancel(evt);
9306                                         }
9307                                 });
9308                         }
9309
9310                         Event.add(t.id + '_open', 'click', function (evt) {
9311                                 t.showMenu();
9312                                 Event.cancel(evt);
9313                         });
9314                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
9315                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
9316
9317                         // Old IE doesn't have hover on all elements
9318                         if (tinymce.isIE6 || !DOM.boxModel) {
9319                                 Event.add(t.id, 'mouseover', function() {
9320                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9321                                                 DOM.addClass(t.id, 'mceSplitButtonHover');
9322                                 });
9323
9324                                 Event.add(t.id, 'mouseout', function() {
9325                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9326                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');
9327                                 });
9328                         }
9329                 },
9330
9331                 destroy : function() {
9332                         this.parent();
9333
9334                         Event.clear(this.id + '_action');
9335                         Event.clear(this.id + '_open');
9336                         Event.clear(this.id);
9337                 }
9338         });
9339 })(tinymce);
9340
9341 (function(tinymce) {
9342         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
9343
9344         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
9345                 ColorSplitButton : function(id, s, ed) {
9346                         var t = this;
9347
9348                         t.parent(id, s, ed);
9349
9350                         t.settings = s = tinymce.extend({
9351                                 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',
9352                                 grid_width : 8,
9353                                 default_color : '#888888'
9354                         }, t.settings);
9355
9356                         t.onShowMenu = new tinymce.util.Dispatcher(t);
9357
9358                         t.onHideMenu = new tinymce.util.Dispatcher(t);
9359
9360                         t.value = s.default_color;
9361                 },
9362
9363                 showMenu : function() {
9364                         var t = this, r, p, e, p2;
9365
9366                         if (t.isDisabled())
9367                                 return;
9368
9369                         if (!t.isMenuRendered) {
9370                                 t.renderMenu();
9371                                 t.isMenuRendered = true;
9372                         }
9373
9374                         if (t.isMenuVisible)
9375                                 return t.hideMenu();
9376
9377                         e = DOM.get(t.id);
9378                         DOM.show(t.id + '_menu');
9379                         DOM.addClass(e, 'mceSplitButtonSelected');
9380                         p2 = DOM.getPos(e);
9381                         DOM.setStyles(t.id + '_menu', {
9382                                 left : p2.x,
9383                                 top : p2.y + e.clientHeight,
9384                                 zIndex : 200000
9385                         });
9386                         e = 0;
9387
9388                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9389                         t.onShowMenu.dispatch(t);
9390
9391                         if (t._focused) {
9392                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
9393                                         if (e.keyCode == 27)
9394                                                 t.hideMenu();
9395                                 });
9396
9397                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
9398                         }
9399
9400                         t.isMenuVisible = 1;
9401                 },
9402
9403                 hideMenu : function(e) {
9404                         var t = this;
9405
9406                         if (t.isMenuVisible) {
9407                                 // Prevent double toogles by canceling the mouse click event to the button
9408                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
9409                                         return;
9410
9411                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
9412                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');
9413                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9414                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
9415                                         DOM.hide(t.id + '_menu');
9416                                 }
9417
9418                                 t.isMenuVisible = 0;
9419                         }
9420                 },
9421
9422                 renderMenu : function() {
9423                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
9424
9425                         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;'});
9426                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
9427                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});
9428
9429                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
9430                         tb = DOM.add(n, 'tbody');
9431
9432                         // Generate color grid
9433                         i = 0;
9434                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
9435                                 c = c.replace(/^#/, '');
9436
9437                                 if (!i--) {
9438                                         tr = DOM.add(tb, 'tr');
9439                                         i = s.grid_width - 1;
9440                                 }
9441
9442                                 n = DOM.add(tr, 'td');
9443                                 n = DOM.add(n, 'a', {
9444                                         role : 'option',
9445                                         href : 'javascript:;',
9446                                         style : {
9447                                                 backgroundColor : '#' + c
9448                                         },
9449                                         'title': t.editor.getLang('colors.' + c, c),
9450                                         'data-mce-color' : '#' + c
9451                                 });
9452
9453                                 if (t.editor.forcedHighContrastMode) {
9454                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
9455                                         if (n.getContext && (context = n.getContext("2d"))) {
9456                                                 context.fillStyle = '#' + c;
9457                                                 context.fillRect(0, 0, 16, 16);
9458                                         } else {
9459                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.
9460                                                 DOM.remove(n);
9461                                         }
9462                                 }
9463                         });
9464
9465                         if (s.more_colors_func) {
9466                                 n = DOM.add(tb, 'tr');
9467                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
9468                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
9469
9470                                 Event.add(n, 'click', function(e) {
9471                                         s.more_colors_func.call(s.more_colors_scope || this);
9472                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
9473                                 });
9474                         }
9475
9476                         DOM.addClass(m, 'mceColorSplitMenu');
9477                         
9478                         new tinymce.ui.KeyboardNavigation({
9479                                 root: t.id + '_menu',
9480                                 items: DOM.select('a', t.id + '_menu'),
9481                                 onCancel: function() {
9482                                         t.hideMenu();
9483                                         t.focus();
9484                                 }
9485                         });
9486
9487                         // Prevent IE from scrolling and hindering click to occur #4019
9488                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
9489
9490                         Event.add(t.id + '_menu', 'click', function(e) {
9491                                 var c;
9492
9493                                 e = DOM.getParent(e.target, 'a', tb);
9494
9495                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
9496                                         t.setColor(c);
9497
9498                                 return Event.cancel(e); // Prevent IE auto save warning
9499                         });
9500
9501                         return w;
9502                 },
9503
9504                 setColor : function(c) {
9505                         this.displayColor(c);
9506                         this.hideMenu();
9507                         this.settings.onselect(c);
9508                 },
9509                 
9510                 displayColor : function(c) {
9511                         var t = this;
9512
9513                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
9514
9515                         t.value = c;
9516                 },
9517
9518                 postRender : function() {
9519                         var t = this, id = t.id;
9520
9521                         t.parent();
9522                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
9523                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
9524                 },
9525
9526                 destroy : function() {
9527                         this.parent();
9528
9529                         Event.clear(this.id + '_menu');
9530                         Event.clear(this.id + '_more');
9531                         DOM.remove(this.id + '_menu');
9532                 }
9533         });
9534 })(tinymce);
9535
9536 (function(tinymce) {
9537 // Shorten class names
9538 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
9539 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
9540         renderHTML : function() {
9541                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
9542
9543                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
9544                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
9545                 h.push("<span role='application'>");
9546                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
9547                 each(controls, function(toolbar) {
9548                         h.push(toolbar.renderHTML());
9549                 });
9550                 h.push("</span>");
9551                 h.push('</div>');
9552
9553                 return h.join('');
9554         },
9555         
9556         focus : function() {
9557                 this.keyNav.focus();
9558         },
9559         
9560         postRender : function() {
9561                 var t = this, items = [];
9562
9563                 each(t.controls, function(toolbar) {
9564                         each (toolbar.controls, function(control) {
9565                                 if (control.id) {
9566                                         items.push(control);
9567                                 }
9568                         });
9569                 });
9570
9571                 t.keyNav = new tinymce.ui.KeyboardNavigation({
9572                         root: t.id,
9573                         items: items,
9574                         onCancel: function() {
9575                                 t.editor.focus();
9576                         },
9577                         excludeFromTabOrder: !t.settings.tab_focus_toolbar
9578                 });
9579         },
9580         
9581         destroy : function() {
9582                 var self = this;
9583
9584                 self.parent();
9585                 self.keyNav.destroy();
9586                 Event.clear(self.id);
9587         }
9588 });
9589 })(tinymce);
9590
9591 (function(tinymce) {
9592 // Shorten class names
9593 var dom = tinymce.DOM, each = tinymce.each;
9594 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
9595         renderHTML : function() {
9596                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
9597
9598                 cl = t.controls;
9599                 for (i=0; i<cl.length; i++) {
9600                         // Get current control, prev control, next control and if the control is a list box or not
9601                         co = cl[i];
9602                         pr = cl[i - 1];
9603                         nx = cl[i + 1];
9604
9605                         // Add toolbar start
9606                         if (i === 0) {
9607                                 c = 'mceToolbarStart';
9608
9609                                 if (co.Button)
9610                                         c += ' mceToolbarStartButton';
9611                                 else if (co.SplitButton)
9612                                         c += ' mceToolbarStartSplitButton';
9613                                 else if (co.ListBox)
9614                                         c += ' mceToolbarStartListBox';
9615
9616                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
9617                         }
9618
9619                         // Add toolbar end before list box and after the previous button
9620                         // This is to fix the o2k7 editor skins
9621                         if (pr && co.ListBox) {
9622                                 if (pr.Button || pr.SplitButton)
9623                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
9624                         }
9625
9626                         // Render control HTML
9627
9628                         // IE 8 quick fix, needed to propertly generate a hit area for anchors
9629                         if (dom.stdMode)
9630                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
9631                         else
9632                                 h += '<td>' + co.renderHTML() + '</td>';
9633
9634                         // Add toolbar start after list box and before the next button
9635                         // This is to fix the o2k7 editor skins
9636                         if (nx && co.ListBox) {
9637                                 if (nx.Button || nx.SplitButton)
9638                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
9639                         }
9640                 }
9641
9642                 c = 'mceToolbarEnd';
9643
9644                 if (co.Button)
9645                         c += ' mceToolbarEndButton';
9646                 else if (co.SplitButton)
9647                         c += ' mceToolbarEndSplitButton';
9648                 else if (co.ListBox)
9649                         c += ' mceToolbarEndListBox';
9650
9651                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
9652
9653                 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>');
9654         }
9655 });
9656 })(tinymce);
9657
9658 (function(tinymce) {
9659         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
9660
9661         tinymce.create('tinymce.AddOnManager', {
9662                 AddOnManager : function() {
9663                         var self = this;
9664
9665                         self.items = [];
9666                         self.urls = {};
9667                         self.lookup = {};
9668                         self.onAdd = new Dispatcher(self);
9669                 },
9670
9671                 get : function(n) {
9672                         if (this.lookup[n]) {
9673                                 return this.lookup[n].instance;
9674                         } else {
9675                                 return undefined;
9676                         }
9677                 },
9678
9679                 dependencies : function(n) {
9680                         var result;
9681                         if (this.lookup[n]) {
9682                                 result = this.lookup[n].dependencies;
9683                         }
9684                         return result || [];
9685                 },
9686
9687                 requireLangPack : function(n) {
9688                         var s = tinymce.settings;
9689
9690                         if (s && s.language && s.language_load !== false)
9691                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
9692                 },
9693
9694                 add : function(id, o, dependencies) {
9695                         this.items.push(o);
9696                         this.lookup[id] = {instance:o, dependencies:dependencies};
9697                         this.onAdd.dispatch(this, id, o);
9698
9699                         return o;
9700                 },
9701                 createUrl: function(baseUrl, dep) {
9702                         if (typeof dep === "object") {
9703                                 return dep
9704                         } else {
9705                                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
9706                         }
9707                 },
9708
9709                 addComponents: function(pluginName, scripts) {
9710                         var pluginUrl = this.urls[pluginName];
9711                         tinymce.each(scripts, function(script){
9712                                 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 
9713                         });
9714                 },
9715
9716                 load : function(n, u, cb, s) {
9717                         var t = this, url = u;
9718
9719                         function loadDependencies() {
9720                                 var dependencies = t.dependencies(n);
9721                                 tinymce.each(dependencies, function(dep) {
9722                                         var newUrl = t.createUrl(u, dep);
9723                                         t.load(newUrl.resource, newUrl, undefined, undefined);
9724                                 });
9725                                 if (cb) {
9726                                         if (s) {
9727                                                 cb.call(s);
9728                                         } else {
9729                                                 cb.call(tinymce.ScriptLoader);
9730                                         }
9731                                 }
9732                         }
9733
9734                         if (t.urls[n])
9735                                 return;
9736                         if (typeof u === "object")
9737                                 url = u.prefix + u.resource + u.suffix;
9738
9739                         if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
9740                                 url = tinymce.baseURL + '/' + url;
9741
9742                         t.urls[n] = url.substring(0, url.lastIndexOf('/'));
9743
9744                         if (t.lookup[n]) {
9745                                 loadDependencies();
9746                         } else {
9747                                 tinymce.ScriptLoader.add(url, loadDependencies, s);
9748                         }
9749                 }
9750         });
9751
9752         // Create plugin and theme managers
9753         tinymce.PluginManager = new tinymce.AddOnManager();
9754         tinymce.ThemeManager = new tinymce.AddOnManager();
9755 }(tinymce));
9756
9757 (function(tinymce) {
9758         // Shorten names
9759         var each = tinymce.each, extend = tinymce.extend,
9760                 DOM = tinymce.DOM, Event = tinymce.dom.Event,
9761                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
9762                 explode = tinymce.explode,
9763                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
9764
9765         // Setup some URLs where the editor API is located and where the document is
9766         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
9767         if (!/[\/\\]$/.test(tinymce.documentBaseURL))
9768                 tinymce.documentBaseURL += '/';
9769
9770         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
9771
9772         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
9773
9774         // Add before unload listener
9775         // This was required since IE was leaking memory if you added and removed beforeunload listeners
9776         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
9777         tinymce.onBeforeUnload = new Dispatcher(tinymce);
9778
9779         // Must be on window or IE will leak if the editor is placed in frame or iframe
9780         Event.add(window, 'beforeunload', function(e) {
9781                 tinymce.onBeforeUnload.dispatch(tinymce, e);
9782         });
9783
9784         tinymce.onAddEditor = new Dispatcher(tinymce);
9785
9786         tinymce.onRemoveEditor = new Dispatcher(tinymce);
9787
9788         tinymce.EditorManager = extend(tinymce, {
9789                 editors : [],
9790
9791                 i18n : {},
9792
9793                 activeEditor : null,
9794
9795                 init : function(s) {
9796                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
9797
9798                         function execCallback(se, n, s) {
9799                                 var f = se[n];
9800
9801                                 if (!f)
9802                                         return;
9803
9804                                 if (tinymce.is(f, 'string')) {
9805                                         s = f.replace(/\.\w+$/, '');
9806                                         s = s ? tinymce.resolve(s) : 0;
9807                                         f = tinymce.resolve(f);
9808                                 }
9809
9810                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
9811                         };
9812
9813                         s = extend({
9814                                 theme : "simple",
9815                                 language : "en"
9816                         }, s);
9817
9818                         t.settings = s;
9819
9820                         // Legacy call
9821                         Event.add(document, 'init', function() {
9822                                 var l, co;
9823
9824                                 execCallback(s, 'onpageload');
9825
9826                                 switch (s.mode) {
9827                                         case "exact":
9828                                                 l = s.elements || '';
9829
9830                                                 if(l.length > 0) {
9831                                                         each(explode(l), function(v) {
9832                                                                 if (DOM.get(v)) {
9833                                                                         ed = new tinymce.Editor(v, s);
9834                                                                         el.push(ed);
9835                                                                         ed.render(1);
9836                                                                 } else {
9837                                                                         each(document.forms, function(f) {
9838                                                                                 each(f.elements, function(e) {
9839                                                                                         if (e.name === v) {
9840                                                                                                 v = 'mce_editor_' + instanceCounter++;
9841                                                                                                 DOM.setAttrib(e, 'id', v);
9842
9843                                                                                                 ed = new tinymce.Editor(v, s);
9844                                                                                                 el.push(ed);
9845                                                                                                 ed.render(1);
9846                                                                                         }
9847                                                                                 });
9848                                                                         });
9849                                                                 }
9850                                                         });
9851                                                 }
9852                                                 break;
9853
9854                                         case "textareas":
9855                                         case "specific_textareas":
9856                                                 function hasClass(n, c) {
9857                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
9858                                                 };
9859
9860                                                 each(DOM.select('textarea'), function(v) {
9861                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))
9862                                                                 return;
9863
9864                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {
9865                                                                 // Can we use the name
9866                                                                 e = DOM.get(v.name);
9867                                                                 if (!v.id && !e)
9868                                                                         v.id = v.name;
9869
9870                                                                 // Generate unique name if missing or already exists
9871                                                                 if (!v.id || t.get(v.id))
9872                                                                         v.id = DOM.uniqueId();
9873
9874                                                                 ed = new tinymce.Editor(v.id, s);
9875                                                                 el.push(ed);
9876                                                                 ed.render(1);
9877                                                         }
9878                                                 });
9879                                                 break;
9880                                 }
9881
9882                                 // Call onInit when all editors are initialized
9883                                 if (s.oninit) {
9884                                         l = co = 0;
9885
9886                                         each(el, function(ed) {
9887                                                 co++;
9888
9889                                                 if (!ed.initialized) {
9890                                                         // Wait for it
9891                                                         ed.onInit.add(function() {
9892                                                                 l++;
9893
9894                                                                 // All done
9895                                                                 if (l == co)
9896                                                                         execCallback(s, 'oninit');
9897                                                         });
9898                                                 } else
9899                                                         l++;
9900
9901                                                 // All done
9902                                                 if (l == co)
9903                                                         execCallback(s, 'oninit');                                      
9904                                         });
9905                                 }
9906                         });
9907                 },
9908
9909                 get : function(id) {
9910                         if (id === undefined)
9911                                 return this.editors;
9912
9913                         return this.editors[id];
9914                 },
9915
9916                 getInstanceById : function(id) {
9917                         return this.get(id);
9918                 },
9919
9920                 add : function(editor) {
9921                         var self = this, editors = self.editors;
9922
9923                         // Add named and index editor instance
9924                         editors[editor.id] = editor;
9925                         editors.push(editor);
9926
9927                         self._setActive(editor);
9928                         self.onAddEditor.dispatch(self, editor);
9929
9930
9931                         // Patch the tinymce.Editor instance with jQuery adapter logic
9932                         if (tinymce.adapter)
9933                                 tinymce.adapter.patchEditor(editor);
9934
9935
9936                         return editor;
9937                 },
9938
9939                 remove : function(editor) {
9940                         var t = this, i, editors = t.editors;
9941
9942                         // Not in the collection
9943                         if (!editors[editor.id])
9944                                 return null;
9945
9946                         delete editors[editor.id];
9947
9948                         for (i = 0; i < editors.length; i++) {
9949                                 if (editors[i] == editor) {
9950                                         editors.splice(i, 1);
9951                                         break;
9952                                 }
9953                         }
9954
9955                         // Select another editor since the active one was removed
9956                         if (t.activeEditor == editor)
9957                                 t._setActive(editors[0]);
9958
9959                         editor.destroy();
9960                         t.onRemoveEditor.dispatch(t, editor);
9961
9962                         return editor;
9963                 },
9964
9965                 execCommand : function(c, u, v) {
9966                         var t = this, ed = t.get(v), w;
9967
9968                         // Manager commands
9969                         switch (c) {
9970                                 case "mceFocus":
9971                                         ed.focus();
9972                                         return true;
9973
9974                                 case "mceAddEditor":
9975                                 case "mceAddControl":
9976                                         if (!t.get(v))
9977                                                 new tinymce.Editor(v, t.settings).render();
9978
9979                                         return true;
9980
9981                                 case "mceAddFrameControl":
9982                                         w = v.window;
9983
9984                                         // Add tinyMCE global instance and tinymce namespace to specified window
9985                                         w.tinyMCE = tinyMCE;
9986                                         w.tinymce = tinymce;
9987
9988                                         tinymce.DOM.doc = w.document;
9989                                         tinymce.DOM.win = w;
9990
9991                                         ed = new tinymce.Editor(v.element_id, v);
9992                                         ed.render();
9993
9994                                         // Fix IE memory leaks
9995                                         if (tinymce.isIE) {
9996                                                 function clr() {
9997                                                         ed.destroy();
9998                                                         w.detachEvent('onunload', clr);
9999                                                         w = w.tinyMCE = w.tinymce = null; // IE leak
10000                                                 };
10001
10002                                                 w.attachEvent('onunload', clr);
10003                                         }
10004
10005                                         v.page_window = null;
10006
10007                                         return true;
10008
10009                                 case "mceRemoveEditor":
10010                                 case "mceRemoveControl":
10011                                         if (ed)
10012                                                 ed.remove();
10013
10014                                         return true;
10015
10016                                 case 'mceToggleEditor':
10017                                         if (!ed) {
10018                                                 t.execCommand('mceAddControl', 0, v);
10019                                                 return true;
10020                                         }
10021
10022                                         if (ed.isHidden())
10023                                                 ed.show();
10024                                         else
10025                                                 ed.hide();
10026
10027                                         return true;
10028                         }
10029
10030                         // Run command on active editor
10031                         if (t.activeEditor)
10032                                 return t.activeEditor.execCommand(c, u, v);
10033
10034                         return false;
10035                 },
10036
10037                 execInstanceCommand : function(id, c, u, v) {
10038                         var ed = this.get(id);
10039
10040                         if (ed)
10041                                 return ed.execCommand(c, u, v);
10042
10043                         return false;
10044                 },
10045
10046                 triggerSave : function() {
10047                         each(this.editors, function(e) {
10048                                 e.save();
10049                         });
10050                 },
10051
10052                 addI18n : function(p, o) {
10053                         var lo, i18n = this.i18n;
10054
10055                         if (!tinymce.is(p, 'string')) {
10056                                 each(p, function(o, lc) {
10057                                         each(o, function(o, g) {
10058                                                 each(o, function(o, k) {
10059                                                         if (g === 'common')
10060                                                                 i18n[lc + '.' + k] = o;
10061                                                         else
10062                                                                 i18n[lc + '.' + g + '.' + k] = o;
10063                                                 });
10064                                         });
10065                                 });
10066                         } else {
10067                                 each(o, function(o, k) {
10068                                         i18n[p + '.' + k] = o;
10069                                 });
10070                         }
10071                 },
10072
10073                 // Private methods
10074
10075                 _setActive : function(editor) {
10076                         this.selectedInstance = this.activeEditor = editor;
10077                 }
10078         });
10079 })(tinymce);
10080
10081 (function(tinymce) {
10082         // Shorten these names
10083         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
10084                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10085                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10086                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10087                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
10088
10089         tinymce.create('tinymce.Editor', {
10090                 Editor : function(id, s) {
10091                         var t = this;
10092
10093                         t.id = t.editorId = id;
10094
10095                         t.execCommands = {};
10096                         t.queryStateCommands = {};
10097                         t.queryValueCommands = {};
10098
10099                         t.isNotDirty = false;
10100
10101                         t.plugins = {};
10102
10103                         // Add events to the editor
10104                         each([
10105                                 'onPreInit',
10106
10107                                 'onBeforeRenderUI',
10108
10109                                 'onPostRender',
10110
10111                                 'onInit',
10112
10113                                 'onRemove',
10114
10115                                 'onActivate',
10116
10117                                 'onDeactivate',
10118
10119                                 'onClick',
10120
10121                                 'onEvent',
10122
10123                                 'onMouseUp',
10124
10125                                 'onMouseDown',
10126
10127                                 'onDblClick',
10128
10129                                 'onKeyDown',
10130
10131                                 'onKeyUp',
10132
10133                                 'onKeyPress',
10134
10135                                 'onContextMenu',
10136
10137                                 'onSubmit',
10138
10139                                 'onReset',
10140
10141                                 'onPaste',
10142
10143                                 'onPreProcess',
10144
10145                                 'onPostProcess',
10146
10147                                 'onBeforeSetContent',
10148
10149                                 'onBeforeGetContent',
10150
10151                                 'onSetContent',
10152
10153                                 'onGetContent',
10154
10155                                 'onLoadContent',
10156
10157                                 'onSaveContent',
10158
10159                                 'onNodeChange',
10160
10161                                 'onChange',
10162
10163                                 'onBeforeExecCommand',
10164
10165                                 'onExecCommand',
10166
10167                                 'onUndo',
10168
10169                                 'onRedo',
10170
10171                                 'onVisualAid',
10172
10173                                 'onSetProgressState'
10174                         ], function(e) {
10175                                 t[e] = new Dispatcher(t);
10176                         });
10177
10178                         t.settings = s = extend({
10179                                 id : id,
10180                                 language : 'en',
10181                                 docs_language : 'en',
10182                                 theme : 'simple',
10183                                 skin : 'default',
10184                                 delta_width : 0,
10185                                 delta_height : 0,
10186                                 popup_css : '',
10187                                 plugins : '',
10188                                 document_base_url : tinymce.documentBaseURL,
10189                                 add_form_submit_trigger : 1,
10190                                 submit_patch : 1,
10191                                 add_unload_trigger : 1,
10192                                 convert_urls : 1,
10193                                 relative_urls : 1,
10194                                 remove_script_host : 1,
10195                                 table_inline_editing : 0,
10196                                 object_resizing : 1,
10197                                 cleanup : 1,
10198                                 accessibility_focus : 1,
10199                                 custom_shortcuts : 1,
10200                                 custom_undo_redo_keyboard_shortcuts : 1,
10201                                 custom_undo_redo_restore_selection : 1,
10202                                 custom_undo_redo : 1,
10203                                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
10204                                 visual_table_class : 'mceItemTable',
10205                                 visual : 1,
10206                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
10207                                 apply_source_formatting : 1,
10208                                 directionality : 'ltr',
10209                                 forced_root_block : 'p',
10210                                 hidden_input : 1,
10211                                 padd_empty_editor : 1,
10212                                 render_ui : 1,
10213                                 init_theme : 1,
10214                                 force_p_newlines : 1,
10215                                 indentation : '30px',
10216                                 keep_styles : 1,
10217                                 fix_table_elements : 1,
10218                                 inline_styles : 1,
10219                                 convert_fonts_to_spans : true,
10220                                 indent : 'simple',
10221                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10222                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10223                                 validate : true,
10224                                 entity_encoding : 'named',
10225                                 url_converter : t.convertURL,
10226                                 url_converter_scope : t,
10227                                 ie7_compat : true
10228                         }, s);
10229
10230                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
10231                                 base_uri : tinyMCE.baseURI
10232                         });
10233
10234                         t.baseURI = tinymce.baseURI;
10235
10236                         t.contentCSS = [];
10237
10238                         // Call setup
10239                         t.execCallback('setup', t);
10240                 },
10241
10242                 render : function(nst) {
10243                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
10244
10245                         // Page is not loaded yet, wait for it
10246                         if (!Event.domLoaded) {
10247                                 Event.add(document, 'init', function() {
10248                                         t.render();
10249                                 });
10250                                 return;
10251                         }
10252
10253                         tinyMCE.settings = s;
10254
10255                         // Element not found, then skip initialization
10256                         if (!t.getElement())
10257                                 return;
10258
10259                         // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
10260                         // here since the browser says it has contentEditable support but there is no visible
10261                         // caret We will remove this check ones Apple implements full contentEditable support
10262                         if (tinymce.isIDevice && !tinymce.isIOS5)
10263                                 return;
10264
10265                         // Add hidden input for non input elements inside form elements
10266                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
10267                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
10268
10269                         if (tinymce.WindowManager)
10270                                 t.windowManager = new tinymce.WindowManager(t);
10271
10272                         if (s.encoding == 'xml') {
10273                                 t.onGetContent.add(function(ed, o) {
10274                                         if (o.save)
10275                                                 o.content = DOM.encode(o.content);
10276                                 });
10277                         }
10278
10279                         if (s.add_form_submit_trigger) {
10280                                 t.onSubmit.addToTop(function() {
10281                                         if (t.initialized) {
10282                                                 t.save();
10283                                                 t.isNotDirty = 1;
10284                                         }
10285                                 });
10286                         }
10287
10288                         if (s.add_unload_trigger) {
10289                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
10290                                         if (t.initialized && !t.destroyed && !t.isHidden())
10291                                                 t.save({format : 'raw', no_events : true});
10292                                 });
10293                         }
10294
10295                         tinymce.addUnload(t.destroy, t);
10296
10297                         if (s.submit_patch) {
10298                                 t.onBeforeRenderUI.add(function() {
10299                                         var n = t.getElement().form;
10300
10301                                         if (!n)
10302                                                 return;
10303
10304                                         // Already patched
10305                                         if (n._mceOldSubmit)
10306                                                 return;
10307
10308                                         // Check page uses id="submit" or name="submit" for it's submit button
10309                                         if (!n.submit.nodeType && !n.submit.length) {
10310                                                 t.formElement = n;
10311                                                 n._mceOldSubmit = n.submit;
10312                                                 n.submit = function() {
10313                                                         // Save all instances
10314                                                         tinymce.triggerSave();
10315                                                         t.isNotDirty = 1;
10316
10317                                                         return t.formElement._mceOldSubmit(t.formElement);
10318                                                 };
10319                                         }
10320
10321                                         n = null;
10322                                 });
10323                         }
10324
10325                         // Load scripts
10326                         function loadScripts() {
10327                                 if (s.language && s.language_load !== false)
10328                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
10329
10330                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
10331                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
10332
10333                                 each(explode(s.plugins), function(p) {
10334                                         if (p &&!PluginManager.urls[p]) {
10335                                                 if (p.charAt(0) == '-') {
10336                                                         p = p.substr(1, p.length);
10337                                                         var dependencies = PluginManager.dependencies(p);
10338                                                         each(dependencies, function(dep) {
10339                                                                 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
10340                                                                 var dep = PluginManager.createUrl(defaultSettings, dep);
10341                                                                 PluginManager.load(dep.resource, dep);
10342                                                                 
10343                                                         });
10344                                                 } else {
10345                                                         // Skip safari plugin, since it is removed as of 3.3b1
10346                                                         if (p == 'safari') {
10347                                                                 return;
10348                                                         }
10349                                                         PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
10350                                                 }
10351                                         }
10352                                 });
10353
10354                                 // Init when que is loaded
10355                                 sl.loadQueue(function() {
10356                                         if (!t.removed)
10357                                                 t.init();
10358                                 });
10359                         };
10360
10361                         loadScripts();
10362                 },
10363
10364                 init : function() {
10365                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
10366
10367                         tinymce.add(t);
10368
10369                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
10370
10371                         if (s.theme) {
10372                                 s.theme = s.theme.replace(/-/, '');
10373                                 o = ThemeManager.get(s.theme);
10374                                 t.theme = new o();
10375
10376                                 if (t.theme.init && s.init_theme)
10377                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
10378                         }
10379                         function initPlugin(p) {
10380                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
10381                                 if (c && tinymce.inArray(initializedPlugins,p) === -1) {
10382                                         each(PluginManager.dependencies(p), function(dep){
10383                                                 initPlugin(dep);
10384                                         });
10385                                         po = new c(t, u);
10386
10387                                         t.plugins[p] = po;
10388
10389                                         if (po.init) {
10390                                                 po.init(t, u);
10391                                                 initializedPlugins.push(p);
10392                                         }
10393                                 }
10394                         }
10395                         
10396                         // Create all plugins
10397                         each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
10398
10399                         // Setup popup CSS path(s)
10400                         if (s.popup_css !== false) {
10401                                 if (s.popup_css)
10402                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
10403                                 else
10404                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
10405                         }
10406
10407                         if (s.popup_css_add)
10408                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
10409
10410                         t.controlManager = new tinymce.ControlManager(t);
10411
10412                         if (s.custom_undo_redo) {
10413                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
10414                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10415                                                 t.undoManager.beforeChange();
10416                                 });
10417
10418                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
10419                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10420                                                 t.undoManager.add();
10421                                 });
10422                         }
10423
10424                         t.onExecCommand.add(function(ed, c) {
10425                                 // Don't refresh the select lists until caret move
10426                                 if (!/^(FontName|FontSize)$/.test(c))
10427                                         t.nodeChanged();
10428                         });
10429
10430                         // Remove ghost selections on images and tables in Gecko
10431                         if (isGecko) {
10432                                 function repaint(a, o) {
10433                                         if (!o || !o.initial)
10434                                                 t.execCommand('mceRepaint');
10435                                 };
10436
10437                                 t.onUndo.add(repaint);
10438                                 t.onRedo.add(repaint);
10439                                 t.onSetContent.add(repaint);
10440                         }
10441
10442                         // Enables users to override the control factory
10443                         t.onBeforeRenderUI.dispatch(t, t.controlManager);
10444
10445                         // Measure box
10446                         if (s.render_ui) {
10447                                 w = s.width || e.style.width || e.offsetWidth;
10448                                 h = s.height || e.style.height || e.offsetHeight;
10449                                 t.orgDisplay = e.style.display;
10450                                 re = /^[0-9\.]+(|px)$/i;
10451
10452                                 if (re.test('' + w))
10453                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
10454
10455                                 if (re.test('' + h))
10456                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
10457
10458                                 // Render UI
10459                                 o = t.theme.renderUI({
10460                                         targetNode : e,
10461                                         width : w,
10462                                         height : h,
10463                                         deltaWidth : s.delta_width,
10464                                         deltaHeight : s.delta_height
10465                                 });
10466
10467                                 t.editorContainer = o.editorContainer;
10468                         }
10469
10470
10471                         // User specified a document.domain value
10472                         if (document.domain && location.hostname != document.domain)
10473                                 tinymce.relaxedDomain = document.domain;
10474
10475                         // Resize editor
10476                         DOM.setStyles(o.sizeContainer || o.editorContainer, {
10477                                 width : w,
10478                                 height : h
10479                         });
10480
10481                         // Load specified content CSS last
10482                         if (s.content_css) {
10483                                 tinymce.each(explode(s.content_css), function(u) {
10484                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
10485                                 });
10486                         }
10487
10488                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
10489                         if (h < 100)
10490                                 h = 100;
10491
10492                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
10493
10494                         // We only need to override paths if we have to
10495                         // IE has a bug where it remove site absolute urls to relative ones if this is specified
10496                         if (s.document_base_url != tinymce.documentBaseURL)
10497                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
10498
10499                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
10500                         if (s.ie7_compat)
10501                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
10502                         else
10503                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
10504
10505                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
10506
10507                         // Firefox 2 doesn't load stylesheets correctly this way
10508                         if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
10509                                 for (i = 0; i < t.contentCSS.length; i++)
10510                                         t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
10511
10512                                 t.contentCSS = [];
10513                         }
10514
10515                         bi = s.body_id || 'tinymce';
10516                         if (bi.indexOf('=') != -1) {
10517                                 bi = t.getParam('body_id', '', 'hash');
10518                                 bi = bi[t.id] || bi;
10519                         }
10520
10521                         bc = s.body_class || '';
10522                         if (bc.indexOf('=') != -1) {
10523                                 bc = t.getParam('body_class', '', 'hash');
10524                                 bc = bc[t.id] || '';
10525                         }
10526
10527                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
10528
10529                         // Domain relaxing enabled, then set document domain
10530                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
10531                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
10532                                 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();})()';                         
10533                         }
10534
10535                         // Create iframe
10536                         // TODO: ACC add the appropriate description on this.
10537                         n = DOM.add(o.iframeContainer, 'iframe', { 
10538                                 id : t.id + "_ifr",
10539                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
10540                                 frameBorder : '0',
10541                                 allowTransparency : "true",
10542                                 title : s.aria_label,
10543                                 style : {
10544                                         width : '100%',
10545                                         height : h
10546                                 }
10547                         });
10548
10549                         t.contentAreaContainer = o.iframeContainer;
10550                         DOM.get(o.editorContainer).style.display = t.orgDisplay;
10551                         DOM.get(t.id).style.display = 'none';
10552                         DOM.setAttrib(t.id, 'aria-hidden', true);
10553
10554                         if (!tinymce.relaxedDomain || !u)
10555                                 t.setupIframe();
10556
10557                         e = n = o = null; // Cleanup
10558                 },
10559
10560                 setupIframe : function(filled) {
10561                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
10562
10563                         // Setup iframe body
10564                         if ((!isIE || !tinymce.relaxedDomain) && !filled) {
10565                                 // We need to wait for the load event on Gecko
10566                                 if (isGecko && !s.readonly) {
10567                                         t.getWin().addEventListener("DOMContentLoaded", function() {
10568                                                 window.setTimeout(function() {
10569                                                         var b = t.getBody(), undef;
10570
10571                                                         // Editable element needs to have some contents or backspace/delete won't work properly for some odd reason on FF 3.6 or older
10572                                                         b.innerHTML = '<br>';
10573
10574                                                         // Check if Gecko supports contentEditable mode FF2 doesn't
10575                                                         if (b.contentEditable !== undef) {
10576                                                                 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
10577                                                                 b.contentEditable = false;
10578                                                                 b.contentEditable = true;
10579
10580                                                                 // Caret doesn't get rendered when you mousedown on the HTML element on FF 3.x
10581                                                                 t.onMouseDown.add(function(ed, e) {
10582                                                                         if (e.target.nodeName === "HTML") {
10583                                                                                 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
10584                                                                                 b.contentEditable = false;
10585                                                                                 b.contentEditable = true;
10586
10587                                                                                 d.designMode = 'on'; // Render the caret
10588
10589                                                                                 // Remove design mode again after a while so it has some time to execute
10590                                                                                 window.setTimeout(function() {
10591                                                                                         d.designMode = 'off';
10592                                                                                         t.getBody().focus();
10593                                                                                 }, 1);
10594                                                                         }
10595                                                                 });
10596                                                         } else
10597                                                                 d.designMode = 'on';
10598
10599                                                         // Call setup frame once the contentEditable/designMode has been initialized
10600                                                         // since the caret won't be rendered some times otherwise.
10601                                                         t.setupIframe(true);
10602                                                 }, 1);
10603                                         }, false);
10604                                 }
10605
10606                                 d.open();
10607                                 d.write(t.iframeHTML);
10608                                 d.close();
10609
10610                                 if (tinymce.relaxedDomain)
10611                                         d.domain = tinymce.relaxedDomain;
10612
10613                                 // Wait for iframe onload event on Gecko
10614                                 if (isGecko && !s.readonly)
10615                                         return;
10616                         }
10617
10618                         // It will not steal focus while setting contentEditable
10619                         b = t.getBody();
10620                         b.disabled = true;
10621
10622                         if (!isGecko && !s.readonly)
10623                                 b.contentEditable = true;
10624
10625                         b.disabled = false;
10626
10627                         t.schema = new tinymce.html.Schema(s);
10628
10629                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
10630                                 keep_values : true,
10631                                 url_converter : t.convertURL,
10632                                 url_converter_scope : t,
10633                                 hex_colors : s.force_hex_style_colors,
10634                                 class_filter : s.class_filter,
10635                                 update_styles : 1,
10636                                 fix_ie_paragraphs : 1,
10637                                 schema : t.schema
10638                         });
10639
10640                         t.parser = new tinymce.html.DomParser(s, t.schema);
10641
10642                         // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
10643                         if (!t.settings.allow_html_in_named_anchor) {
10644                                 t.parser.addAttributeFilter('name', function(nodes, name) {
10645                                         var i = nodes.length, sibling, prevSibling, parent, node;
10646         
10647                                         while (i--) {
10648                                                 node = nodes[i];
10649                                                 if (node.name === 'a' && node.firstChild) {
10650                                                         parent = node.parent;
10651         
10652                                                         // Move children after current node
10653                                                         sibling = node.lastChild;
10654                                                         do {
10655                                                                 prevSibling = sibling.prev;
10656                                                                 parent.insert(sibling, node);
10657                                                                 sibling = prevSibling;
10658                                                         } while (sibling);
10659                                                 }
10660                                         }
10661                                 });
10662                         }
10663
10664                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style
10665                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
10666                                 var i = nodes.length, node, dom = t.dom, value, internalName;
10667
10668                                 while (i--) {
10669                                         node = nodes[i];
10670                                         value = node.attr(name);
10671                                         internalName = 'data-mce-' + name;
10672
10673                                         // Add internal attribute if we need to we don't on a refresh of the document
10674                                         if (!node.attributes.map[internalName]) {       
10675                                                 if (name === "style")
10676                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
10677                                                 else
10678                                                         node.attr(internalName, t.convertURL(value, name, node.name));
10679                                         }
10680                                 }
10681                         });
10682
10683                         // Keep scripts from executing
10684                         t.parser.addNodeFilter('script', function(nodes, name) {
10685                                 var i = nodes.length;
10686
10687                                 while (i--)
10688                                         nodes[i].attr('type', 'mce-text/javascript');
10689                         });
10690
10691                         t.parser.addNodeFilter('#cdata', function(nodes, name) {
10692                                 var i = nodes.length, node;
10693
10694                                 while (i--) {
10695                                         node = nodes[i];
10696                                         node.type = 8;
10697                                         node.name = '#comment';
10698                                         node.value = '[CDATA[' + node.value + ']]';
10699                                 }
10700                         });
10701
10702                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
10703                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
10704
10705                                 while (i--) {
10706                                         node = nodes[i];
10707
10708                                         if (node.isEmpty(nonEmptyElements))
10709                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
10710                                 }
10711                         });
10712
10713                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
10714
10715                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
10716
10717                         t.formatter = new tinymce.Formatter(this);
10718
10719                         // Register default formats
10720                         t.formatter.register({
10721                                 alignleft : [
10722                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
10723                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
10724                                 ],
10725
10726                                 aligncenter : [
10727                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
10728                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
10729                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
10730                                 ],
10731
10732                                 alignright : [
10733                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
10734                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
10735                                 ],
10736
10737                                 alignfull : [
10738                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
10739                                 ],
10740
10741                                 bold : [
10742                                         {inline : 'strong', remove : 'all'},
10743                                         {inline : 'span', styles : {fontWeight : 'bold'}},
10744                                         {inline : 'b', remove : 'all'}
10745                                 ],
10746
10747                                 italic : [
10748                                         {inline : 'em', remove : 'all'},
10749                                         {inline : 'span', styles : {fontStyle : 'italic'}},
10750                                         {inline : 'i', remove : 'all'}
10751                                 ],
10752
10753                                 underline : [
10754                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
10755                                         {inline : 'u', remove : 'all'}
10756                                 ],
10757
10758                                 strikethrough : [
10759                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
10760                                         {inline : 'strike', remove : 'all'}
10761                                 ],
10762
10763                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
10764                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
10765                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
10766                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
10767                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
10768                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
10769                                 subscript : {inline : 'sub'},
10770                                 superscript : {inline : 'sup'},
10771
10772                                 removeformat : [
10773                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
10774                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
10775                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
10776                                 ]
10777                         });
10778
10779                         // Register default block formats
10780                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
10781                                 t.formatter.register(name, {block : name, remove : 'all'});
10782                         });
10783
10784                         // Register user defined formats
10785                         t.formatter.register(t.settings.formats);
10786
10787                         t.undoManager = new tinymce.UndoManager(t);
10788
10789                         // Pass through
10790                         t.undoManager.onAdd.add(function(um, l) {
10791                                 if (um.hasUndo())
10792                                         return t.onChange.dispatch(t, l, um);
10793                         });
10794
10795                         t.undoManager.onUndo.add(function(um, l) {
10796                                 return t.onUndo.dispatch(t, l, um);
10797                         });
10798
10799                         t.undoManager.onRedo.add(function(um, l) {
10800                                 return t.onRedo.dispatch(t, l, um);
10801                         });
10802
10803                         t.forceBlocks = new tinymce.ForceBlocks(t, {
10804                                 forced_root_block : s.forced_root_block
10805                         });
10806
10807                         t.editorCommands = new tinymce.EditorCommands(t);
10808
10809                         // Pass through
10810                         t.serializer.onPreProcess.add(function(se, o) {
10811                                 return t.onPreProcess.dispatch(t, o, se);
10812                         });
10813
10814                         t.serializer.onPostProcess.add(function(se, o) {
10815                                 return t.onPostProcess.dispatch(t, o, se);
10816                         });
10817
10818                         t.onPreInit.dispatch(t);
10819
10820                         if (!s.gecko_spellcheck)
10821                                 t.getBody().spellcheck = 0;
10822
10823                         if (!s.readonly)
10824                                 t._addEvents();
10825
10826                         t.controlManager.onPostRender.dispatch(t, t.controlManager);
10827                         t.onPostRender.dispatch(t);
10828
10829                         t.quirks = new tinymce.util.Quirks(this);
10830
10831                         if (s.directionality)
10832                                 t.getBody().dir = s.directionality;
10833
10834                         if (s.nowrap)
10835                                 t.getBody().style.whiteSpace = "nowrap";
10836
10837                         if (s.handle_node_change_callback) {
10838                                 t.onNodeChange.add(function(ed, cm, n) {
10839                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
10840                                 });
10841                         }
10842
10843                         if (s.save_callback) {
10844                                 t.onSaveContent.add(function(ed, o) {
10845                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
10846
10847                                         if (h)
10848                                                 o.content = h;
10849                                 });
10850                         }
10851
10852                         if (s.onchange_callback) {
10853                                 t.onChange.add(function(ed, l) {
10854                                         t.execCallback('onchange_callback', t, l);
10855                                 });
10856                         }
10857
10858                         if (s.protect) {
10859                                 t.onBeforeSetContent.add(function(ed, o) {
10860                                         if (s.protect) {
10861                                                 each(s.protect, function(pattern) {
10862                                                         o.content = o.content.replace(pattern, function(str) {
10863                                                                 return '<!--mce:protected ' + escape(str) + '-->';
10864                                                         });
10865                                                 });
10866                                         }
10867                                 });
10868                         }
10869
10870                         if (s.convert_newlines_to_brs) {
10871                                 t.onBeforeSetContent.add(function(ed, o) {
10872                                         if (o.initial)
10873                                                 o.content = o.content.replace(/\r?\n/g, '<br />');
10874                                 });
10875                         }
10876
10877                         if (s.preformatted) {
10878                                 t.onPostProcess.add(function(ed, o) {
10879                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');
10880                                         o.content = o.content.replace(/<\/pre>\s*$/, '');
10881
10882                                         if (o.set)
10883                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
10884                                 });
10885                         }
10886
10887                         if (s.verify_css_classes) {
10888                                 t.serializer.attribValueFilter = function(n, v) {
10889                                         var s, cl;
10890
10891                                         if (n == 'class') {
10892                                                 // Build regexp for classes
10893                                                 if (!t.classesRE) {
10894                                                         cl = t.dom.getClasses();
10895
10896                                                         if (cl.length > 0) {
10897                                                                 s = '';
10898
10899                                                                 each (cl, function(o) {
10900                                                                         s += (s ? '|' : '') + o['class'];
10901                                                                 });
10902
10903                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');
10904                                                         }
10905                                                 }
10906
10907                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
10908                                         }
10909
10910                                         return v;
10911                                 };
10912                         }
10913
10914                         if (s.cleanup_callback) {
10915                                 t.onBeforeSetContent.add(function(ed, o) {
10916                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
10917                                 });
10918
10919                                 t.onPreProcess.add(function(ed, o) {
10920                                         if (o.set)
10921                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
10922
10923                                         if (o.get)
10924                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
10925                                 });
10926
10927                                 t.onPostProcess.add(function(ed, o) {
10928                                         if (o.set)
10929                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
10930
10931                                         if (o.get)                                              
10932                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
10933                                 });
10934                         }
10935
10936                         if (s.save_callback) {
10937                                 t.onGetContent.add(function(ed, o) {
10938                                         if (o.save)
10939                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
10940                                 });
10941                         }
10942
10943                         if (s.handle_event_callback) {
10944                                 t.onEvent.add(function(ed, e, o) {
10945                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)
10946                                                 Event.cancel(e);
10947                                 });
10948                         }
10949
10950                         // Add visual aids when new contents is added
10951                         t.onSetContent.add(function() {
10952                                 t.addVisual(t.getBody());
10953                         });
10954
10955                         // Remove empty contents
10956                         if (s.padd_empty_editor) {
10957                                 t.onPostProcess.add(function(ed, o) {
10958                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
10959                                 });
10960                         }
10961
10962                         if (isGecko) {
10963                                 // Fix gecko link bug, when a link is placed at the end of block elements there is
10964                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link
10965                                 function fixLinks(ed, o) {
10966                                         each(ed.dom.select('a'), function(n) {
10967                                                 var pn = n.parentNode;
10968
10969                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)
10970                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
10971                                         });
10972                                 };
10973
10974                                 t.onExecCommand.add(function(ed, cmd) {
10975                                         if (cmd === 'CreateLink')
10976                                                 fixLinks(ed);
10977                                 });
10978
10979                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
10980                         }
10981
10982                         t.load({initial : true, format : 'html'});
10983                         t.startContent = t.getContent({format : 'raw'});
10984                         t.undoManager.add();
10985                         t.initialized = true;
10986
10987                         t.onInit.dispatch(t);
10988                         t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
10989                         t.execCallback('init_instance_callback', t);
10990                         t.focus(true);
10991                         t.nodeChanged({initial : 1});
10992
10993                         // Load specified content CSS last
10994                         each(t.contentCSS, function(u) {
10995                                 t.dom.loadCSS(u);
10996                         });
10997
10998                         // Handle auto focus
10999                         if (s.auto_focus) {
11000                                 setTimeout(function () {
11001                                         var ed = tinymce.get(s.auto_focus);
11002
11003                                         ed.selection.select(ed.getBody(), 1);
11004                                         ed.selection.collapse(1);
11005                                         ed.getBody().focus();
11006                                         ed.getWin().focus();
11007                                 }, 100);
11008                         }
11009
11010                         e = null;
11011                 },
11012
11013
11014                 focus : function(sf) {
11015                         var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
11016
11017                         if (!sf) {
11018                                 // Get selected control element
11019                                 ieRng = selection.getRng();
11020                                 if (ieRng.item) {
11021                                         controlElm = ieRng.item(0);
11022                                 }
11023
11024                                 selection.normalize();
11025
11026                                 // Is not content editable
11027                                 if (!ce)
11028                                         t.getWin().focus();
11029
11030                                 // Focus the body as well since it's contentEditable
11031                                 if (tinymce.isGecko) {
11032                                         t.getBody().focus();
11033                                 }
11034
11035                                 // Restore selected control element
11036                                 // This is needed when for example an image is selected within a
11037                                 // layer a call to focus will then remove the control selection
11038                                 if (controlElm && controlElm.ownerDocument == doc) {
11039                                         ieRng = doc.body.createControlRange();
11040                                         ieRng.addElement(controlElm);
11041                                         ieRng.select();
11042                                 }
11043
11044                         }
11045
11046                         if (tinymce.activeEditor != t) {
11047                                 if ((oed = tinymce.activeEditor) != null)
11048                                         oed.onDeactivate.dispatch(oed, t);
11049
11050                                 t.onActivate.dispatch(t, oed);
11051                         }
11052
11053                         tinymce._setActive(t);
11054                 },
11055
11056                 execCallback : function(n) {
11057                         var t = this, f = t.settings[n], s;
11058
11059                         if (!f)
11060                                 return;
11061
11062                         // Look through lookup
11063                         if (t.callbackLookup && (s = t.callbackLookup[n])) {
11064                                 f = s.func;
11065                                 s = s.scope;
11066                         }
11067
11068                         if (is(f, 'string')) {
11069                                 s = f.replace(/\.\w+$/, '');
11070                                 s = s ? tinymce.resolve(s) : 0;
11071                                 f = tinymce.resolve(f);
11072                                 t.callbackLookup = t.callbackLookup || {};
11073                                 t.callbackLookup[n] = {func : f, scope : s};
11074                         }
11075
11076                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
11077                 },
11078
11079                 translate : function(s) {
11080                         var c = this.settings.language || 'en', i18n = tinymce.i18n;
11081
11082                         if (!s)
11083                                 return '';
11084
11085                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
11086                                 return i18n[c + '.' + b] || '{#' + b + '}';
11087                         });
11088                 },
11089
11090                 getLang : function(n, dv) {
11091                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
11092                 },
11093
11094                 getParam : function(n, dv, ty) {
11095                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
11096
11097                         if (ty === 'hash') {
11098                                 o = {};
11099
11100                                 if (is(v, 'string')) {
11101                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
11102                                                 v = v.split('=');
11103
11104                                                 if (v.length > 1)
11105                                                         o[tr(v[0])] = tr(v[1]);
11106                                                 else
11107                                                         o[tr(v[0])] = tr(v);
11108                                         });
11109                                 } else
11110                                         o = v;
11111
11112                                 return o;
11113                         }
11114
11115                         return v;
11116                 },
11117
11118                 nodeChanged : function(o) {
11119                         var t = this, s = t.selection, n = s.getStart() || t.getBody();
11120
11121                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
11122                         if (t.initialized) {
11123                                 o = o || {};
11124                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
11125
11126                                 // Get parents and add them to object
11127                                 o.parents = [];
11128                                 t.dom.getParent(n, function(node) {
11129                                         if (node.nodeName == 'BODY')
11130                                                 return true;
11131
11132                                         o.parents.push(node);
11133                                 });
11134
11135                                 t.onNodeChange.dispatch(
11136                                         t,
11137                                         o ? o.controlManager || t.controlManager : t.controlManager,
11138                                         n,
11139                                         s.isCollapsed(),
11140                                         o
11141                                 );
11142                         }
11143                 },
11144
11145                 addButton : function(n, s) {
11146                         var t = this;
11147
11148                         t.buttons = t.buttons || {};
11149                         t.buttons[n] = s;
11150                 },
11151
11152                 addCommand : function(name, callback, scope) {
11153                         this.execCommands[name] = {func : callback, scope : scope || this};
11154                 },
11155
11156                 addQueryStateHandler : function(name, callback, scope) {
11157                         this.queryStateCommands[name] = {func : callback, scope : scope || this};
11158                 },
11159
11160                 addQueryValueHandler : function(name, callback, scope) {
11161                         this.queryValueCommands[name] = {func : callback, scope : scope || this};
11162                 },
11163
11164                 addShortcut : function(pa, desc, cmd_func, sc) {
11165                         var t = this, c;
11166
11167                         if (!t.settings.custom_shortcuts)
11168                                 return false;
11169
11170                         t.shortcuts = t.shortcuts || {};
11171
11172                         if (is(cmd_func, 'string')) {
11173                                 c = cmd_func;
11174
11175                                 cmd_func = function() {
11176                                         t.execCommand(c, false, null);
11177                                 };
11178                         }
11179
11180                         if (is(cmd_func, 'object')) {
11181                                 c = cmd_func;
11182
11183                                 cmd_func = function() {
11184                                         t.execCommand(c[0], c[1], c[2]);
11185                                 };
11186                         }
11187
11188                         each(explode(pa), function(pa) {
11189                                 var o = {
11190                                         func : cmd_func,
11191                                         scope : sc || this,
11192                                         desc : desc,
11193                                         alt : false,
11194                                         ctrl : false,
11195                                         shift : false
11196                                 };
11197
11198                                 each(explode(pa, '+'), function(v) {
11199                                         switch (v) {
11200                                                 case 'alt':
11201                                                 case 'ctrl':
11202                                                 case 'shift':
11203                                                         o[v] = true;
11204                                                         break;
11205
11206                                                 default:
11207                                                         o.charCode = v.charCodeAt(0);
11208                                                         o.keyCode = v.toUpperCase().charCodeAt(0);
11209                                         }
11210                                 });
11211
11212                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
11213                         });
11214
11215                         return true;
11216                 },
11217
11218                 execCommand : function(cmd, ui, val, a) {
11219                         var t = this, s = 0, o, st;
11220
11221                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
11222                                 t.focus();
11223
11224                         o = {};
11225                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
11226                         if (o.terminate)
11227                                 return false;
11228
11229                         // Command callback
11230                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
11231                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11232                                 return true;
11233                         }
11234
11235                         // Registred commands
11236                         if (o = t.execCommands[cmd]) {
11237                                 st = o.func.call(o.scope, ui, val);
11238
11239                                 // Fall through on true
11240                                 if (st !== true) {
11241                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11242                                         return st;
11243                                 }
11244                         }
11245
11246                         // Plugin commands
11247                         each(t.plugins, function(p) {
11248                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {
11249                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11250                                         s = 1;
11251                                         return false;
11252                                 }
11253                         });
11254
11255                         if (s)
11256                                 return true;
11257
11258                         // Theme commands
11259                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
11260                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11261                                 return true;
11262                         }
11263
11264                         // Editor commands
11265                         if (t.editorCommands.execCommand(cmd, ui, val)) {
11266                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11267                                 return true;
11268                         }
11269
11270                         // Browser commands
11271                         t.getDoc().execCommand(cmd, ui, val);
11272                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
11273                 },
11274
11275                 queryCommandState : function(cmd) {
11276                         var t = this, o, s;
11277
11278                         // Is hidden then return undefined
11279                         if (t._isHidden())
11280                                 return;
11281
11282                         // Registred commands
11283                         if (o = t.queryStateCommands[cmd]) {
11284                                 s = o.func.call(o.scope);
11285
11286                                 // Fall though on true
11287                                 if (s !== true)
11288                                         return s;
11289                         }
11290
11291                         // Registred commands
11292                         o = t.editorCommands.queryCommandState(cmd);
11293                         if (o !== -1)
11294                                 return o;
11295
11296                         // Browser commands
11297                         try {
11298                                 return this.getDoc().queryCommandState(cmd);
11299                         } catch (ex) {
11300                                 // Fails sometimes see bug: 1896577
11301                         }
11302                 },
11303
11304                 queryCommandValue : function(c) {
11305                         var t = this, o, s;
11306
11307                         // Is hidden then return undefined
11308                         if (t._isHidden())
11309                                 return;
11310
11311                         // Registred commands
11312                         if (o = t.queryValueCommands[c]) {
11313                                 s = o.func.call(o.scope);
11314
11315                                 // Fall though on true
11316                                 if (s !== true)
11317                                         return s;
11318                         }
11319
11320                         // Registred commands
11321                         o = t.editorCommands.queryCommandValue(c);
11322                         if (is(o))
11323                                 return o;
11324
11325                         // Browser commands
11326                         try {
11327                                 return this.getDoc().queryCommandValue(c);
11328                         } catch (ex) {
11329                                 // Fails sometimes see bug: 1896577
11330                         }
11331                 },
11332
11333                 show : function() {
11334                         var t = this;
11335
11336                         DOM.show(t.getContainer());
11337                         DOM.hide(t.id);
11338                         t.load();
11339                 },
11340
11341                 hide : function() {
11342                         var t = this, d = t.getDoc();
11343
11344                         // Fixed bug where IE has a blinking cursor left from the editor
11345                         if (isIE && d)
11346                                 d.execCommand('SelectAll');
11347
11348                         // We must save before we hide so Safari doesn't crash
11349                         t.save();
11350                         DOM.hide(t.getContainer());
11351                         DOM.setStyle(t.id, 'display', t.orgDisplay);
11352                 },
11353
11354                 isHidden : function() {
11355                         return !DOM.isHidden(this.id);
11356                 },
11357
11358                 setProgressState : function(b, ti, o) {
11359                         this.onSetProgressState.dispatch(this, b, ti, o);
11360
11361                         return b;
11362                 },
11363
11364                 load : function(o) {
11365                         var t = this, e = t.getElement(), h;
11366
11367                         if (e) {
11368                                 o = o || {};
11369                                 o.load = true;
11370
11371                                 // Double encode existing entities in the value
11372                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
11373                                 o.element = e;
11374
11375                                 if (!o.no_events)
11376                                         t.onLoadContent.dispatch(t, o);
11377
11378                                 o.element = e = null;
11379
11380                                 return h;
11381                         }
11382                 },
11383
11384                 save : function(o) {
11385                         var t = this, e = t.getElement(), h, f;
11386
11387                         if (!e || !t.initialized)
11388                                 return;
11389
11390                         o = o || {};
11391                         o.save = true;
11392
11393                         // Add undo level will trigger onchange event
11394                         if (!o.no_events) {
11395                                 t.undoManager.typing = false;
11396                                 t.undoManager.add();
11397                         }
11398
11399                         o.element = e;
11400                         h = o.content = t.getContent(o);
11401
11402                         if (!o.no_events)
11403                                 t.onSaveContent.dispatch(t, o);
11404
11405                         h = o.content;
11406
11407                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
11408                                 e.innerHTML = h;
11409
11410                                 // Update hidden form element
11411                                 if (f = DOM.getParent(t.id, 'form')) {
11412                                         each(f.elements, function(e) {
11413                                                 if (e.name == t.id) {
11414                                                         e.value = h;
11415                                                         return false;
11416                                                 }
11417                                         });
11418                                 }
11419                         } else
11420                                 e.value = h;
11421
11422                         o.element = e = null;
11423
11424                         return h;
11425                 },
11426
11427                 setContent : function(content, args) {
11428                         var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
11429
11430                         // Setup args object
11431                         args = args || {};
11432                         args.format = args.format || 'html';
11433                         args.set = true;
11434                         args.content = content;
11435
11436                         // Do preprocessing
11437                         if (!args.no_events)
11438                                 self.onBeforeSetContent.dispatch(self, args);
11439
11440                         content = args.content;
11441
11442                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
11443                         // It will also be impossible to place the caret in the editor unless there is a BR element present
11444                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
11445                                 forcedRootBlockName = self.settings.forced_root_block;
11446                                 if (forcedRootBlockName)
11447                                         content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
11448                                 else
11449                                         content = '<br data-mce-bogus="1">';
11450
11451                                 body.innerHTML = content;
11452                                 self.selection.select(body, true);
11453                                 self.selection.collapse(true);
11454                                 return;
11455                         }
11456
11457                         // Parse and serialize the html
11458                         if (args.format !== 'raw') {
11459                                 content = new tinymce.html.Serializer({}, self.schema).serialize(
11460                                         self.parser.parse(content)
11461                                 );
11462                         }
11463
11464                         // Set the new cleaned contents to the editor
11465                         args.content = tinymce.trim(content);
11466                         self.dom.setHTML(body, args.content);
11467
11468                         // Do post processing
11469                         if (!args.no_events)
11470                                 self.onSetContent.dispatch(self, args);
11471
11472                         self.selection.normalize();
11473
11474                         return args.content;
11475                 },
11476
11477                 getContent : function(args) {
11478                         var self = this, content;
11479
11480                         // Setup args object
11481                         args = args || {};
11482                         args.format = args.format || 'html';
11483                         args.get = true;
11484
11485                         // Do preprocessing
11486                         if (!args.no_events)
11487                                 self.onBeforeGetContent.dispatch(self, args);
11488
11489                         // Get raw contents or by default the cleaned contents
11490                         if (args.format == 'raw')
11491                                 content = self.getBody().innerHTML;
11492                         else
11493                                 content = self.serializer.serialize(self.getBody(), args);
11494
11495                         args.content = tinymce.trim(content);
11496
11497                         // Do post processing
11498                         if (!args.no_events)
11499                                 self.onGetContent.dispatch(self, args);
11500
11501                         return args.content;
11502                 },
11503
11504                 isDirty : function() {
11505                         var self = this;
11506
11507                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
11508                 },
11509
11510                 getContainer : function() {
11511                         var t = this;
11512
11513                         if (!t.container)
11514                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');
11515
11516                         return t.container;
11517                 },
11518
11519                 getContentAreaContainer : function() {
11520                         return this.contentAreaContainer;
11521                 },
11522
11523                 getElement : function() {
11524                         return DOM.get(this.settings.content_element || this.id);
11525                 },
11526
11527                 getWin : function() {
11528                         var t = this, e;
11529
11530                         if (!t.contentWindow) {
11531                                 e = DOM.get(t.id + "_ifr");
11532
11533                                 if (e)
11534                                         t.contentWindow = e.contentWindow;
11535                         }
11536
11537                         return t.contentWindow;
11538                 },
11539
11540                 getDoc : function() {
11541                         var t = this, w;
11542
11543                         if (!t.contentDocument) {
11544                                 w = t.getWin();
11545
11546                                 if (w)
11547                                         t.contentDocument = w.document;
11548                         }
11549
11550                         return t.contentDocument;
11551                 },
11552
11553                 getBody : function() {
11554                         return this.bodyElement || this.getDoc().body;
11555                 },
11556
11557                 convertURL : function(u, n, e) {
11558                         var t = this, s = t.settings;
11559
11560                         // Use callback instead
11561                         if (s.urlconverter_callback)
11562                                 return t.execCallback('urlconverter_callback', u, e, true, n);
11563
11564                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
11565                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
11566                                 return u;
11567
11568                         // Convert to relative
11569                         if (s.relative_urls)
11570                                 return t.documentBaseURI.toRelative(u);
11571
11572                         // Convert to absolute
11573                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
11574
11575                         return u;
11576                 },
11577
11578                 addVisual : function(e) {
11579                         var t = this, s = t.settings;
11580
11581                         e = e || t.getBody();
11582
11583                         if (!is(t.hasVisual))
11584                                 t.hasVisual = s.visual;
11585
11586                         each(t.dom.select('table,a', e), function(e) {
11587                                 var v;
11588
11589                                 switch (e.nodeName) {
11590                                         case 'TABLE':
11591                                                 v = t.dom.getAttrib(e, 'border');
11592
11593                                                 if (!v || v == '0') {
11594                                                         if (t.hasVisual)
11595                                                                 t.dom.addClass(e, s.visual_table_class);
11596                                                         else
11597                                                                 t.dom.removeClass(e, s.visual_table_class);
11598                                                 }
11599
11600                                                 return;
11601
11602                                         case 'A':
11603                                                 v = t.dom.getAttrib(e, 'name');
11604
11605                                                 if (v) {
11606                                                         if (t.hasVisual)
11607                                                                 t.dom.addClass(e, 'mceItemAnchor');
11608                                                         else
11609                                                                 t.dom.removeClass(e, 'mceItemAnchor');
11610                                                 }
11611
11612                                                 return;
11613                                 }
11614                         });
11615
11616                         t.onVisualAid.dispatch(t, e, t.hasVisual);
11617                 },
11618
11619                 remove : function() {
11620                         var t = this, e = t.getContainer();
11621
11622                         t.removed = 1; // Cancels post remove event execution
11623                         t.hide();
11624
11625                         t.execCallback('remove_instance_callback', t);
11626                         t.onRemove.dispatch(t);
11627
11628                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
11629                         t.onExecCommand.listeners = [];
11630
11631                         tinymce.remove(t);
11632                         DOM.remove(e);
11633                 },
11634
11635                 destroy : function(s) {
11636                         var t = this;
11637
11638                         // One time is enough
11639                         if (t.destroyed)
11640                                 return;
11641
11642                         if (!s) {
11643                                 tinymce.removeUnload(t.destroy);
11644                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
11645
11646                                 // Manual destroy
11647                                 if (t.theme && t.theme.destroy)
11648                                         t.theme.destroy();
11649
11650                                 // Destroy controls, selection and dom
11651                                 t.controlManager.destroy();
11652                                 t.selection.destroy();
11653                                 t.dom.destroy();
11654
11655                                 // Remove all events
11656
11657                                 // Don't clear the window or document if content editable
11658                                 // is enabled since other instances might still be present
11659                                 if (!t.settings.content_editable) {
11660                                         Event.clear(t.getWin());
11661                                         Event.clear(t.getDoc());
11662                                 }
11663
11664                                 Event.clear(t.getBody());
11665                                 Event.clear(t.formElement);
11666                         }
11667
11668                         if (t.formElement) {
11669                                 t.formElement.submit = t.formElement._mceOldSubmit;
11670                                 t.formElement._mceOldSubmit = null;
11671                         }
11672
11673                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
11674
11675                         if (t.selection)
11676                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
11677
11678                         t.destroyed = 1;
11679                 },
11680
11681                 // Internal functions
11682
11683                 _addEvents : function() {
11684                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
11685                         var t = this, i, s = t.settings, dom = t.dom, lo = {
11686                                 mouseup : 'onMouseUp',
11687                                 mousedown : 'onMouseDown',
11688                                 click : 'onClick',
11689                                 keyup : 'onKeyUp',
11690                                 keydown : 'onKeyDown',
11691                                 keypress : 'onKeyPress',
11692                                 submit : 'onSubmit',
11693                                 reset : 'onReset',
11694                                 contextmenu : 'onContextMenu',
11695                                 dblclick : 'onDblClick',
11696                                 paste : 'onPaste' // Doesn't work in all browsers yet
11697                         };
11698
11699                         function eventHandler(e, o) {
11700                                 var ty = e.type;
11701
11702                                 // Don't fire events when it's removed
11703                                 if (t.removed)
11704                                         return;
11705
11706                                 // Generic event handler
11707                                 if (t.onEvent.dispatch(t, e, o) !== false) {
11708                                         // Specific event handler
11709                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);
11710                                 }
11711                         };
11712
11713                         // Add DOM events
11714                         each(lo, function(v, k) {
11715                                 switch (k) {
11716                                         case 'contextmenu':
11717                                                 dom.bind(t.getDoc(), k, eventHandler);
11718                                                 break;
11719
11720                                         case 'paste':
11721                                                 dom.bind(t.getBody(), k, function(e) {
11722                                                         eventHandler(e);
11723                                                 });
11724                                                 break;
11725
11726                                         case 'submit':
11727                                         case 'reset':
11728                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
11729                                                 break;
11730
11731                                         default:
11732                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
11733                                 }
11734                         });
11735
11736                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
11737                                 t.focus(true);
11738                         });
11739
11740
11741                         // Fixes bug where a specified document_base_uri could result in broken images
11742                         // This will also fix drag drop of images in Gecko
11743                         if (tinymce.isGecko) {
11744                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
11745                                         var v;
11746
11747                                         e = e.target;
11748
11749                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
11750                                                 e.src = t.documentBaseURI.toAbsolute(v);
11751                                 });
11752                         }
11753
11754                         // Set various midas options in Gecko
11755                         if (isGecko) {
11756                                 function setOpts() {
11757                                         var t = this, d = t.getDoc(), s = t.settings;
11758
11759                                         if (isGecko && !s.readonly) {
11760                                                 if (t._isHidden()) {
11761                                                         try {
11762                                                                 if (!s.content_editable) {
11763                                                                         d.body.contentEditable = false;
11764                                                                         d.body.contentEditable = true;
11765                                                                 }
11766                                                         } catch (ex) {
11767                                                                 // Fails if it's hidden
11768                                                         }
11769                                                 }
11770
11771                                                 try {
11772                                                         // Try new Gecko method
11773                                                         d.execCommand("styleWithCSS", 0, false);
11774                                                 } catch (ex) {
11775                                                         // Use old method
11776                                                         if (!t._isHidden())
11777                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
11778                                                 }
11779
11780                                                 if (!s.table_inline_editing)
11781                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
11782
11783                                                 if (!s.object_resizing)
11784                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
11785                                         }
11786                                 };
11787
11788                                 t.onBeforeExecCommand.add(setOpts);
11789                                 t.onMouseDown.add(setOpts);
11790                         }
11791
11792                         t.onClick.add(function(ed, e) {
11793                                 e = e.target;
11794
11795                                 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
11796                                 // WebKit can't even do simple things like selecting an image
11797                                 // Needs tobe the setBaseAndExtend or it will fail to select floated images
11798                                 if (tinymce.isWebKit && e.nodeName == 'IMG')
11799                                         t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
11800
11801                                 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))
11802                                         t.selection.select(e);
11803
11804                                 t.nodeChanged();
11805                         });
11806
11807                         // Add node change handlers
11808                         t.onMouseUp.add(t.nodeChanged);
11809                         //t.onClick.add(t.nodeChanged);
11810                         t.onKeyUp.add(function(ed, e) {
11811                                 var c = e.keyCode;
11812
11813                                 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)
11814                                         t.nodeChanged();
11815                         });
11816
11817
11818                         // Add block quote deletion handler
11819                         t.onKeyDown.add(function(ed, e) {
11820                                 // Was the BACKSPACE key pressed?
11821                                 if (e.keyCode != 8)
11822                                         return;
11823
11824                                 var n = ed.selection.getRng().startContainer;
11825                                 var offset = ed.selection.getRng().startOffset;
11826
11827                                 while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
11828                                         n = n.parentNode;
11829                                         
11830                                 // Is the cursor at the beginning of a blockquote?
11831                                 if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
11832                                         // Remove the blockquote
11833                                         ed.formatter.toggle('blockquote', null, n.parentNode);
11834
11835                                         // Move the caret to the beginning of n
11836                                         var rng = ed.selection.getRng();
11837                                         rng.setStart(n, 0);
11838                                         rng.setEnd(n, 0);
11839                                         ed.selection.setRng(rng);
11840                                         ed.selection.collapse(false);
11841                                 }
11842                         });
11843  
11844
11845
11846                         // Add reset handler
11847                         t.onReset.add(function() {
11848                                 t.setContent(t.startContent, {format : 'raw'});
11849                         });
11850
11851                         // Add shortcuts
11852                         if (s.custom_shortcuts) {
11853                                 if (s.custom_undo_redo_keyboard_shortcuts) {
11854                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
11855                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
11856                                 }
11857
11858                                 // Add default shortcuts for gecko
11859                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
11860                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
11861                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
11862
11863                                 // BlockFormat shortcuts keys
11864                                 for (i=1; i<=6; i++)
11865                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
11866
11867                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
11868                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
11869                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
11870
11871                                 function find(e) {
11872                                         var v = null;
11873
11874                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)
11875                                                 return v;
11876
11877                                         each(t.shortcuts, function(o) {
11878                                                 if (tinymce.isMac && o.ctrl != e.metaKey)
11879                                                         return;
11880                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
11881                                                         return;
11882
11883                                                 if (o.alt != e.altKey)
11884                                                         return;
11885
11886                                                 if (o.shift != e.shiftKey)
11887                                                         return;
11888
11889                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
11890                                                         v = o;
11891                                                         return false;
11892                                                 }
11893                                         });
11894
11895                                         return v;
11896                                 };
11897
11898                                 t.onKeyUp.add(function(ed, e) {
11899                                         var o = find(e);
11900
11901                                         if (o)
11902                                                 return Event.cancel(e);
11903                                 });
11904
11905                                 t.onKeyPress.add(function(ed, e) {
11906                                         var o = find(e);
11907
11908                                         if (o)
11909                                                 return Event.cancel(e);
11910                                 });
11911
11912                                 t.onKeyDown.add(function(ed, e) {
11913                                         var o = find(e);
11914
11915                                         if (o) {
11916                                                 o.func.call(o.scope);
11917                                                 return Event.cancel(e);
11918                                         }
11919                                 });
11920                         }
11921
11922                         if (tinymce.isIE) {
11923                                 // Fix so resize will only update the width and height attributes not the styles of an image
11924                                 // It will also block mceItemNoResize items
11925                                 dom.bind(t.getDoc(), 'controlselect', function(e) {
11926                                         var re = t.resizeInfo, cb;
11927
11928                                         e = e.target;
11929
11930                                         // Don't do this action for non image elements
11931                                         if (e.nodeName !== 'IMG')
11932                                                 return;
11933
11934                                         if (re)
11935                                                 dom.unbind(re.node, re.ev, re.cb);
11936
11937                                         if (!dom.hasClass(e, 'mceItemNoResize')) {
11938                                                 ev = 'resizeend';
11939                                                 cb = dom.bind(e, ev, function(e) {
11940                                                         var v;
11941
11942                                                         e = e.target;
11943
11944                                                         if (v = dom.getStyle(e, 'width')) {
11945                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
11946                                                                 dom.setStyle(e, 'width', '');
11947                                                         }
11948
11949                                                         if (v = dom.getStyle(e, 'height')) {
11950                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
11951                                                                 dom.setStyle(e, 'height', '');
11952                                                         }
11953                                                 });
11954                                         } else {
11955                                                 ev = 'resizestart';
11956                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
11957                                         }
11958
11959                                         re = t.resizeInfo = {
11960                                                 node : e,
11961                                                 ev : ev,
11962                                                 cb : cb
11963                                         };
11964                                 });
11965                         }
11966
11967                         if (tinymce.isOpera) {
11968                                 t.onClick.add(function(ed, e) {
11969                                         Event.prevent(e);
11970                                 });
11971                         }
11972
11973                         // Add custom undo/redo handlers
11974                         if (s.custom_undo_redo) {
11975                                 function addUndo() {
11976                                         t.undoManager.typing = false;
11977                                         t.undoManager.add();
11978                                 };
11979
11980                                 dom.bind(t.getDoc(), 'focusout', function(e) {
11981                                         if (!t.removed && t.undoManager.typing)
11982                                                 addUndo();
11983                                 });
11984
11985                                 // Add undo level when contents is drag/dropped within the editor
11986                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
11987                                         addUndo();
11988                                 });
11989
11990                                 t.onKeyUp.add(function(ed, e) {
11991                                         var keyCode = e.keyCode;
11992
11993                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
11994                                                 addUndo();
11995                                 });
11996
11997                                 t.onKeyDown.add(function(ed, e) {
11998                                         var keyCode = e.keyCode, sel;
11999
12000                                         if (keyCode == 8) {
12001                                                 sel = t.getDoc().selection;
12002
12003                                                 // Fix IE control + backspace browser bug
12004                                                 if (sel && sel.createRange && sel.createRange().item) {
12005                                                         t.undoManager.beforeChange();
12006                                                         ed.dom.remove(sel.createRange().item(0));
12007                                                         addUndo();
12008
12009                                                         return Event.cancel(e);
12010                                                 }
12011                                         }
12012
12013                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
12014                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
12015                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
12016                                                 // Todo: Remove this once we normalize enter behavior on IE
12017                                                 if (tinymce.isIE && keyCode == 13)
12018                                                         t.undoManager.beforeChange();
12019
12020                                                 if (t.undoManager.typing)
12021                                                         addUndo();
12022
12023                                                 return;
12024                                         }
12025
12026                                         // If key isn't shift,ctrl,alt,capslock,metakey
12027                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
12028                                                 t.undoManager.beforeChange();
12029                                                 t.undoManager.typing = true;
12030                                                 t.undoManager.add();
12031                                         }
12032                                 });
12033
12034                                 t.onMouseDown.add(function() {
12035                                         if (t.undoManager.typing)
12036                                                 addUndo();
12037                                 });
12038                         }
12039
12040                         // Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5
12041                         // It only fires the nodeChange event every 50ms since it would other wise update the UI when you type and it hogs the CPU
12042                         if (tinymce.isWebKit) {
12043                                 dom.bind(t.getDoc(), 'selectionchange', function() {
12044                                         if (t.selectionTimer) {
12045                                                 window.clearTimeout(t.selectionTimer);
12046                                                 t.selectionTimer = 0;
12047                                         }
12048
12049                                         t.selectionTimer = window.setTimeout(function() {
12050                                                 t.nodeChanged();
12051                                         }, 50);
12052                                 });
12053                         }
12054
12055                         // Bug fix for FireFox keeping styles from end of selection instead of start.
12056                         if (tinymce.isGecko) {
12057                                 function getAttributeApplyFunction() {
12058                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
12059
12060                                         return function() {
12061                                                 var target = t.selection.getStart();
12062
12063                                                 if (target !== t.getBody()) {
12064                                                         t.dom.removeAllAttribs(target);
12065
12066                                                         each(template, function(attr) {
12067                                                                 target.setAttributeNode(attr.cloneNode(true));
12068                                                         });
12069                                                 }
12070                                         };
12071                                 }
12072
12073                                 function isSelectionAcrossElements() {
12074                                         var s = t.selection;
12075
12076                                         return !s.isCollapsed() && s.getStart() != s.getEnd();
12077                                 }
12078
12079                                 t.onKeyPress.add(function(ed, e) {
12080                                         var applyAttributes;
12081
12082                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
12083                                                 applyAttributes = getAttributeApplyFunction();
12084                                                 t.getDoc().execCommand('delete', false, null);
12085                                                 applyAttributes();
12086
12087                                                 return Event.cancel(e);
12088                                         }
12089                                 });
12090
12091                                 t.dom.bind(t.getDoc(), 'cut', function(e) {
12092                                         var applyAttributes;
12093
12094                                         if (isSelectionAcrossElements()) {
12095                                                 applyAttributes = getAttributeApplyFunction();
12096                                                 t.onKeyUp.addToTop(Event.cancel, Event);
12097
12098                                                 setTimeout(function() {
12099                                                         applyAttributes();
12100                                                         t.onKeyUp.remove(Event.cancel, Event);
12101                                                 }, 0);
12102                                         }
12103                                 });
12104                         }
12105                 },
12106
12107                 _isHidden : function() {
12108                         var s;
12109
12110                         if (!isGecko)
12111                                 return 0;
12112
12113                         // Weird, wheres that cursor selection?
12114                         s = this.selection.getSel();
12115                         return (!s || !s.rangeCount || s.rangeCount == 0);
12116                 }
12117         });
12118 })(tinymce);
12119
12120 (function(tinymce) {
12121         // Added for compression purposes
12122         var each = tinymce.each, undefined, TRUE = true, FALSE = false;
12123
12124         tinymce.EditorCommands = function(editor) {
12125                 var dom = editor.dom,
12126                         selection = editor.selection,
12127                         commands = {state: {}, exec : {}, value : {}},
12128                         settings = editor.settings,
12129                         bookmark;
12130
12131                 function execCommand(command, ui, value) {
12132                         var func;
12133
12134                         command = command.toLowerCase();
12135                         if (func = commands.exec[command]) {
12136                                 func(command, ui, value);
12137                                 return TRUE;
12138                         }
12139
12140                         return FALSE;
12141                 };
12142
12143                 function queryCommandState(command) {
12144                         var func;
12145
12146                         command = command.toLowerCase();
12147                         if (func = commands.state[command])
12148                                 return func(command);
12149
12150                         return -1;
12151                 };
12152
12153                 function queryCommandValue(command) {
12154                         var func;
12155
12156                         command = command.toLowerCase();
12157                         if (func = commands.value[command])
12158                                 return func(command);
12159
12160                         return FALSE;
12161                 };
12162
12163                 function addCommands(command_list, type) {
12164                         type = type || 'exec';
12165
12166                         each(command_list, function(callback, command) {
12167                                 each(command.toLowerCase().split(','), function(command) {
12168                                         commands[type][command] = callback;
12169                                 });
12170                         });
12171                 };
12172
12173                 // Expose public methods
12174                 tinymce.extend(this, {
12175                         execCommand : execCommand,
12176                         queryCommandState : queryCommandState,
12177                         queryCommandValue : queryCommandValue,
12178                         addCommands : addCommands
12179                 });
12180
12181                 // Private methods
12182
12183                 function execNativeCommand(command, ui, value) {
12184                         if (ui === undefined)
12185                                 ui = FALSE;
12186
12187                         if (value === undefined)
12188                                 value = null;
12189
12190                         return editor.getDoc().execCommand(command, ui, value);
12191                 };
12192
12193                 function isFormatMatch(name) {
12194                         return editor.formatter.match(name);
12195                 };
12196
12197                 function toggleFormat(name, value) {
12198                         editor.formatter.toggle(name, value ? {value : value} : undefined);
12199                 };
12200
12201                 function storeSelection(type) {
12202                         bookmark = selection.getBookmark(type);
12203                 };
12204
12205                 function restoreSelection() {
12206                         selection.moveToBookmark(bookmark);
12207                 };
12208
12209                 // Add execCommand overrides
12210                 addCommands({
12211                         // Ignore these, added for compatibility
12212                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},
12213
12214                         // Add undo manager logic
12215                         'mceEndUndoLevel,mceAddUndoLevel' : function() {
12216                                 editor.undoManager.add();
12217                         },
12218
12219                         'Cut,Copy,Paste' : function(command) {
12220                                 var doc = editor.getDoc(), failed;
12221
12222                                 // Try executing the native command
12223                                 try {
12224                                         execNativeCommand(command);
12225                                 } catch (ex) {
12226                                         // Command failed
12227                                         failed = TRUE;
12228                                 }
12229
12230                                 // Present alert message about clipboard access not being available
12231                                 if (failed || !doc.queryCommandSupported(command)) {
12232                                         if (tinymce.isGecko) {
12233                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
12234                                                         if (state)
12235                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
12236                                                 });
12237                                         } else
12238                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
12239                                 }
12240                         },
12241
12242                         // Override unlink command
12243                         unlink : function(command) {
12244                                 if (selection.isCollapsed())
12245                                         selection.select(selection.getNode());
12246
12247                                 execNativeCommand(command);
12248                                 selection.collapse(FALSE);
12249                         },
12250
12251                         // Override justify commands to use the text formatter engine
12252                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12253                                 var align = command.substring(7);
12254
12255                                 // Remove all other alignments first
12256                                 each('left,center,right,full'.split(','), function(name) {
12257                                         if (align != name)
12258                                                 editor.formatter.remove('align' + name);
12259                                 });
12260
12261                                 toggleFormat('align' + align);
12262                                 execCommand('mceRepaint');
12263                         },
12264
12265                         // Override list commands to fix WebKit bug
12266                         'InsertUnorderedList,InsertOrderedList' : function(command) {
12267                                 var listElm, listParent;
12268
12269                                 execNativeCommand(command);
12270
12271                                 // WebKit produces lists within block elements so we need to split them
12272                                 // we will replace the native list creation logic to custom logic later on
12273                                 // TODO: Remove this when the list creation logic is removed
12274                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
12275                                 if (listElm) {
12276                                         listParent = listElm.parentNode;
12277
12278                                         // If list is within a text block then split that block
12279                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
12280                                                 storeSelection();
12281                                                 dom.split(listParent, listElm);
12282                                                 restoreSelection();
12283                                         }
12284                                 }
12285                         },
12286
12287                         // Override commands to use the text formatter engine
12288                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12289                                 toggleFormat(command);
12290                         },
12291
12292                         // Override commands to use the text formatter engine
12293                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
12294                                 toggleFormat(command, value);
12295                         },
12296
12297                         FontSize : function(command, ui, value) {
12298                                 var fontClasses, fontSizes;
12299
12300                                 // Convert font size 1-7 to styles
12301                                 if (value >= 1 && value <= 7) {
12302                                         fontSizes = tinymce.explode(settings.font_size_style_values);
12303                                         fontClasses = tinymce.explode(settings.font_size_classes);
12304
12305                                         if (fontClasses)
12306                                                 value = fontClasses[value - 1] || value;
12307                                         else
12308                                                 value = fontSizes[value - 1] || value;
12309                                 }
12310
12311                                 toggleFormat(command, value);
12312                         },
12313
12314                         RemoveFormat : function(command) {
12315                                 editor.formatter.remove(command);
12316                         },
12317
12318                         mceBlockQuote : function(command) {
12319                                 toggleFormat('blockquote');
12320                         },
12321
12322                         FormatBlock : function(command, ui, value) {
12323                                 return toggleFormat(value || 'p');
12324                         },
12325
12326                         mceCleanup : function() {
12327                                 var bookmark = selection.getBookmark();
12328
12329                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
12330
12331                                 selection.moveToBookmark(bookmark);
12332                         },
12333
12334                         mceRemoveNode : function(command, ui, value) {
12335                                 var node = value || selection.getNode();
12336
12337                                 // Make sure that the body node isn't removed
12338                                 if (node != editor.getBody()) {
12339                                         storeSelection();
12340                                         editor.dom.remove(node, TRUE);
12341                                         restoreSelection();
12342                                 }
12343                         },
12344
12345                         mceSelectNodeDepth : function(command, ui, value) {
12346                                 var counter = 0;
12347
12348                                 dom.getParent(selection.getNode(), function(node) {
12349                                         if (node.nodeType == 1 && counter++ == value) {
12350                                                 selection.select(node);
12351                                                 return FALSE;
12352                                         }
12353                                 }, editor.getBody());
12354                         },
12355
12356                         mceSelectNode : function(command, ui, value) {
12357                                 selection.select(value);
12358                         },
12359
12360                         mceInsertContent : function(command, ui, value) {
12361                                 var parser, serializer, parentNode, rootNode, fragment, args,
12362                                         marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
12363
12364                                 // Setup parser and serializer
12365                                 parser = editor.parser;
12366                                 serializer = new tinymce.html.Serializer({}, editor.schema);
12367                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
12368
12369                                 // Run beforeSetContent handlers on the HTML to be inserted
12370                                 args = {content: value, format: 'html'};
12371                                 selection.onBeforeSetContent.dispatch(selection, args);
12372                                 value = args.content;
12373
12374                                 // Add caret at end of contents if it's missing
12375                                 if (value.indexOf('{$caret}') == -1)
12376                                         value += '{$caret}';
12377
12378                                 // Replace the caret marker with a span bookmark element
12379                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);
12380
12381                                 // Insert node maker where we will insert the new HTML and get it's parent
12382                                 if (!selection.isCollapsed())
12383                                         editor.getDoc().execCommand('Delete', false, null);
12384
12385                                 parentNode = selection.getNode();
12386
12387                                 // Parse the fragment within the context of the parent node
12388                                 args = {context : parentNode.nodeName.toLowerCase()};
12389                                 fragment = parser.parse(value, args);
12390
12391                                 // Move the caret to a more suitable location
12392                                 node = fragment.lastChild;
12393                                 if (node.attr('id') == 'mce_marker') {
12394                                         marker = node;
12395
12396                                         for (node = node.prev; node; node = node.walk(true)) {
12397                                                 if (node.type == 3 || !dom.isBlock(node.name)) {
12398                                                         node.parent.insert(marker, node, node.name === 'br');
12399                                                         break;
12400                                                 }
12401                                         }
12402                                 }
12403
12404                                 // If parser says valid we can insert the contents into that parent
12405                                 if (!args.invalid) {
12406                                         value = serializer.serialize(fragment);
12407
12408                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent
12409                                         node = parentNode.firstChild;
12410                                         node2 = parentNode.lastChild;
12411                                         if (!node || (node === node2 && node.nodeName === 'BR'))
12412                                                 dom.setHTML(parentNode, value);
12413                                         else
12414                                                 selection.setContent(value);
12415                                 } else {
12416                                         // If the fragment was invalid within that context then we need
12417                                         // to parse and process the parent it's inserted into
12418
12419                                         // Insert bookmark node and get the parent
12420                                         selection.setContent(bookmarkHtml);
12421                                         parentNode = editor.selection.getNode();
12422                                         rootNode = editor.getBody();
12423
12424                                         // Opera will return the document node when selection is in root
12425                                         if (parentNode.nodeType == 9)
12426                                                 parentNode = node = rootNode;
12427                                         else
12428                                                 node = parentNode;
12429
12430                                         // Find the ancestor just before the root element
12431                                         while (node !== rootNode) {
12432                                                 parentNode = node;
12433                                                 node = node.parentNode;
12434                                         }
12435
12436                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
12437                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
12438                                         value = serializer.serialize(
12439                                                 parser.parse(
12440                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem
12441                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
12442                                                                 return serializer.serialize(fragment);
12443                                                         })
12444                                                 )
12445                                         );
12446
12447                                         // Set the inner/outer HTML depending on if we are in the root or not
12448                                         if (parentNode == rootNode)
12449                                                 dom.setHTML(rootNode, value);
12450                                         else
12451                                                 dom.setOuterHTML(parentNode, value);
12452                                 }
12453
12454                                 marker = dom.get('mce_marker');
12455
12456                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
12457                                 nodeRect = dom.getRect(marker);
12458                                 viewPortRect = dom.getViewPort(editor.getWin());
12459
12460                                 // Check if node is out side the viewport if it is then scroll to it
12461                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
12462                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
12463                                         viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
12464                                         viewportBodyElement.scrollLeft = nodeRect.x;
12465                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
12466                                 }
12467
12468                                 // Move selection before marker and remove it
12469                                 rng = dom.createRng();
12470
12471                                 // If previous sibling is a text node set the selection to the end of that node
12472                                 node = marker.previousSibling;
12473                                 if (node && node.nodeType == 3) {
12474                                         rng.setStart(node, node.nodeValue.length);
12475                                 } else {
12476                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
12477                                         rng.setStartBefore(marker);
12478                                         rng.setEndBefore(marker);
12479                                 }
12480
12481                                 // Remove the marker node and set the new range
12482                                 dom.remove(marker);
12483                                 selection.setRng(rng);
12484
12485                                 // Dispatch after event and add any visual elements needed
12486                                 selection.onSetContent.dispatch(selection, args);
12487                                 editor.addVisual();
12488                         },
12489
12490                         mceInsertRawHTML : function(command, ui, value) {
12491                                 selection.setContent('tiny_mce_marker');
12492                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
12493                         },
12494
12495                         mceSetContent : function(command, ui, value) {
12496                                 editor.setContent(value);
12497                         },
12498
12499                         'Indent,Outdent' : function(command) {
12500                                 var intentValue, indentUnit, value;
12501
12502                                 // Setup indent level
12503                                 intentValue = settings.indentation;
12504                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
12505                                 intentValue = parseInt(intentValue);
12506
12507                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
12508                                         each(selection.getSelectedBlocks(), function(element) {
12509                                                 if (command == 'outdent') {
12510                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
12511                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
12512                                                 } else
12513                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
12514                                         });
12515                                 } else
12516                                         execNativeCommand(command);
12517                         },
12518
12519                         mceRepaint : function() {
12520                                 var bookmark;
12521
12522                                 if (tinymce.isGecko) {
12523                                         try {
12524                                                 storeSelection(TRUE);
12525
12526                                                 if (selection.getSel())
12527                                                         selection.getSel().selectAllChildren(editor.getBody());
12528
12529                                                 selection.collapse(TRUE);
12530                                                 restoreSelection();
12531                                         } catch (ex) {
12532                                                 // Ignore
12533                                         }
12534                                 }
12535                         },
12536
12537                         mceToggleFormat : function(command, ui, value) {
12538                                 editor.formatter.toggle(value);
12539                         },
12540
12541                         InsertHorizontalRule : function() {
12542                                 editor.execCommand('mceInsertContent', false, '<hr />');
12543                         },
12544
12545                         mceToggleVisualAid : function() {
12546                                 editor.hasVisual = !editor.hasVisual;
12547                                 editor.addVisual();
12548                         },
12549
12550                         mceReplaceContent : function(command, ui, value) {
12551                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
12552                         },
12553
12554                         mceInsertLink : function(command, ui, value) {
12555                                 var link = dom.getParent(selection.getNode(), 'a'), img, style, cls;
12556
12557                                 if (tinymce.is(value, 'string'))
12558                                         value = {href : value};
12559
12560                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
12561                                 value.href = value.href.replace(' ', '%20');
12562
12563                                 if (!link) {
12564                                         // WebKit can't create links on floated images for some odd reason
12565                                         // So, just remove styles and restore it later
12566                                         if (tinymce.isWebKit) {
12567                                                 img = dom.getParent(selection.getNode(), 'img');
12568
12569                                                 if (img) {
12570                                                         style = img.style.cssText;
12571                                                         cls = img.className;
12572                                                         img.style.cssText = null;
12573                                                         img.className = null;
12574                                                 }
12575                                         }
12576
12577                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
12578
12579                                         // Restore styles
12580                                         if (style)
12581                                                 img.style.cssText = style;
12582                                         if (cls)
12583                                                 img.className = cls;
12584
12585                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
12586                                                 dom.setAttribs(link, value);
12587                                         });
12588                                 } else {
12589                                         if (value.href)
12590                                                 dom.setAttribs(link, value);
12591                                         else
12592                                                 editor.dom.remove(link, TRUE);
12593                                 }
12594                         },
12595                         
12596                         selectAll : function() {
12597                                 var root = dom.getRoot(), rng = dom.createRng();
12598
12599                                 rng.setStart(root, 0);
12600                                 rng.setEnd(root, root.childNodes.length);
12601
12602                                 editor.selection.setRng(rng);
12603                         }
12604                 });
12605
12606                 // Add queryCommandState overrides
12607                 addCommands({
12608                         // Override justify commands
12609                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12610                                 return isFormatMatch('align' + command.substring(7));
12611                         },
12612
12613                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12614                                 return isFormatMatch(command);
12615                         },
12616
12617                         mceBlockQuote : function() {
12618                                 return isFormatMatch('blockquote');
12619                         },
12620
12621                         Outdent : function() {
12622                                 var node;
12623
12624                                 if (settings.inline_styles) {
12625                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12626                                                 return TRUE;
12627
12628                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12629                                                 return TRUE;
12630                                 }
12631
12632                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
12633                         },
12634
12635                         'InsertUnorderedList,InsertOrderedList' : function(command) {
12636                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
12637                         }
12638                 }, 'state');
12639
12640                 // Add queryCommandValue overrides
12641                 addCommands({
12642                         'FontSize,FontName' : function(command) {
12643                                 var value = 0, parent;
12644
12645                                 if (parent = dom.getParent(selection.getNode(), 'span')) {
12646                                         if (command == 'fontsize')
12647                                                 value = parent.style.fontSize;
12648                                         else
12649                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
12650                                 }
12651
12652                                 return value;
12653                         }
12654                 }, 'value');
12655
12656                 // Add undo manager logic
12657                 if (settings.custom_undo_redo) {
12658                         addCommands({
12659                                 Undo : function() {
12660                                         editor.undoManager.undo();
12661                                 },
12662
12663                                 Redo : function() {
12664                                         editor.undoManager.redo();
12665                                 }
12666                         });
12667                 }
12668         };
12669 })(tinymce);
12670
12671 (function(tinymce) {
12672         var Dispatcher = tinymce.util.Dispatcher;
12673
12674         tinymce.UndoManager = function(editor) {
12675                 var self, index = 0, data = [], beforeBookmark;
12676
12677                 function getContent() {
12678                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
12679                 };
12680
12681                 return self = {
12682                         typing : false,
12683
12684                         onAdd : new Dispatcher(self),
12685
12686                         onUndo : new Dispatcher(self),
12687
12688                         onRedo : new Dispatcher(self),
12689
12690                         beforeChange : function() {
12691                                 beforeBookmark = editor.selection.getBookmark(2, true);
12692                         },
12693
12694                         add : function(level) {
12695                                 var i, settings = editor.settings, lastLevel;
12696
12697                                 level = level || {};
12698                                 level.content = getContent();
12699
12700                                 // Add undo level if needed
12701                                 lastLevel = data[index];
12702                                 if (lastLevel && lastLevel.content == level.content)
12703                                         return null;
12704
12705                                 // Set before bookmark on previous level
12706                                 if (data[index])
12707                                         data[index].beforeBookmark = beforeBookmark;
12708
12709                                 // Time to compress
12710                                 if (settings.custom_undo_redo_levels) {
12711                                         if (data.length > settings.custom_undo_redo_levels) {
12712                                                 for (i = 0; i < data.length - 1; i++)
12713                                                         data[i] = data[i + 1];
12714
12715                                                 data.length--;
12716                                                 index = data.length;
12717                                         }
12718                                 }
12719
12720                                 // Get a non intrusive normalized bookmark
12721                                 level.bookmark = editor.selection.getBookmark(2, true);
12722
12723                                 // Crop array if needed
12724                                 if (index < data.length - 1)
12725                                         data.length = index + 1;
12726
12727                                 data.push(level);
12728                                 index = data.length - 1;
12729
12730                                 self.onAdd.dispatch(self, level);
12731                                 editor.isNotDirty = 0;
12732
12733                                 return level;
12734                         },
12735
12736                         undo : function() {
12737                                 var level, i;
12738
12739                                 if (self.typing) {
12740                                         self.add();
12741                                         self.typing = false;
12742                                 }
12743
12744                                 if (index > 0) {
12745                                         level = data[--index];
12746
12747                                         editor.setContent(level.content, {format : 'raw'});
12748                                         editor.selection.moveToBookmark(level.beforeBookmark);
12749
12750                                         self.onUndo.dispatch(self, level);
12751                                 }
12752
12753                                 return level;
12754                         },
12755
12756                         redo : function() {
12757                                 var level;
12758
12759                                 if (index < data.length - 1) {
12760                                         level = data[++index];
12761
12762                                         editor.setContent(level.content, {format : 'raw'});
12763                                         editor.selection.moveToBookmark(level.bookmark);
12764
12765                                         self.onRedo.dispatch(self, level);
12766                                 }
12767
12768                                 return level;
12769                         },
12770
12771                         clear : function() {
12772                                 data = [];
12773                                 index = 0;
12774                                 self.typing = false;
12775                         },
12776
12777                         hasUndo : function() {
12778                                 return index > 0 || this.typing;
12779                         },
12780
12781                         hasRedo : function() {
12782                                 return index < data.length - 1 && !this.typing;
12783                         }
12784                 };
12785         };
12786 })(tinymce);
12787
12788 (function(tinymce) {
12789         // Shorten names
12790         var Event = tinymce.dom.Event,
12791                 isIE = tinymce.isIE,
12792                 isGecko = tinymce.isGecko,
12793                 isOpera = tinymce.isOpera,
12794                 each = tinymce.each,
12795                 extend = tinymce.extend,
12796                 TRUE = true,
12797                 FALSE = false;
12798
12799         function cloneFormats(node) {
12800                 var clone, temp, inner;
12801
12802                 do {
12803                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
12804                                 if (clone) {
12805                                         temp = node.cloneNode(false);
12806                                         temp.appendChild(clone);
12807                                         clone = temp;
12808                                 } else {
12809                                         clone = inner = node.cloneNode(false);
12810                                 }
12811
12812                                 clone.removeAttribute('id');
12813                         }
12814                 } while (node = node.parentNode);
12815
12816                 if (clone)
12817                         return {wrapper : clone, inner : inner};
12818         };
12819
12820         // Checks if the selection/caret is at the end of the specified block element
12821         function isAtEnd(rng, par) {
12822                 var rng2 = par.ownerDocument.createRange();
12823
12824                 rng2.setStart(rng.endContainer, rng.endOffset);
12825                 rng2.setEndAfter(par);
12826
12827                 // 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
12828                 return rng2.cloneContents().textContent.length == 0;
12829         };
12830
12831         function splitList(selection, dom, li) {
12832                 var listBlock, block;
12833
12834                 if (dom.isEmpty(li)) {
12835                         listBlock = dom.getParent(li, 'ul,ol');
12836
12837                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
12838                                 dom.split(listBlock, li);
12839                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
12840                                 dom.replace(block, li);
12841                                 selection.select(block, 1);
12842                         }
12843
12844                         return FALSE;
12845                 }
12846
12847                 return TRUE;
12848         };
12849
12850         tinymce.create('tinymce.ForceBlocks', {
12851                 ForceBlocks : function(ed) {
12852                         var t = this, s = ed.settings, elm;
12853
12854                         t.editor = ed;
12855                         t.dom = ed.dom;
12856                         elm = (s.forced_root_block || 'p').toLowerCase();
12857                         s.element = elm.toUpperCase();
12858
12859                         ed.onPreInit.add(t.setup, t);
12860                 },
12861
12862                 setup : function() {
12863                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
12864
12865                         // Force root blocks
12866                         if (s.forced_root_block) {
12867                                 function addRootBlocks() {
12868                                         var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
12869
12870                                         if (!node || node.nodeType !== 1)
12871                                                 return;
12872
12873                                         // Check if node is wrapped in block
12874                                         while (node != rootNode) {
12875                                                 if (blockElements[node.nodeName])
12876                                                         return;
12877
12878                                                 node = node.parentNode;
12879                                         }
12880
12881                                         // Get current selection
12882                                         rng = selection.getRng();
12883                                         if (rng.setStart) {
12884                                                 startContainer = rng.startContainer;
12885                                                 startOffset = rng.startOffset;
12886                                                 endContainer = rng.endContainer;
12887                                                 endOffset = rng.endOffset;
12888                                         } else {
12889                                                 // Force control range into text range
12890                                                 if (rng.item) {
12891                                                         rng = ed.getDoc().body.createTextRange();
12892                                                         rng.moveToElementText(rng.item(0));
12893                                                 }
12894
12895                                                 tmpRng = rng.duplicate();
12896                                                 tmpRng.collapse(true);
12897                                                 startOffset = tmpRng.move('character', offset) * -1;
12898
12899                                                 if (!tmpRng.collapsed) {
12900                                                         tmpRng = rng.duplicate();
12901                                                         tmpRng.collapse(false);
12902                                                         endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
12903                                                 }
12904                                         }
12905
12906                                         // Wrap non block elements and text nodes
12907                                         for (node = rootNode.firstChild; node; node) {
12908                                                 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
12909                                                         if (!rootBlockNode) {
12910                                                                 rootBlockNode = dom.create(s.forced_root_block);
12911                                                                 node.parentNode.insertBefore(rootBlockNode, node);
12912                                                         }
12913
12914                                                         tempNode = node;
12915                                                         node = node.nextSibling;
12916                                                         rootBlockNode.appendChild(tempNode);
12917                                                 } else {
12918                                                         rootBlockNode = null;
12919                                                         node = node.nextSibling;
12920                                                 }
12921                                         }
12922
12923                                         if (rng.setStart) {
12924                                                 rng.setStart(startContainer, startOffset);
12925                                                 rng.setEnd(endContainer, endOffset);
12926                                                 selection.setRng(rng);
12927                                         } else {
12928                                                 try {
12929                                                         rng = ed.getDoc().body.createTextRange();
12930                                                         rng.moveToElementText(rootNode);
12931                                                         rng.collapse(true);
12932                                                         rng.moveStart('character', startOffset);
12933
12934                                                         if (endOffset > 0)
12935                                                                 rng.moveEnd('character', endOffset);
12936
12937                                                         rng.select();
12938                                                 } catch (ex) {
12939                                                         // Ignore
12940                                                 }
12941                                         }
12942
12943                                         ed.nodeChanged();
12944                                 };
12945
12946                                 ed.onKeyUp.add(addRootBlocks);
12947                                 ed.onClick.add(addRootBlocks);
12948                         }
12949
12950                         if (s.force_br_newlines) {
12951                                 // Force IE to produce BRs on enter
12952                                 if (isIE) {
12953                                         ed.onKeyPress.add(function(ed, e) {
12954                                                 var n;
12955
12956                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
12957                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});
12958                                                         n = dom.get('__');
12959                                                         n.removeAttribute('id');
12960                                                         selection.select(n);
12961                                                         selection.collapse();
12962                                                         return Event.cancel(e);
12963                                                 }
12964                                         });
12965                                 }
12966                         }
12967
12968                         if (s.force_p_newlines) {
12969                                 if (!isIE) {
12970                                         ed.onKeyPress.add(function(ed, e) {
12971                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
12972                                                         Event.cancel(e);
12973                                         });
12974                                 } else {
12975                                         // Ungly hack to for IE to preserve the formatting when you press
12976                                         // enter at the end of a block element with formatted contents
12977                                         // This logic overrides the browsers default logic with
12978                                         // custom logic that enables us to control the output
12979                                         tinymce.addUnload(function() {
12980                                                 t._previousFormats = 0; // Fix IE leak
12981                                         });
12982
12983                                         ed.onKeyPress.add(function(ed, e) {
12984                                                 t._previousFormats = 0;
12985
12986                                                 // Clone the current formats, this will later be applied to the new block contents
12987                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
12988                                                         t._previousFormats = cloneFormats(ed.selection.getStart());
12989                                         });
12990
12991                                         ed.onKeyUp.add(function(ed, e) {
12992                                                 // Let IE break the element and the wrap the new caret location in the previous formats
12993                                                 if (e.keyCode == 13 && !e.shiftKey) {
12994                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;
12995
12996                                                         // Parent is an empty block
12997                                                         if (!parent.hasChildNodes() && fmt) {
12998                                                                 parent = dom.getParent(parent, dom.isBlock);
12999
13000                                                                 if (parent && parent.nodeName != 'LI') {
13001                                                                         parent.innerHTML = '';
13002
13003                                                                         if (t._previousFormats) {
13004                                                                                 parent.appendChild(fmt.wrapper);
13005                                                                                 fmt.inner.innerHTML = '\uFEFF';
13006                                                                         } else
13007                                                                                 parent.innerHTML = '\uFEFF';
13008
13009                                                                         selection.select(parent, 1);
13010                                                                         selection.collapse(true);
13011                                                                         ed.getDoc().execCommand('Delete', false, null);
13012                                                                         t._previousFormats = 0;
13013                                                                 }
13014                                                         }
13015                                                 }
13016                                         });
13017                                 }
13018
13019                                 if (isGecko) {
13020                                         ed.onKeyDown.add(function(ed, e) {
13021                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
13022                                                         t.backspaceDelete(e, e.keyCode == 8);
13023                                         });
13024                                 }
13025                         }
13026
13027                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
13028                         if (tinymce.isWebKit) {
13029                                 function insertBr(ed) {
13030                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
13031
13032                                         // Insert BR element
13033                                         rng.insertNode(br = dom.create('br'));
13034
13035                                         // Place caret after BR
13036                                         rng.setStartAfter(br);
13037                                         rng.setEndAfter(br);
13038                                         selection.setRng(rng);
13039
13040                                         // Could not place caret after BR then insert an nbsp entity and move the caret
13041                                         if (selection.getSel().focusNode == br.previousSibling) {
13042                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
13043                                                 selection.collapse(TRUE);
13044                                         }
13045
13046                                         // Create a temporary DIV after the BR and get the position as it
13047                                         // seems like getPos() returns 0 for text nodes and BR elements.
13048                                         dom.insertAfter(div, br);
13049                                         divYPos = dom.getPos(div).y;
13050                                         dom.remove(div);
13051
13052                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
13053                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
13054                                                 ed.getWin().scrollTo(0, divYPos);
13055                                 };
13056
13057                                 ed.onKeyPress.add(function(ed, e) {
13058                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
13059                                                 insertBr(ed);
13060                                                 Event.cancel(e);
13061                                         }
13062                                 });
13063                         }
13064
13065                         // IE specific fixes
13066                         if (isIE) {
13067                                 // Replaces IE:s auto generated paragraphs with the specified element name
13068                                 if (s.element != 'P') {
13069                                         ed.onKeyPress.add(function(ed, e) {
13070                                                 t.lastElm = selection.getNode().nodeName;
13071                                         });
13072
13073                                         ed.onKeyUp.add(function(ed, e) {
13074                                                 var bl, n = selection.getNode(), b = ed.getBody();
13075
13076                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {
13077                                                         n = dom.rename(n, s.element);
13078                                                         selection.select(n);
13079                                                         selection.collapse();
13080                                                         ed.nodeChanged();
13081                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
13082                                                         bl = dom.getParent(n, 'p');
13083
13084                                                         if (bl) {
13085                                                                 dom.rename(bl, s.element);
13086                                                                 ed.nodeChanged();
13087                                                         }
13088                                                 }
13089                                         });
13090                                 }
13091                         }
13092                 },
13093
13094                 getParentBlock : function(n) {
13095                         var d = this.dom;
13096
13097                         return d.getParent(n, d.isBlock);
13098                 },
13099
13100                 insertPara : function(e) {
13101                         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;
13102                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
13103
13104                         ed.undoManager.beforeChange();
13105
13106                         // If root blocks are forced then use Operas default behavior since it's really good
13107 // Removed due to bug: #1853816
13108 //                      if (se.forced_root_block && isOpera)
13109 //                              return TRUE;
13110
13111                         // Setup before range
13112                         rb = d.createRange();
13113
13114                         // If is before the first block element and in body, then move it into first block element
13115                         rb.setStart(s.anchorNode, s.anchorOffset);
13116                         rb.collapse(TRUE);
13117
13118                         // Setup after range
13119                         ra = d.createRange();
13120
13121                         // If is before the first block element and in body, then move it into first block element
13122                         ra.setStart(s.focusNode, s.focusOffset);
13123                         ra.collapse(TRUE);
13124
13125                         // Setup start/end points
13126                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
13127                         sn = dir ? s.anchorNode : s.focusNode;
13128                         so = dir ? s.anchorOffset : s.focusOffset;
13129                         en = dir ? s.focusNode : s.anchorNode;
13130                         eo = dir ? s.focusOffset : s.anchorOffset;
13131
13132                         // If selection is in empty table cell
13133                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
13134                                 if (sn.firstChild.nodeName == 'BR')
13135                                         dom.remove(sn.firstChild); // Remove BR
13136
13137                                 // Create two new block elements
13138                                 if (sn.childNodes.length == 0) {
13139                                         ed.dom.add(sn, se.element, null, '<br />');
13140                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13141                                 } else {
13142                                         n = sn.innerHTML;
13143                                         sn.innerHTML = '';
13144                                         ed.dom.add(sn, se.element, null, n);
13145                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13146                                 }
13147
13148                                 // Move caret into the last one
13149                                 r = d.createRange();
13150                                 r.selectNodeContents(aft);
13151                                 r.collapse(1);
13152                                 ed.selection.setRng(r);
13153
13154                                 return FALSE;
13155                         }
13156
13157                         // If the caret is in an invalid location in FF we need to move it into the first block
13158                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
13159                                 sn = en = sn.firstChild;
13160                                 so = eo = 0;
13161                                 rb = d.createRange();
13162                                 rb.setStart(sn, 0);
13163                                 ra = d.createRange();
13164                                 ra.setStart(en, 0);
13165                         }
13166
13167                         // Never use body as start or end node
13168                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13169                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
13170                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13171                         en = en.nodeName == "BODY" ? en.firstChild : en;
13172
13173                         // Get start and end blocks
13174                         sb = t.getParentBlock(sn);
13175                         eb = t.getParentBlock(en);
13176                         bn = sb ? sb.nodeName : se.element; // Get block name to create
13177
13178                         // Return inside list use default browser behavior
13179                         if (n = t.dom.getParent(sb, 'li,pre')) {
13180                                 if (n.nodeName == 'LI')
13181                                         return splitList(ed.selection, t.dom, n);
13182
13183                                 return TRUE;
13184                         }
13185
13186                         // If caption or absolute layers then always generate new blocks within
13187                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13188                                 bn = se.element;
13189                                 sb = null;
13190                         }
13191
13192                         // If caption or absolute layers then always generate new blocks within
13193                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13194                                 bn = se.element;
13195                                 eb = null;
13196                         }
13197
13198                         // Use P instead
13199                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
13200                                 bn = se.element;
13201                                 sb = eb = null;
13202                         }
13203
13204                         // Setup new before and after blocks
13205                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
13206                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
13207
13208                         // Remove id from after clone
13209                         aft.removeAttribute('id');
13210
13211                         // Is header and cursor is at the end, then force paragraph under
13212                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
13213                                 aft = ed.dom.create(se.element);
13214
13215                         // Find start chop node
13216                         n = sc = sn;
13217                         do {
13218                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13219                                         break;
13220
13221                                 sc = n;
13222                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
13223
13224                         // Find end chop node
13225                         n = ec = en;
13226                         do {
13227                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13228                                         break;
13229
13230                                 ec = n;
13231                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
13232
13233                         // Place first chop part into before block element
13234                         if (sc.nodeName == bn)
13235                                 rb.setStart(sc, 0);
13236                         else
13237                                 rb.setStartBefore(sc);
13238
13239                         rb.setEnd(sn, so);
13240                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13241
13242                         // Place secnd chop part within new block element
13243                         try {
13244                                 ra.setEndAfter(ec);
13245                         } catch(ex) {
13246                                 //console.debug(s.focusNode, s.focusOffset);
13247                         }
13248
13249                         ra.setStart(en, eo);
13250                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13251
13252                         // Create range around everything
13253                         r = d.createRange();
13254                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
13255                                 r.setStartBefore(sc.parentNode);
13256                         } else {
13257                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
13258                                         r.setStartBefore(rb.startContainer);
13259                                 else
13260                                         r.setStart(rb.startContainer, rb.startOffset);
13261                         }
13262
13263                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)
13264                                 r.setEndAfter(ec.parentNode);
13265                         else
13266                                 r.setEnd(ra.endContainer, ra.endOffset);
13267
13268                         // Delete and replace it with new block elements
13269                         r.deleteContents();
13270
13271                         if (isOpera)
13272                                 ed.getWin().scrollTo(0, vp.y);
13273
13274                         // Never wrap blocks in blocks
13275                         if (bef.firstChild && bef.firstChild.nodeName == bn)
13276                                 bef.innerHTML = bef.firstChild.innerHTML;
13277
13278                         if (aft.firstChild && aft.firstChild.nodeName == bn)
13279                                 aft.innerHTML = aft.firstChild.innerHTML;
13280
13281                         function appendStyles(e, en) {
13282                                 var nl = [], nn, n, i;
13283
13284                                 e.innerHTML = '';
13285
13286                                 // Make clones of style elements
13287                                 if (se.keep_styles) {
13288                                         n = en;
13289                                         do {
13290                                                 // We only want style specific elements
13291                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
13292                                                         nn = n.cloneNode(FALSE);
13293                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
13294                                                         nl.push(nn);
13295                                                 }
13296                                         } while (n = n.parentNode);
13297                                 }
13298
13299                                 // Append style elements to aft
13300                                 if (nl.length > 0) {
13301                                         for (i = nl.length - 1, nn = e; i >= 0; i--)
13302                                                 nn = nn.appendChild(nl[i]);
13303
13304                                         // Padd most inner style element
13305                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13306                                         return nl[0]; // Move caret to most inner element
13307                                 } else
13308                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13309                         };
13310                                 
13311                         // Padd empty blocks
13312                         if (dom.isEmpty(bef))
13313                                 appendStyles(bef, sn);
13314
13315                         // Fill empty afterblook with current style
13316                         if (dom.isEmpty(aft))
13317                                 car = appendStyles(aft, en);
13318
13319                         // Opera needs this one backwards for older versions
13320                         if (isOpera && parseFloat(opera.version()) < 9.5) {
13321                                 r.insertNode(bef);
13322                                 r.insertNode(aft);
13323                         } else {
13324                                 r.insertNode(aft);
13325                                 r.insertNode(bef);
13326                         }
13327
13328                         // Normalize
13329                         aft.normalize();
13330                         bef.normalize();
13331
13332                         // Move cursor and scroll into view
13333                         ed.selection.select(aft, true);
13334                         ed.selection.collapse(true);
13335
13336                         // 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
13337                         y = ed.dom.getPos(aft).y;
13338                         //ch = aft.clientHeight;
13339
13340                         // Is element within viewport
13341                         if (y < vp.y || y + 25 > vp.y + vp.h) {
13342                                 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
13343
13344                                 /*console.debug(
13345                                         'Element: y=' + y + ', h=' + ch + ', ' +
13346                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
13347                                 );*/
13348                         }
13349
13350                         ed.undoManager.add();
13351
13352                         return FALSE;
13353                 },
13354
13355                 backspaceDelete : function(e, bs) {
13356                         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;
13357
13358                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
13359                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
13360                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
13361
13362                                 // Walk the dom backwards until we find a text node
13363                                 for (n = sc.lastChild; n; n = walker.prev()) {
13364                                         if (n.nodeType == 3) {
13365                                                 r.setStart(n, n.nodeValue.length);
13366                                                 r.collapse(true);
13367                                                 se.setRng(r);
13368                                                 return;
13369                                         }
13370                                 }
13371                         }
13372
13373                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
13374                         // This workaround removes the element by hand and moves the caret to the previous element
13375                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
13376                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
13377                                         // Find previous block element
13378                                         n = sc;
13379                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
13380
13381                                         if (n) {
13382                                                 if (sc != b.firstChild) {
13383                                                         // Find last text node
13384                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
13385                                                         while (tn = w.nextNode())
13386                                                                 n = tn;
13387
13388                                                         // Place caret at the end of last text node
13389                                                         r = ed.getDoc().createRange();
13390                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
13391                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
13392                                                         se.setRng(r);
13393
13394                                                         // Remove the target container
13395                                                         ed.dom.remove(sc);
13396                                                 }
13397
13398                                                 return Event.cancel(e);
13399                                         }
13400                                 }
13401                         }
13402                 }
13403         });
13404 })(tinymce);
13405
13406 (function(tinymce) {
13407         // Shorten names
13408         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
13409
13410         tinymce.create('tinymce.ControlManager', {
13411                 ControlManager : function(ed, s) {
13412                         var t = this, i;
13413
13414                         s = s || {};
13415                         t.editor = ed;
13416                         t.controls = {};
13417                         t.onAdd = new tinymce.util.Dispatcher(t);
13418                         t.onPostRender = new tinymce.util.Dispatcher(t);
13419                         t.prefix = s.prefix || ed.id + '_';
13420                         t._cls = {};
13421
13422                         t.onPostRender.add(function() {
13423                                 each(t.controls, function(c) {
13424                                         c.postRender();
13425                                 });
13426                         });
13427                 },
13428
13429                 get : function(id) {
13430                         return this.controls[this.prefix + id] || this.controls[id];
13431                 },
13432
13433                 setActive : function(id, s) {
13434                         var c = null;
13435
13436                         if (c = this.get(id))
13437                                 c.setActive(s);
13438
13439                         return c;
13440                 },
13441
13442                 setDisabled : function(id, s) {
13443                         var c = null;
13444
13445                         if (c = this.get(id))
13446                                 c.setDisabled(s);
13447
13448                         return c;
13449                 },
13450
13451                 add : function(c) {
13452                         var t = this;
13453
13454                         if (c) {
13455                                 t.controls[c.id] = c;
13456                                 t.onAdd.dispatch(c, t);
13457                         }
13458
13459                         return c;
13460                 },
13461
13462                 createControl : function(n) {
13463                         var c, t = this, ed = t.editor;
13464
13465                         each(ed.plugins, function(p) {
13466                                 if (p.createControl) {
13467                                         c = p.createControl(n, t);
13468
13469                                         if (c)
13470                                                 return false;
13471                                 }
13472                         });
13473
13474                         switch (n) {
13475                                 case "|":
13476                                 case "separator":
13477                                         return t.createSeparator();
13478                         }
13479
13480                         if (!c && ed.buttons && (c = ed.buttons[n]))
13481                                 return t.createButton(n, c);
13482
13483                         return t.add(c);
13484                 },
13485
13486                 createDropMenu : function(id, s, cc) {
13487                         var t = this, ed = t.editor, c, bm, v, cls;
13488
13489                         s = extend({
13490                                 'class' : 'mceDropDown',
13491                                 constrain : ed.settings.constrain_menus
13492                         }, s);
13493
13494                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
13495                         if (v = ed.getParam('skin_variant'))
13496                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
13497
13498                         id = t.prefix + id;
13499                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
13500                         c = t.controls[id] = new cls(id, s);
13501                         c.onAddItem.add(function(c, o) {
13502                                 var s = o.settings;
13503
13504                                 s.title = ed.getLang(s.title, s.title);
13505
13506                                 if (!s.onclick) {
13507                                         s.onclick = function(v) {
13508                                                 if (s.cmd)
13509                                                         ed.execCommand(s.cmd, s.ui || false, s.value);
13510                                         };
13511                                 }
13512                         });
13513
13514                         ed.onRemove.add(function() {
13515                                 c.destroy();
13516                         });
13517
13518                         // Fix for bug #1897785, #1898007
13519                         if (tinymce.isIE) {
13520                                 c.onShowMenu.add(function() {
13521                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
13522                                         ed.focus();
13523
13524                                         bm = ed.selection.getBookmark(1);
13525                                 });
13526
13527                                 c.onHideMenu.add(function() {
13528                                         if (bm) {
13529                                                 ed.selection.moveToBookmark(bm);
13530                                                 bm = 0;
13531                                         }
13532                                 });
13533                         }
13534
13535                         return t.add(c);
13536                 },
13537
13538                 createListBox : function(id, s, cc) {
13539                         var t = this, ed = t.editor, cmd, c, cls;
13540
13541                         if (t.get(id))
13542                                 return null;
13543
13544                         s.title = ed.translate(s.title);
13545                         s.scope = s.scope || ed;
13546
13547                         if (!s.onselect) {
13548                                 s.onselect = function(v) {
13549                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13550                                 };
13551                         }
13552
13553                         s = extend({
13554                                 title : s.title,
13555                                 'class' : 'mce_' + id,
13556                                 scope : s.scope,
13557                                 control_manager : t
13558                         }, s);
13559
13560                         id = t.prefix + id;
13561
13562                         if (ed.settings.use_native_selects)
13563                                 c = new tinymce.ui.NativeListBox(id, s);
13564                         else {
13565                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
13566                                 c = new cls(id, s, ed);
13567                         }
13568
13569                         t.controls[id] = c;
13570
13571                         // Fix focus problem in Safari
13572                         if (tinymce.isWebKit) {
13573                                 c.onPostRender.add(function(c, n) {
13574                                         // Store bookmark on mousedown
13575                                         Event.add(n, 'mousedown', function() {
13576                                                 ed.bookmark = ed.selection.getBookmark(1);
13577                                         });
13578
13579                                         // Restore on focus, since it might be lost
13580                                         Event.add(n, 'focus', function() {
13581                                                 ed.selection.moveToBookmark(ed.bookmark);
13582                                                 ed.bookmark = null;
13583                                         });
13584                                 });
13585                         }
13586
13587                         if (c.hideMenu)
13588                                 ed.onMouseDown.add(c.hideMenu, c);
13589
13590                         return t.add(c);
13591                 },
13592
13593                 createButton : function(id, s, cc) {
13594                         var t = this, ed = t.editor, o, c, cls;
13595
13596                         if (t.get(id))
13597                                 return null;
13598
13599                         s.title = ed.translate(s.title);
13600                         s.label = ed.translate(s.label);
13601                         s.scope = s.scope || ed;
13602
13603                         if (!s.onclick && !s.menu_button) {
13604                                 s.onclick = function() {
13605                                         ed.execCommand(s.cmd, s.ui || false, s.value);
13606                                 };
13607                         }
13608
13609                         s = extend({
13610                                 title : s.title,
13611                                 'class' : 'mce_' + id,
13612                                 unavailable_prefix : ed.getLang('unavailable', ''),
13613                                 scope : s.scope,
13614                                 control_manager : t
13615                         }, s);
13616
13617                         id = t.prefix + id;
13618
13619                         if (s.menu_button) {
13620                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
13621                                 c = new cls(id, s, ed);
13622                                 ed.onMouseDown.add(c.hideMenu, c);
13623                         } else {
13624                                 cls = t._cls.button || tinymce.ui.Button;
13625                                 c = new cls(id, s, ed);
13626                         }
13627
13628                         return t.add(c);
13629                 },
13630
13631                 createMenuButton : function(id, s, cc) {
13632                         s = s || {};
13633                         s.menu_button = 1;
13634
13635                         return this.createButton(id, s, cc);
13636                 },
13637
13638                 createSplitButton : function(id, s, cc) {
13639                         var t = this, ed = t.editor, cmd, c, cls;
13640
13641                         if (t.get(id))
13642                                 return null;
13643
13644                         s.title = ed.translate(s.title);
13645                         s.scope = s.scope || ed;
13646
13647                         if (!s.onclick) {
13648                                 s.onclick = function(v) {
13649                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13650                                 };
13651                         }
13652
13653                         if (!s.onselect) {
13654                                 s.onselect = function(v) {
13655                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13656                                 };
13657                         }
13658
13659                         s = extend({
13660                                 title : s.title,
13661                                 'class' : 'mce_' + id,
13662                                 scope : s.scope,
13663                                 control_manager : t
13664                         }, s);
13665
13666                         id = t.prefix + id;
13667                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
13668                         c = t.add(new cls(id, s, ed));
13669                         ed.onMouseDown.add(c.hideMenu, c);
13670
13671                         return c;
13672                 },
13673
13674                 createColorSplitButton : function(id, s, cc) {
13675                         var t = this, ed = t.editor, cmd, c, cls, bm;
13676
13677                         if (t.get(id))
13678                                 return null;
13679
13680                         s.title = ed.translate(s.title);
13681                         s.scope = s.scope || ed;
13682
13683                         if (!s.onclick) {
13684                                 s.onclick = function(v) {
13685                                         if (tinymce.isIE)
13686                                                 bm = ed.selection.getBookmark(1);
13687
13688                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13689                                 };
13690                         }
13691
13692                         if (!s.onselect) {
13693                                 s.onselect = function(v) {
13694                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13695                                 };
13696                         }
13697
13698                         s = extend({
13699                                 title : s.title,
13700                                 'class' : 'mce_' + id,
13701                                 'menu_class' : ed.getParam('skin') + 'Skin',
13702                                 scope : s.scope,
13703                                 more_colors_title : ed.getLang('more_colors')
13704                         }, s);
13705
13706                         id = t.prefix + id;
13707                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
13708                         c = new cls(id, s, ed);
13709                         ed.onMouseDown.add(c.hideMenu, c);
13710
13711                         // Remove the menu element when the editor is removed
13712                         ed.onRemove.add(function() {
13713                                 c.destroy();
13714                         });
13715
13716                         // Fix for bug #1897785, #1898007
13717                         if (tinymce.isIE) {
13718                                 c.onShowMenu.add(function() {
13719                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
13720                                         ed.focus();
13721                                         bm = ed.selection.getBookmark(1);
13722                                 });
13723
13724                                 c.onHideMenu.add(function() {
13725                                         if (bm) {
13726                                                 ed.selection.moveToBookmark(bm);
13727                                                 bm = 0;
13728                                         }
13729                                 });
13730                         }
13731
13732                         return t.add(c);
13733                 },
13734
13735                 createToolbar : function(id, s, cc) {
13736                         var c, t = this, cls;
13737
13738                         id = t.prefix + id;
13739                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
13740                         c = new cls(id, s, t.editor);
13741
13742                         if (t.get(id))
13743                                 return null;
13744
13745                         return t.add(c);
13746                 },
13747                 
13748                 createToolbarGroup : function(id, s, cc) {
13749                         var c, t = this, cls;
13750                         id = t.prefix + id;
13751                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
13752                         c = new cls(id, s, t.editor);
13753                         
13754                         if (t.get(id))
13755                                 return null;
13756                         
13757                         return t.add(c);
13758                 },
13759
13760                 createSeparator : function(cc) {
13761                         var cls = cc || this._cls.separator || tinymce.ui.Separator;
13762
13763                         return new cls();
13764                 },
13765
13766                 setControlType : function(n, c) {
13767                         return this._cls[n.toLowerCase()] = c;
13768                 },
13769         
13770                 destroy : function() {
13771                         each(this.controls, function(c) {
13772                                 c.destroy();
13773                         });
13774
13775                         this.controls = null;
13776                 }
13777         });
13778 })(tinymce);
13779
13780 (function(tinymce) {
13781         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
13782
13783         tinymce.create('tinymce.WindowManager', {
13784                 WindowManager : function(ed) {
13785                         var t = this;
13786
13787                         t.editor = ed;
13788                         t.onOpen = new Dispatcher(t);
13789                         t.onClose = new Dispatcher(t);
13790                         t.params = {};
13791                         t.features = {};
13792                 },
13793
13794                 open : function(s, p) {
13795                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
13796
13797                         // Default some options
13798                         s = s || {};
13799                         p = p || {};
13800                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
13801                         sh = isOpera ? vp.h : screen.height;
13802                         s.name = s.name || 'mc_' + new Date().getTime();
13803                         s.width = parseInt(s.width || 320);
13804                         s.height = parseInt(s.height || 240);
13805                         s.resizable = true;
13806                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
13807                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
13808                         p.inline = false;
13809                         p.mce_width = s.width;
13810                         p.mce_height = s.height;
13811                         p.mce_auto_focus = s.auto_focus;
13812
13813                         if (mo) {
13814                                 if (isIE) {
13815                                         s.center = true;
13816                                         s.help = false;
13817                                         s.dialogWidth = s.width + 'px';
13818                                         s.dialogHeight = s.height + 'px';
13819                                         s.scroll = s.scrollbars || false;
13820                                 }
13821                         }
13822
13823                         // Build features string
13824                         each(s, function(v, k) {
13825                                 if (tinymce.is(v, 'boolean'))
13826                                         v = v ? 'yes' : 'no';
13827
13828                                 if (!/^(name|url)$/.test(k)) {
13829                                         if (isIE && mo)
13830                                                 f += (f ? ';' : '') + k + ':' + v;
13831                                         else
13832                                                 f += (f ? ',' : '') + k + '=' + v;
13833                                 }
13834                         });
13835
13836                         t.features = s;
13837                         t.params = p;
13838                         t.onOpen.dispatch(t, s, p);
13839
13840                         u = s.url || s.file;
13841                         u = tinymce._addVer(u);
13842
13843                         try {
13844                                 if (isIE && mo) {
13845                                         w = 1;
13846                                         window.showModalDialog(u, window, f);
13847                                 } else
13848                                         w = window.open(u, s.name, f);
13849                         } catch (ex) {
13850                                 // Ignore
13851                         }
13852
13853                         if (!w)
13854                                 alert(t.editor.getLang('popup_blocked'));
13855                 },
13856
13857                 close : function(w) {
13858                         w.close();
13859                         this.onClose.dispatch(this);
13860                 },
13861
13862                 createInstance : function(cl, a, b, c, d, e) {
13863                         var f = tinymce.resolve(cl);
13864
13865                         return new f(a, b, c, d, e);
13866                 },
13867
13868                 confirm : function(t, cb, s, w) {
13869                         w = w || window;
13870
13871                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
13872                 },
13873
13874                 alert : function(tx, cb, s, w) {
13875                         var t = this;
13876
13877                         w = w || window;
13878                         w.alert(t._decode(t.editor.getLang(tx, tx)));
13879
13880                         if (cb)
13881                                 cb.call(s || t);
13882                 },
13883
13884                 resizeBy : function(dw, dh, win) {
13885                         win.resizeBy(dw, dh);
13886                 },
13887
13888                 // Internal functions
13889
13890                 _decode : function(s) {
13891                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
13892                 }
13893         });
13894 }(tinymce));
13895 (function(tinymce) {
13896         tinymce.Formatter = function(ed) {
13897                 var formats = {},
13898                         each = tinymce.each,
13899                         dom = ed.dom,
13900                         selection = ed.selection,
13901                         TreeWalker = tinymce.dom.TreeWalker,
13902                         rangeUtils = new tinymce.dom.RangeUtils(dom),
13903                         isValid = ed.schema.isValidChild,
13904                         isBlock = dom.isBlock,
13905                         forcedRootBlock = ed.settings.forced_root_block,
13906                         nodeIndex = dom.nodeIndex,
13907                         INVISIBLE_CHAR = '\uFEFF',
13908                         MCE_ATTR_RE = /^(src|href|style)$/,
13909                         FALSE = false,
13910                         TRUE = true,
13911                         undefined,
13912                         pendingFormats = {apply : [], remove : []};
13913
13914                 function isArray(obj) {
13915                         return obj instanceof Array;
13916                 };
13917
13918                 function getParents(node, selector) {
13919                         return dom.getParents(node, selector, dom.getRoot());
13920                 };
13921
13922                 function isCaretNode(node) {
13923                         return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
13924                 };
13925
13926                 // Public functions
13927
13928                 function get(name) {
13929                         return name ? formats[name] : formats;
13930                 };
13931
13932                 function register(name, format) {
13933                         if (name) {
13934                                 if (typeof(name) !== 'string') {
13935                                         each(name, function(format, name) {
13936                                                 register(name, format);
13937                                         });
13938                                 } else {
13939                                         // Force format into array and add it to internal collection
13940                                         format = format.length ? format : [format];
13941
13942                                         each(format, function(format) {
13943                                                 // Set deep to false by default on selector formats this to avoid removing
13944                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs
13945                                                 if (format.deep === undefined)
13946                                                         format.deep = !format.selector;
13947
13948                                                 // Default to true
13949                                                 if (format.split === undefined)
13950                                                         format.split = !format.selector || format.inline;
13951
13952                                                 // Default to true
13953                                                 if (format.remove === undefined && format.selector && !format.inline)
13954                                                         format.remove = 'none';
13955
13956                                                 // Mark format as a mixed format inline + block level
13957                                                 if (format.selector && format.inline) {
13958                                                         format.mixed = true;
13959                                                         format.block_expand = true;
13960                                                 }
13961
13962                                                 // Split classes if needed
13963                                                 if (typeof(format.classes) === 'string')
13964                                                         format.classes = format.classes.split(/\s+/);
13965                                         });
13966
13967                                         formats[name] = format;
13968                                 }
13969                         }
13970                 };
13971
13972                 var getTextDecoration = function(node) {
13973                         var decoration;
13974
13975                         ed.dom.getParent(node, function(n) {
13976                                 decoration = ed.dom.getStyle(n, 'text-decoration');
13977                                 return decoration && decoration !== 'none';
13978                         });
13979
13980                         return decoration;
13981                 };
13982
13983                 var processUnderlineAndColor = function(node) {
13984                         var textDecoration;
13985                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
13986                                 textDecoration = getTextDecoration(node.parentNode);
13987                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
13988                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);
13989                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
13990                                         ed.dom.setStyle(node, 'text-decoration', null);
13991                                 }
13992                         }
13993                 };
13994
13995                 function apply(name, vars, node) {
13996                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
13997
13998                         function moveStart(rng) {
13999                                 var container = rng.startContainer,
14000                                         offset = rng.startOffset,
14001                                         walker, node;
14002
14003                                 // Move startContainer/startOffset in to a suitable node
14004                                 if (container.nodeType == 1 || container.nodeValue === "") {
14005                                         container = container.nodeType == 1 ? container.childNodes[offset] : container;
14006
14007                                         // Might fail if the offset is behind the last element in it's container
14008                                         if (container) {
14009                                                 walker = new TreeWalker(container, container.parentNode);
14010                                                 for (node = walker.current(); node; node = walker.next()) {
14011                                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14012                                                                 rng.setStart(node, 0);
14013                                                                 break;
14014                                                         }
14015                                                 }
14016                                         }
14017                                 }
14018
14019                                 return rng;
14020                         };
14021
14022                         function setElementFormat(elm, fmt) {
14023                                 fmt = fmt || format;
14024
14025                                 if (elm) {
14026                                         each(fmt.styles, function(value, name) {
14027                                                 dom.setStyle(elm, name, replaceVars(value, vars));
14028                                         });
14029
14030                                         each(fmt.attributes, function(value, name) {
14031                                                 dom.setAttrib(elm, name, replaceVars(value, vars));
14032                                         });
14033
14034                                         each(fmt.classes, function(value) {
14035                                                 value = replaceVars(value, vars);
14036
14037                                                 if (!dom.hasClass(elm, value))
14038                                                         dom.addClass(elm, value);
14039                                         });
14040                                 }
14041                         };
14042                         function adjustSelectionToVisibleSelection() {
14043
14044                                 function findSelectionEnd(start, end) {
14045                                         var walker = new TreeWalker(end);
14046                                         for (node = walker.current(); node; node = walker.prev()) {
14047                                                 if (node.childNodes.length > 1 || node == start) {
14048                                                         return node;
14049                                                 }
14050                                         }
14051                                 }
14052
14053                                 // Adjust selection so that a end container with a end offset of zero is not included in the selection
14054                                 // as this isn't visible to the user.
14055                                 var rng = ed.selection.getRng();
14056                                 var start = rng.startContainer;
14057                                 var end = rng.endContainer;
14058                                 if (start != end && rng.endOffset == 0) {
14059                                         var newEnd = findSelectionEnd(start, end);
14060                                         var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
14061                                         rng.setEnd(newEnd, endOffset);
14062                                 }
14063                                 return rng;
14064                         }
14065                         
14066                         function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
14067                                 var nodes =[], listIndex =-1, list, startIndex = -1, endIndex = -1, currentWrapElm;
14068                                 
14069                                 // find the index of the first child list.
14070                                 each(node.childNodes, function(n, index) {
14071                                         if (n.nodeName==="UL"||n.nodeName==="OL") {listIndex = index; list=n; return false; }
14072                                 });
14073                                 
14074                                 // get the index of the bookmarks
14075                                 each(node.childNodes, function(n, index) {
14076                                         if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_start") {startIndex=index}
14077                                         if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_end") {endIndex=index}
14078                                 });
14079                                 
14080                                 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
14081                                 if (listIndex<=0 || (startIndex<listIndex&&endIndex>listIndex)) {
14082                                         each(tinymce.grep(node.childNodes), process);
14083                                         return 0;
14084                                 } else {
14085                                         currentWrapElm = wrapElm.cloneNode(FALSE);
14086                                         
14087                                         // create a list of the nodes on the same side of the list as the selection
14088                                         each(tinymce.grep(node.childNodes), function(n, index) {
14089                                                 if ((startIndex<listIndex && index <listIndex) || (startIndex>listIndex && index >listIndex)) {
14090                                                         nodes.push(n); 
14091                                                         n.parentNode.removeChild(n); 
14092                                                 }
14093                                         });
14094                                         
14095                                         // insert the wrapping element either before or after the list.
14096                                         if (startIndex<listIndex) {
14097                                                 node.insertBefore(currentWrapElm, list);
14098                                         } else if (startIndex>listIndex) {
14099                                                 node.insertBefore(currentWrapElm, list.nextSibling);
14100                                         }
14101                                         
14102                                         // add the new nodes to the list.
14103                                         newWrappers.push(currentWrapElm);
14104                                         each(nodes, function(node){currentWrapElm.appendChild(node)});
14105                                         return currentWrapElm;
14106                                 }
14107                         };
14108                         
14109                         function applyRngStyle(rng, bookmark) {
14110                                 var newWrappers = [], wrapName, wrapElm;
14111
14112                                 // Setup wrapper element
14113                                 wrapName = format.inline || format.block;
14114                                 wrapElm = dom.create(wrapName);
14115                                 setElementFormat(wrapElm);
14116
14117                                 rangeUtils.walk(rng, function(nodes) {
14118                                         var currentWrapElm;
14119
14120                                         function process(node) {
14121                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
14122
14123                                                 // Stop wrapping on br elements
14124                                                 if (isEq(nodeName, 'br')) {
14125                                                         currentWrapElm = 0;
14126
14127                                                         // Remove any br elements when we wrap things
14128                                                         if (format.block)
14129                                                                 dom.remove(node);
14130
14131                                                         return;
14132                                                 }
14133
14134                                                 // If node is wrapper type
14135                                                 if (format.wrapper && matchNode(node, name, vars)) {
14136                                                         currentWrapElm = 0;
14137                                                         return;
14138                                                 }
14139
14140                                                 // Can we rename the block
14141                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
14142                                                         node = dom.rename(node, wrapName);
14143                                                         setElementFormat(node);
14144                                                         newWrappers.push(node);
14145                                                         currentWrapElm = 0;
14146                                                         return;
14147                                                 }
14148
14149                                                 // Handle selector patterns
14150                                                 if (format.selector) {
14151                                                         // Look for matching formats
14152                                                         each(formatList, function(format) {
14153                                                                 // Check collapsed state if it exists
14154                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
14155                                                                         return;
14156                                                                 }
14157
14158                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
14159                                                                         setElementFormat(node, format);
14160                                                                         found = true;
14161                                                                 }
14162                                                         });
14163
14164                                                         // Continue processing if a selector match wasn't found and a inline element is defined
14165                                                         if (!format.inline || found) {
14166                                                                 currentWrapElm = 0;
14167                                                                 return;
14168                                                         }
14169                                                 }
14170
14171                                                 // Is it valid to wrap this item
14172                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
14173                                                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
14174                                                         // Start wrapping
14175                                                         if (!currentWrapElm) {
14176                                                                 // Wrap the node
14177                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14178                                                                 node.parentNode.insertBefore(currentWrapElm, node);
14179                                                                 newWrappers.push(currentWrapElm);
14180                                                         }
14181
14182                                                         currentWrapElm.appendChild(node);
14183                                                 } else if (nodeName == 'li' && bookmark) {
14184                                                         // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
14185                                                         currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
14186                                                 } else {
14187                                                         // Start a new wrapper for possible children
14188                                                         currentWrapElm = 0;
14189
14190                                                         each(tinymce.grep(node.childNodes), process);
14191
14192                                                         // End the last wrapper
14193                                                         currentWrapElm = 0;
14194                                                 }
14195                                         };
14196
14197                                         // Process siblings from range
14198                                         each(nodes, process);
14199                                 });
14200
14201                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
14202                                 if (format.wrap_links === false) {
14203                                         each(newWrappers, function(node) {
14204                                                 function process(node) {
14205                                                         var i, currentWrapElm, children;
14206
14207                                                         if (node.nodeName === 'A') {
14208                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14209                                                                 newWrappers.push(currentWrapElm);
14210
14211                                                                 children = tinymce.grep(node.childNodes);
14212                                                                 for (i = 0; i < children.length; i++)
14213                                                                         currentWrapElm.appendChild(children[i]);
14214
14215                                                                 node.appendChild(currentWrapElm);
14216                                                         }
14217
14218                                                         each(tinymce.grep(node.childNodes), process);
14219                                                 };
14220
14221                                                 process(node);
14222                                         });
14223                                 }
14224
14225                                 // Cleanup
14226                                 each(newWrappers, function(node) {
14227                                         var childCount;
14228
14229                                         function getChildCount(node) {
14230                                                 var count = 0;
14231
14232                                                 each(node.childNodes, function(node) {
14233                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
14234                                                                 count++;
14235                                                 });
14236
14237                                                 return count;
14238                                         };
14239
14240                                         function mergeStyles(node) {
14241                                                 var child, clone;
14242
14243                                                 each(node.childNodes, function(node) {
14244                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
14245                                                                 child = node;
14246                                                                 return FALSE; // break loop
14247                                                         }
14248                                                 });
14249
14250                                                 // If child was found and of the same type as the current node
14251                                                 if (child && matchName(child, format)) {
14252                                                         clone = child.cloneNode(FALSE);
14253                                                         setElementFormat(clone);
14254
14255                                                         dom.replace(clone, node, TRUE);
14256                                                         dom.remove(child, 1);
14257                                                 }
14258
14259                                                 return clone || node;
14260                                         };
14261
14262                                         childCount = getChildCount(node);
14263
14264                                         // Remove empty nodes but only if there is multiple wrappers and they are not block
14265                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
14266                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
14267                                                 dom.remove(node, 1);
14268                                                 return;
14269                                         }
14270
14271                                         if (format.inline || format.wrapper) {
14272                                                 // Merges the current node with it's children of similar type to reduce the number of elements
14273                                                 if (!format.exact && childCount === 1)
14274                                                         node = mergeStyles(node);
14275
14276                                                 // Remove/merge children
14277                                                 each(formatList, function(format) {
14278                                                         // Merge all children of similar type will move styles from child to parent
14279                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
14280                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
14281                                                         each(dom.select(format.inline, node), function(child) {
14282                                                                 var parent;
14283
14284                                                                 // When wrap_links is set to false we don't want
14285                                                                 // to remove the format on children within links
14286                                                                 if (format.wrap_links === false) {
14287                                                                         parent = child.parentNode;
14288
14289                                                                         do {
14290                                                                                 if (parent.nodeName === 'A')
14291                                                                                         return;
14292                                                                         } while (parent = parent.parentNode);
14293                                                                 }
14294
14295                                                                 removeFormat(format, vars, child, format.exact ? child : null);
14296                                                         });
14297                                                 });
14298
14299                                                 // Remove child if direct parent is of same type
14300                                                 if (matchNode(node.parentNode, name, vars)) {
14301                                                         dom.remove(node, 1);
14302                                                         node = 0;
14303                                                         return TRUE;
14304                                                 }
14305
14306                                                 // Look for parent with similar style format
14307                                                 if (format.merge_with_parents) {
14308                                                         dom.getParent(node.parentNode, function(parent) {
14309                                                                 if (matchNode(parent, name, vars)) {
14310                                                                         dom.remove(node, 1);
14311                                                                         node = 0;
14312                                                                         return TRUE;
14313                                                                 }
14314                                                         });
14315                                                 }
14316
14317                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
14318                                                 if (node) {
14319                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
14320                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
14321                                                 }
14322                                         }
14323                                 });
14324                         };
14325
14326                         if (format) {
14327                                 if (node) {
14328                                         rng = dom.createRng();
14329
14330                                         rng.setStartBefore(node);
14331                                         rng.setEndAfter(node);
14332
14333                                         applyRngStyle(expandRng(rng, formatList));
14334                                 } else {
14335                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14336                                                 // Obtain selection node before selection is unselected by applyRngStyle()
14337                                                 var curSelNode = ed.selection.getNode();
14338
14339                                                 // Apply formatting to selection
14340                                                 ed.selection.setRng(adjustSelectionToVisibleSelection());
14341                                                 bookmark = selection.getBookmark();
14342                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
14343
14344                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.
14345                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
14346                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
14347                                                         processUnderlineAndColor(curSelNode);
14348                                                 }
14349
14350                                                 selection.moveToBookmark(bookmark);
14351                                                 selection.setRng(moveStart(selection.getRng(TRUE)));
14352                                                 ed.nodeChanged();
14353                                         } else
14354                                                 performCaretAction('apply', name, vars);
14355                                 }
14356                         }
14357                 };
14358
14359                 function remove(name, vars, node) {
14360                         var formatList = get(name), format = formatList[0], bookmark, i, rng;
14361                         function moveStart(rng) {
14362                                 var container = rng.startContainer,
14363                                         offset = rng.startOffset,
14364                                         walker, node, nodes, tmpNode;
14365
14366                                 // Convert text node into index if possible
14367                                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
14368                                         container = container.parentNode;
14369                                         offset = nodeIndex(container) + 1;
14370                                 }
14371
14372                                 // Move startContainer/startOffset in to a suitable node
14373                                 if (container.nodeType == 1) {
14374                                         nodes = container.childNodes;
14375                                         container = nodes[Math.min(offset, nodes.length - 1)];
14376                                         walker = new TreeWalker(container);
14377
14378                                         // If offset is at end of the parent node walk to the next one
14379                                         if (offset > nodes.length - 1)
14380                                                 walker.next();
14381
14382                                         for (node = walker.current(); node; node = walker.next()) {
14383                                                 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14384                                                         // IE has a "neat" feature where it moves the start node into the closest element
14385                                                         // we can avoid this by inserting an element before it and then remove it after we set the selection
14386                                                         tmpNode = dom.create('a', null, INVISIBLE_CHAR);
14387                                                         node.parentNode.insertBefore(tmpNode, node);
14388
14389                                                         // Set selection and remove tmpNode
14390                                                         rng.setStart(node, 0);
14391                                                         selection.setRng(rng);
14392                                                         dom.remove(tmpNode);
14393
14394                                                         return;
14395                                                 }
14396                                         }
14397                                 }
14398                         };
14399
14400                         // Merges the styles for each node
14401                         function process(node) {
14402                                 var children, i, l;
14403
14404                                 // Grab the children first since the nodelist might be changed
14405                                 children = tinymce.grep(node.childNodes);
14406
14407                                 // Process current node
14408                                 for (i = 0, l = formatList.length; i < l; i++) {
14409                                         if (removeFormat(formatList[i], vars, node, node))
14410                                                 break;
14411                                 }
14412
14413                                 // Process the children
14414                                 if (format.deep) {
14415                                         for (i = 0, l = children.length; i < l; i++)
14416                                                 process(children[i]);
14417                                 }
14418                         };
14419
14420                         function findFormatRoot(container) {
14421                                 var formatRoot;
14422
14423                                 // Find format root
14424                                 each(getParents(container.parentNode).reverse(), function(parent) {
14425                                         var format;
14426
14427                                         // Find format root element
14428                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
14429                                                 // Is the node matching the format we are looking for
14430                                                 format = matchNode(parent, name, vars);
14431                                                 if (format && format.split !== false)
14432                                                         formatRoot = parent;
14433                                         }
14434                                 });
14435
14436                                 return formatRoot;
14437                         };
14438
14439                         function wrapAndSplit(format_root, container, target, split) {
14440                                 var parent, clone, lastClone, firstClone, i, formatRootParent;
14441
14442                                 // Format root found then clone formats and split it
14443                                 if (format_root) {
14444                                         formatRootParent = format_root.parentNode;
14445
14446                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14447                                                 clone = parent.cloneNode(FALSE);
14448
14449                                                 for (i = 0; i < formatList.length; i++) {
14450                                                         if (removeFormat(formatList[i], vars, clone, clone)) {
14451                                                                 clone = 0;
14452                                                                 break;
14453                                                         }
14454                                                 }
14455
14456                                                 // Build wrapper node
14457                                                 if (clone) {
14458                                                         if (lastClone)
14459                                                                 clone.appendChild(lastClone);
14460
14461                                                         if (!firstClone)
14462                                                                 firstClone = clone;
14463
14464                                                         lastClone = clone;
14465                                                 }
14466                                         }
14467
14468                                         // Never split block elements if the format is mixed
14469                                         if (split && (!format.mixed || !isBlock(format_root)))
14470                                                 container = dom.split(format_root, container);
14471
14472                                         // Wrap container in cloned formats
14473                                         if (lastClone) {
14474                                                 target.parentNode.insertBefore(lastClone, target);
14475                                                 firstClone.appendChild(target);
14476                                         }
14477                                 }
14478
14479                                 return container;
14480                         };
14481
14482                         function splitToFormatRoot(container) {
14483                                 return wrapAndSplit(findFormatRoot(container), container, container, true);
14484                         };
14485
14486                         function unwrap(start) {
14487                                 var node = dom.get(start ? '_start' : '_end'),
14488                                         out = node[start ? 'firstChild' : 'lastChild'];
14489
14490                                 // If the end is placed within the start the result will be removed
14491                                 // So this checks if the out node is a bookmark node if it is it
14492                                 // checks for another more suitable node
14493                                 if (isBookmarkNode(out))
14494                                         out = out[start ? 'firstChild' : 'lastChild'];
14495
14496                                 dom.remove(node, true);
14497
14498                                 return out;
14499                         };
14500
14501                         function removeRngStyle(rng) {
14502                                 var startContainer, endContainer;
14503
14504                                 rng = expandRng(rng, formatList, TRUE);
14505
14506                                 if (format.split) {
14507                                         startContainer = getContainer(rng, TRUE);
14508                                         endContainer = getContainer(rng);
14509
14510                                         if (startContainer != endContainer) {
14511                                                 // Wrap start/end nodes in span element since these might be cloned/moved
14512                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
14513                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
14514
14515                                                 // Split start/end
14516                                                 splitToFormatRoot(startContainer);
14517                                                 splitToFormatRoot(endContainer);
14518
14519                                                 // Unwrap start/end to get real elements again
14520                                                 startContainer = unwrap(TRUE);
14521                                                 endContainer = unwrap();
14522                                         } else
14523                                                 startContainer = endContainer = splitToFormatRoot(startContainer);
14524
14525                                         // Update range positions since they might have changed after the split operations
14526                                         rng.startContainer = startContainer.parentNode;
14527                                         rng.startOffset = nodeIndex(startContainer);
14528                                         rng.endContainer = endContainer.parentNode;
14529                                         rng.endOffset = nodeIndex(endContainer) + 1;
14530                                 }
14531
14532                                 // Remove items between start/end
14533                                 rangeUtils.walk(rng, function(nodes) {
14534                                         each(nodes, function(node) {
14535                                                 process(node);
14536
14537                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14538                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14539                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
14540                                                 }
14541                                         });
14542                                 });
14543                         };
14544
14545                         // Handle node
14546                         if (node) {
14547                                 rng = dom.createRng();
14548                                 rng.setStartBefore(node);
14549                                 rng.setEndAfter(node);
14550                                 removeRngStyle(rng);
14551                                 return;
14552                         }
14553
14554                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14555                                 bookmark = selection.getBookmark();
14556                                 removeRngStyle(selection.getRng(TRUE));
14557                                 selection.moveToBookmark(bookmark);
14558
14559                                 // 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
14560                                 if (match(name, vars, selection.getStart())) {
14561                                         moveStart(selection.getRng(true));
14562                                 }
14563
14564                                 ed.nodeChanged();
14565                         } else
14566                                 performCaretAction('remove', name, vars);
14567                 };
14568
14569                 function toggle(name, vars, node) {
14570                         var fmt = get(name);
14571
14572                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
14573                                 remove(name, vars, node);
14574                         else
14575                                 apply(name, vars, node);
14576                 };
14577
14578                 function matchNode(node, name, vars, similar) {
14579                         var formatList = get(name), format, i, classes;
14580
14581                         function matchItems(node, format, item_name) {
14582                                 var key, value, items = format[item_name], i;
14583
14584                                 // Check all items
14585                                 if (items) {
14586                                         // Non indexed object
14587                                         if (items.length === undefined) {
14588                                                 for (key in items) {
14589                                                         if (items.hasOwnProperty(key)) {
14590                                                                 if (item_name === 'attributes')
14591                                                                         value = dom.getAttrib(node, key);
14592                                                                 else
14593                                                                         value = getStyle(node, key);
14594
14595                                                                 if (similar && !value && !format.exact)
14596                                                                         return;
14597
14598                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
14599                                                                         return;
14600                                                         }
14601                                                 }
14602                                         } else {
14603                                                 // Only one match needed for indexed arrays
14604                                                 for (i = 0; i < items.length; i++) {
14605                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
14606                                                                 return format;
14607                                                 }
14608                                         }
14609                                 }
14610
14611                                 return format;
14612                         };
14613
14614                         if (formatList && node) {
14615                                 // Check each format in list
14616                                 for (i = 0; i < formatList.length; i++) {
14617                                         format = formatList[i];
14618
14619                                         // Name name, attributes, styles and classes
14620                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14621                                                 // Match classes
14622                                                 if (classes = format.classes) {
14623                                                         for (i = 0; i < classes.length; i++) {
14624                                                                 if (!dom.hasClass(node, classes[i]))
14625                                                                         return;
14626                                                         }
14627                                                 }
14628
14629                                                 return format;
14630                                         }
14631                                 }
14632                         }
14633                 };
14634
14635                 function match(name, vars, node) {
14636                         var startNode, i;
14637
14638                         function matchParents(node) {
14639                                 // Find first node with similar format settings
14640                                 node = dom.getParent(node, function(node) {
14641                                         return !!matchNode(node, name, vars, true);
14642                                 });
14643
14644                                 // Do an exact check on the similar format element
14645                                 return matchNode(node, name, vars);
14646                         };
14647
14648                         // Check specified node
14649                         if (node)
14650                                 return matchParents(node);
14651
14652                         // Check pending formats
14653                         if (selection.isCollapsed()) {
14654                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14655                                         if (pendingFormats.apply[i].name == name)
14656                                                 return true;
14657                                 }
14658
14659                                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14660                                         if (pendingFormats.remove[i].name == name)
14661                                                 return false;
14662                                 }
14663
14664                                 return matchParents(selection.getNode());
14665                         }
14666
14667                         // Check selected node
14668                         node = selection.getNode();
14669                         if (matchParents(node))
14670                                 return TRUE;
14671
14672                         // Check start node if it's different
14673                         startNode = selection.getStart();
14674                         if (startNode != node) {
14675                                 if (matchParents(startNode))
14676                                         return TRUE;
14677                         }
14678
14679                         return FALSE;
14680                 };
14681
14682                 function matchAll(names, vars) {
14683                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
14684
14685                         // If the selection is collapsed then check pending formats
14686                         if (selection.isCollapsed()) {
14687                                 for (ni = 0; ni < names.length; ni++) {
14688                                         // If the name is to be removed, then stop it from being added
14689                                         for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14690                                                 name = names[ni];
14691
14692                                                 if (pendingFormats.remove[i].name == name) {
14693                                                         checkedMap[name] = true;
14694                                                         break;
14695                                                 }
14696                                         }
14697                                 }
14698
14699                                 // If the format is to be applied
14700                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14701                                         for (ni = 0; ni < names.length; ni++) {
14702                                                 name = names[ni];
14703
14704                                                 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
14705                                                         checkedMap[name] = true;
14706                                                         matchedFormatNames.push(name);
14707                                                 }
14708                                         }
14709                                 }
14710                         }
14711
14712                         // Check start of selection for formats
14713                         startElement = selection.getStart();
14714                         dom.getParent(startElement, function(node) {
14715                                 var i, name;
14716
14717                                 for (i = 0; i < names.length; i++) {
14718                                         name = names[i];
14719
14720                                         if (!checkedMap[name] && matchNode(node, name, vars)) {
14721                                                 checkedMap[name] = true;
14722                                                 matchedFormatNames.push(name);
14723                                         }
14724                                 }
14725                         });
14726
14727                         return matchedFormatNames;
14728                 };
14729
14730                 function canApply(name) {
14731                         var formatList = get(name), startNode, parents, i, x, selector;
14732
14733                         if (formatList) {
14734                                 startNode = selection.getStart();
14735                                 parents = getParents(startNode);
14736
14737                                 for (x = formatList.length - 1; x >= 0; x--) {
14738                                         selector = formatList[x].selector;
14739
14740                                         // Format is not selector based, then always return TRUE
14741                                         if (!selector)
14742                                                 return TRUE;
14743
14744                                         for (i = parents.length - 1; i >= 0; i--) {
14745                                                 if (dom.is(parents[i], selector))
14746                                                         return TRUE;
14747                                         }
14748                                 }
14749                         }
14750
14751                         return FALSE;
14752                 };
14753
14754                 // Expose to public
14755                 tinymce.extend(this, {
14756                         get : get,
14757                         register : register,
14758                         apply : apply,
14759                         remove : remove,
14760                         toggle : toggle,
14761                         match : match,
14762                         matchAll : matchAll,
14763                         matchNode : matchNode,
14764                         canApply : canApply
14765                 });
14766
14767                 // Private functions
14768
14769                 function matchName(node, format) {
14770                         // Check for inline match
14771                         if (isEq(node, format.inline))
14772                                 return TRUE;
14773
14774                         // Check for block match
14775                         if (isEq(node, format.block))
14776                                 return TRUE;
14777
14778                         // Check for selector match
14779                         if (format.selector)
14780                                 return dom.is(node, format.selector);
14781                 };
14782
14783                 function isEq(str1, str2) {
14784                         str1 = str1 || '';
14785                         str2 = str2 || '';
14786
14787                         str1 = '' + (str1.nodeName || str1);
14788                         str2 = '' + (str2.nodeName || str2);
14789
14790                         return str1.toLowerCase() == str2.toLowerCase();
14791                 };
14792
14793                 function getStyle(node, name) {
14794                         var styleVal = dom.getStyle(node, name);
14795
14796                         // Force the format to hex
14797                         if (name == 'color' || name == 'backgroundColor')
14798                                 styleVal = dom.toHex(styleVal);
14799
14800                         // Opera will return bold as 700
14801                         if (name == 'fontWeight' && styleVal == 700)
14802                                 styleVal = 'bold';
14803
14804                         return '' + styleVal;
14805                 };
14806
14807                 function replaceVars(value, vars) {
14808                         if (typeof(value) != "string")
14809                                 value = value(vars);
14810                         else if (vars) {
14811                                 value = value.replace(/%(\w+)/g, function(str, name) {
14812                                         return vars[name] || str;
14813                                 });
14814                         }
14815
14816                         return value;
14817                 };
14818
14819                 function isWhiteSpaceNode(node) {
14820                         return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
14821                 };
14822
14823                 function wrap(node, name, attrs) {
14824                         var wrapper = dom.create(name, attrs);
14825
14826                         node.parentNode.insertBefore(wrapper, node);
14827                         wrapper.appendChild(node);
14828
14829                         return wrapper;
14830                 };
14831
14832                 function expandRng(rng, format, remove) {
14833                         var startContainer = rng.startContainer,
14834                                 startOffset = rng.startOffset,
14835                                 endContainer = rng.endContainer,
14836                                 endOffset = rng.endOffset, sibling, lastIdx, leaf;
14837
14838                         // This function walks up the tree if there is no siblings before/after the node
14839                         function findParentContainer(container, child_name, sibling_name, root) {
14840                                 var parent, child;
14841
14842                                 root = root || dom.getRoot();
14843
14844                                 for (;;) {
14845                                         // Check if we can move up are we at root level or body level
14846                                         parent = container.parentNode;
14847
14848                                         // Stop expanding on block elements or root depending on format
14849                                         if (parent == root || (!format[0].block_expand && isBlock(parent)))
14850                                                 return container;
14851
14852                                         for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
14853                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
14854                                                         return container;
14855
14856                                                 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
14857                                                         return container;
14858                                         }
14859
14860                                         container = container.parentNode;
14861                                 }
14862
14863                                 return container;
14864                         };
14865
14866                         // This function walks down the tree to find the leaf at the selection.
14867                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
14868                         function findLeaf(node, offset) {
14869                                 if (offset === undefined)
14870                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
14871                                 while (node && node.hasChildNodes()) {
14872                                         node = node.childNodes[offset];
14873                                         if (node)
14874                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
14875                                 }
14876                                 return { node: node, offset: offset };
14877                         }
14878
14879                         // If index based start position then resolve it
14880                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
14881                                 lastIdx = startContainer.childNodes.length - 1;
14882                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
14883
14884                                 if (startContainer.nodeType == 3)
14885                                         startOffset = 0;
14886                         }
14887
14888                         // If index based end position then resolve it
14889                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
14890                                 lastIdx = endContainer.childNodes.length - 1;
14891                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
14892
14893                                 if (endContainer.nodeType == 3)
14894                                         endOffset = endContainer.nodeValue.length;
14895                         }
14896
14897                         // Exclude bookmark nodes if possible
14898                         if (isBookmarkNode(startContainer.parentNode))
14899                                 startContainer = startContainer.parentNode;
14900
14901                         if (isBookmarkNode(startContainer))
14902                                 startContainer = startContainer.nextSibling || startContainer;
14903
14904                         if (isBookmarkNode(endContainer.parentNode)) {
14905                                 endOffset = dom.nodeIndex(endContainer);
14906                                 endContainer = endContainer.parentNode;
14907                         }
14908
14909                         if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
14910                                 endContainer = endContainer.previousSibling;
14911                                 endOffset = endContainer.length;
14912                         }
14913
14914                         if (format[0].inline) {
14915                                 // Avoid applying formatting to a trailing space.
14916                                 leaf = findLeaf(endContainer, endOffset);
14917                                 if (leaf.node) {
14918                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
14919                                                 leaf = findLeaf(leaf.node.previousSibling);
14920
14921                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
14922                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
14923
14924                                                 if (leaf.offset > 1) {
14925                                                         endContainer = leaf.node;
14926                                                         endContainer.splitText(leaf.offset - 1);
14927                                                 } else if (leaf.node.previousSibling) {
14928                                                         endContainer = leaf.node.previousSibling;
14929                                                 }
14930                                         }
14931                                 }
14932                         }
14933                         
14934                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers
14935                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
14936                         // This will reduce the number of wrapper elements that needs to be created
14937                         // Move start point up the tree
14938                         if (format[0].inline || format[0].block_expand) {
14939                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
14940                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
14941                         }
14942
14943                         // Expand start/end container to matching selector
14944                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
14945                                 function findSelectorEndPoint(container, sibling_name) {
14946                                         var parents, i, y, curFormat;
14947
14948                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
14949                                                 container = container[sibling_name];
14950
14951                                         parents = getParents(container);
14952                                         for (i = 0; i < parents.length; i++) {
14953                                                 for (y = 0; y < format.length; y++) {
14954                                                         curFormat = format[y];
14955
14956                                                         // If collapsed state is set then skip formats that doesn't match that
14957                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
14958                                                                 continue;
14959
14960                                                         if (dom.is(parents[i], curFormat.selector))
14961                                                                 return parents[i];
14962                                                 }
14963                                         }
14964
14965                                         return container;
14966                                 };
14967
14968                                 // Find new startContainer/endContainer if there is better one
14969                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
14970                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
14971                         }
14972
14973                         // Expand start/end container to matching block element or text node
14974                         if (format[0].block || format[0].selector) {
14975                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {
14976                                         var node;
14977
14978                                         // Expand to block of similar type
14979                                         if (!format[0].wrapper)
14980                                                 node = dom.getParent(container, format[0].block);
14981
14982                                         // Expand to first wrappable block element or any block element
14983                                         if (!node)
14984                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
14985
14986                                         // Exclude inner lists from wrapping
14987                                         if (node && format[0].wrapper)
14988                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;
14989
14990                                         // Didn't find a block element look for first/last wrappable element
14991                                         if (!node) {
14992                                                 node = container;
14993
14994                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {
14995                                                         node = node[sibling_name];
14996
14997                                                         // Break on BR but include it will be removed later on
14998                                                         // we can't remove it now since we need to check if it can be wrapped
14999                                                         if (isEq(node, 'br'))
15000                                                                 break;
15001                                                 }
15002                                         }
15003
15004                                         return node || container;
15005                                 };
15006
15007                                 // Find new startContainer/endContainer if there is better one
15008                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15009                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15010
15011                                 // Non block element then try to expand up the leaf
15012                                 if (format[0].block) {
15013                                         if (!isBlock(startContainer))
15014                                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15015
15016                                         if (!isBlock(endContainer))
15017                                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15018                                 }
15019                         }
15020
15021                         // Setup index for startContainer
15022                         if (startContainer.nodeType == 1) {
15023                                 startOffset = nodeIndex(startContainer);
15024                                 startContainer = startContainer.parentNode;
15025                         }
15026
15027                         // Setup index for endContainer
15028                         if (endContainer.nodeType == 1) {
15029                                 endOffset = nodeIndex(endContainer) + 1;
15030                                 endContainer = endContainer.parentNode;
15031                         }
15032
15033                         // Return new range like object
15034                         return {
15035                                 startContainer : startContainer,
15036                                 startOffset : startOffset,
15037                                 endContainer : endContainer,
15038                                 endOffset : endOffset
15039                         };
15040                 }
15041
15042                 function removeFormat(format, vars, node, compare_node) {
15043                         var i, attrs, stylesModified;
15044
15045                         // Check if node matches format
15046                         if (!matchName(node, format))
15047                                 return FALSE;
15048
15049                         // Should we compare with format attribs and styles
15050                         if (format.remove != 'all') {
15051                                 // Remove styles
15052                                 each(format.styles, function(value, name) {
15053                                         value = replaceVars(value, vars);
15054
15055                                         // Indexed array
15056                                         if (typeof(name) === 'number') {
15057                                                 name = value;
15058                                                 compare_node = 0;
15059                                         }
15060
15061                                         if (!compare_node || isEq(getStyle(compare_node, name), value))
15062                                                 dom.setStyle(node, name, '');
15063
15064                                         stylesModified = 1;
15065                                 });
15066
15067                                 // Remove style attribute if it's empty
15068                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {
15069                                         node.removeAttribute('style');
15070                                         node.removeAttribute('data-mce-style');
15071                                 }
15072
15073                                 // Remove attributes
15074                                 each(format.attributes, function(value, name) {
15075                                         var valueOut;
15076
15077                                         value = replaceVars(value, vars);
15078
15079                                         // Indexed array
15080                                         if (typeof(name) === 'number') {
15081                                                 name = value;
15082                                                 compare_node = 0;
15083                                         }
15084
15085                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15086                                                 // Keep internal classes
15087                                                 if (name == 'class') {
15088                                                         value = dom.getAttrib(node, name);
15089                                                         if (value) {
15090                                                                 // Build new class value where everything is removed except the internal prefixed classes
15091                                                                 valueOut = '';
15092                                                                 each(value.split(/\s+/), function(cls) {
15093                                                                         if (/mce\w+/.test(cls))
15094                                                                                 valueOut += (valueOut ? ' ' : '') + cls;
15095                                                                 });
15096
15097                                                                 // We got some internal classes left
15098                                                                 if (valueOut) {
15099                                                                         dom.setAttrib(node, name, valueOut);
15100                                                                         return;
15101                                                                 }
15102                                                         }
15103                                                 }
15104
15105                                                 // IE6 has a bug where the attribute doesn't get removed correctly
15106                                                 if (name == "class")
15107                                                         node.removeAttribute('className');
15108
15109                                                 // Remove mce prefixed attributes
15110                                                 if (MCE_ATTR_RE.test(name))
15111                                                         node.removeAttribute('data-mce-' + name);
15112
15113                                                 node.removeAttribute(name);
15114                                         }
15115                                 });
15116
15117                                 // Remove classes
15118                                 each(format.classes, function(value) {
15119                                         value = replaceVars(value, vars);
15120
15121                                         if (!compare_node || dom.hasClass(compare_node, value))
15122                                                 dom.removeClass(node, value);
15123                                 });
15124
15125                                 // Check for non internal attributes
15126                                 attrs = dom.getAttribs(node);
15127                                 for (i = 0; i < attrs.length; i++) {
15128                                         if (attrs[i].nodeName.indexOf('_') !== 0)
15129                                                 return FALSE;
15130                                 }
15131                         }
15132
15133                         // Remove the inline child if it's empty for example <b> or <span>
15134                         if (format.remove != 'none') {
15135                                 removeNode(node, format);
15136                                 return TRUE;
15137                         }
15138                 };
15139
15140                 function removeNode(node, format) {
15141                         var parentNode = node.parentNode, rootBlockElm;
15142
15143                         if (format.block) {
15144                                 if (!forcedRootBlock) {
15145                                         function find(node, next, inc) {
15146                                                 node = getNonWhiteSpaceSibling(node, next, inc);
15147
15148                                                 return !node || (node.nodeName == 'BR' || isBlock(node));
15149                                         };
15150
15151                                         // Append BR elements if needed before we remove the block
15152                                         if (isBlock(node) && !isBlock(parentNode)) {
15153                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
15154                                                         node.insertBefore(dom.create('br'), node.firstChild);
15155
15156                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
15157                                                         node.appendChild(dom.create('br'));
15158                                         }
15159                                 } else {
15160                                         // Wrap the block in a forcedRootBlock if we are at the root of document
15161                                         if (parentNode == dom.getRoot()) {
15162                                                 if (!format.list_block || !isEq(node, format.list_block)) {
15163                                                         each(tinymce.grep(node.childNodes), function(node) {
15164                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15165                                                                         if (!rootBlockElm)
15166                                                                                 rootBlockElm = wrap(node, forcedRootBlock);
15167                                                                         else
15168                                                                                 rootBlockElm.appendChild(node);
15169                                                                 } else
15170                                                                         rootBlockElm = 0;
15171                                                         });
15172                                                 }
15173                                         }
15174                                 }
15175                         }
15176
15177                         // Never remove nodes that isn't the specified inline element if a selector is specified too
15178                         if (format.selector && format.inline && !isEq(format.inline, node))
15179                                 return;
15180
15181                         dom.remove(node, 1);
15182                 };
15183
15184                 function getNonWhiteSpaceSibling(node, next, inc) {
15185                         if (node) {
15186                                 next = next ? 'nextSibling' : 'previousSibling';
15187
15188                                 for (node = inc ? node : node[next]; node; node = node[next]) {
15189                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))
15190                                                 return node;
15191                                 }
15192                         }
15193                 };
15194
15195                 function isBookmarkNode(node) {
15196                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
15197                 };
15198
15199                 function mergeSiblings(prev, next) {
15200                         var marker, sibling, tmpSibling;
15201
15202                         function compareElements(node1, node2) {
15203                                 // Not the same name
15204                                 if (node1.nodeName != node2.nodeName)
15205                                         return FALSE;
15206
15207                                 function getAttribs(node) {
15208                                         var attribs = {};
15209
15210                                         each(dom.getAttribs(node), function(attr) {
15211                                                 var name = attr.nodeName.toLowerCase();
15212
15213                                                 // Don't compare internal attributes or style
15214                                                 if (name.indexOf('_') !== 0 && name !== 'style')
15215                                                         attribs[name] = dom.getAttrib(node, name);
15216                                         });
15217
15218                                         return attribs;
15219                                 };
15220
15221                                 function compareObjects(obj1, obj2) {
15222                                         var value, name;
15223
15224                                         for (name in obj1) {
15225                                                 // Obj1 has item obj2 doesn't have
15226                                                 if (obj1.hasOwnProperty(name)) {
15227                                                         value = obj2[name];
15228
15229                                                         // Obj2 doesn't have obj1 item
15230                                                         if (value === undefined)
15231                                                                 return FALSE;
15232
15233                                                         // Obj2 item has a different value
15234                                                         if (obj1[name] != value)
15235                                                                 return FALSE;
15236
15237                                                         // Delete similar value
15238                                                         delete obj2[name];
15239                                                 }
15240                                         }
15241
15242                                         // Check if obj 2 has something obj 1 doesn't have
15243                                         for (name in obj2) {
15244                                                 // Obj2 has item obj1 doesn't have
15245                                                 if (obj2.hasOwnProperty(name))
15246                                                         return FALSE;
15247                                         }
15248
15249                                         return TRUE;
15250                                 };
15251
15252                                 // Attribs are not the same
15253                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
15254                                         return FALSE;
15255
15256                                 // Styles are not the same
15257                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
15258                                         return FALSE;
15259
15260                                 return TRUE;
15261                         };
15262
15263                         // Check if next/prev exists and that they are elements
15264                         if (prev && next) {
15265                                 function findElementSibling(node, sibling_name) {
15266                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {
15267                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
15268                                                         return node;
15269
15270                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15271                                                         return sibling;
15272                                         }
15273
15274                                         return node;
15275                                 };
15276
15277                                 // If previous sibling is empty then jump over it
15278                                 prev = findElementSibling(prev, 'previousSibling');
15279                                 next = findElementSibling(next, 'nextSibling');
15280
15281                                 // Compare next and previous nodes
15282                                 if (compareElements(prev, next)) {
15283                                         // Append nodes between
15284                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {
15285                                                 tmpSibling = sibling;
15286                                                 sibling = sibling.nextSibling;
15287                                                 prev.appendChild(tmpSibling);
15288                                         }
15289
15290                                         // Remove next node
15291                                         dom.remove(next);
15292
15293                                         // Move children into prev node
15294                                         each(tinymce.grep(next.childNodes), function(node) {
15295                                                 prev.appendChild(node);
15296                                         });
15297
15298                                         return prev;
15299                                 }
15300                         }
15301
15302                         return next;
15303                 };
15304
15305                 function isTextBlock(name) {
15306                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
15307                 };
15308
15309                 function getContainer(rng, start) {
15310                         var container, offset, lastIdx;
15311
15312                         container = rng[start ? 'startContainer' : 'endContainer'];
15313                         offset = rng[start ? 'startOffset' : 'endOffset'];
15314
15315                         if (container.nodeType == 1) {
15316                                 lastIdx = container.childNodes.length - 1;
15317
15318                                 if (!start && offset)
15319                                         offset--;
15320
15321                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
15322                         }
15323
15324                         return container;
15325                 };
15326
15327                 function performCaretAction(type, name, vars) {
15328                         var i, currentPendingFormats = pendingFormats[type],
15329                                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
15330
15331                         function hasPending() {
15332                                 return pendingFormats.apply.length || pendingFormats.remove.length;
15333                         };
15334
15335                         function resetPending() {
15336                                 pendingFormats.apply = [];
15337                                 pendingFormats.remove = [];
15338                         };
15339
15340                         function perform(caret_node) {
15341                                 // Apply pending formats
15342                                 each(pendingFormats.apply.reverse(), function(item) {
15343                                         apply(item.name, item.vars, caret_node);
15344
15345                                         // Colored nodes should be underlined so that the color of the underline matches the text color.
15346                                         if (item.name === 'forecolor' && item.vars.value)
15347                                                 processUnderlineAndColor(caret_node.parentNode);
15348                                 });
15349
15350                                 // Remove pending formats
15351                                 each(pendingFormats.remove.reverse(), function(item) {
15352                                         remove(item.name, item.vars, caret_node);
15353                                 });
15354
15355                                 dom.remove(caret_node, 1);
15356                                 resetPending();
15357                         };
15358
15359                         // Check if it already exists then ignore it
15360                         for (i = currentPendingFormats.length - 1; i >= 0; i--) {
15361                                 if (currentPendingFormats[i].name == name)
15362                                         return;
15363                         }
15364
15365                         currentPendingFormats.push({name : name, vars : vars});
15366
15367                         // Check if it's in the other type, then remove it
15368                         for (i = otherPendingFormats.length - 1; i >= 0; i--) {
15369                                 if (otherPendingFormats[i].name == name)
15370                                         otherPendingFormats.splice(i, 1);
15371                         }
15372
15373                         // Pending apply or remove formats
15374                         if (hasPending()) {
15375                                 ed.getDoc().execCommand('FontName', false, 'mceinline');
15376                                 pendingFormats.lastRng = selection.getRng();
15377
15378                                 // IE will convert the current word
15379                                 each(dom.select('font,span'), function(node) {
15380                                         var bookmark;
15381
15382                                         if (isCaretNode(node)) {
15383                                                 bookmark = selection.getBookmark();
15384                                                 perform(node);
15385                                                 selection.moveToBookmark(bookmark);
15386                                                 ed.nodeChanged();
15387                                         }
15388                                 });
15389
15390                                 // Only register listeners once if we need to
15391                                 if (!pendingFormats.isListening && hasPending()) {
15392                                         pendingFormats.isListening = true;
15393                                         function performPendingFormat(node, textNode) {
15394                                                 var rng = dom.createRng();
15395                                                 perform(node);
15396
15397                                                 rng.setStart(textNode, textNode.nodeValue.length);
15398                                                 rng.setEnd(textNode, textNode.nodeValue.length);
15399                                                 selection.setRng(rng);
15400                                                 ed.nodeChanged();
15401                                         }
15402                                         var enterKeyPressed = false;
15403
15404                                         each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
15405                                                 ed[event].addToTop(function(ed, e) {
15406                                                         if (e.keyCode==13 && !e.shiftKey) {
15407                                                                 enterKeyPressed = true;
15408                                                                 return;
15409                                                         }
15410                                                         // Do we have pending formats and is the selection moved has moved
15411                                                         if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
15412                                                                 var foundCaret = false;
15413                                                                 each(dom.select('font,span'), function(node) {
15414                                                                         var textNode, rng;
15415
15416                                                                         // Look for marker
15417                                                                         if (isCaretNode(node)) {
15418                                                                                 foundCaret = true;
15419                                                                                 textNode = node.firstChild;
15420
15421                                                                                 // Find the first text node within node
15422                                                                                 while (textNode && textNode.nodeType != 3)
15423                                                                                         textNode = textNode.firstChild;
15424
15425                                                                                 if (textNode) 
15426                                                                                         performPendingFormat(node, textNode);
15427                                                                                 else
15428                                                                                         dom.remove(node);
15429                                                                         }
15430                                                                 });
15431                                                                 
15432                                                                 // no caret - so we are 
15433                                                                 if (enterKeyPressed && !foundCaret) {
15434                                                                         var node = selection.getNode();
15435                                                                         var textNode = node;
15436
15437                                                                         // Find the first text node within node
15438                                                                         while (textNode && textNode.nodeType != 3)
15439                                                                                 textNode = textNode.firstChild;
15440                                                                         if (textNode) {
15441                                                                                 node=textNode.parentNode;
15442                                                                                 while (!isBlock(node)){
15443                                                                                         node=node.parentNode;
15444                                                                                 }
15445                                                                                 performPendingFormat(node, textNode);
15446                                                                         }
15447                                                                 }
15448
15449                                                                 // Always unbind and clear pending styles on keyup
15450                                                                 if (e.type == 'keyup' || e.type == 'mouseup') {
15451                                                                         resetPending();
15452                                                                         enterKeyPressed=false;
15453                                                                 }
15454                                                         }
15455                                                 });
15456                                         });
15457                                 }
15458                         }
15459                 };
15460         };
15461 })(tinymce);
15462
15463 tinymce.onAddEditor.add(function(tinymce, ed) {
15464         var filters, fontSizes, dom, settings = ed.settings;
15465
15466         if (settings.inline_styles) {
15467                 fontSizes = tinymce.explode(settings.font_size_style_values);
15468
15469                 function replaceWithSpan(node, styles) {
15470                         tinymce.each(styles, function(value, name) {
15471                                 if (value)
15472                                         dom.setStyle(node, name, value);
15473                         });
15474
15475                         dom.rename(node, 'span');
15476                 };
15477
15478                 filters = {
15479                         font : function(dom, node) {
15480                                 replaceWithSpan(node, {
15481                                         backgroundColor : node.style.backgroundColor,
15482                                         color : node.color,
15483                                         fontFamily : node.face,
15484                                         fontSize : fontSizes[parseInt(node.size) - 1]
15485                                 });
15486                         },
15487
15488                         u : function(dom, node) {
15489                                 replaceWithSpan(node, {
15490                                         textDecoration : 'underline'
15491                                 });
15492                         },
15493
15494                         strike : function(dom, node) {
15495                                 replaceWithSpan(node, {
15496                                         textDecoration : 'line-through'
15497                                 });
15498                         }
15499                 };
15500
15501                 function convert(editor, params) {
15502                         dom = editor.dom;
15503
15504                         if (settings.convert_fonts_to_spans) {
15505                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
15506                                         filters[node.nodeName.toLowerCase()](ed.dom, node);
15507                                 });
15508                         }
15509                 };
15510
15511                 ed.onPreProcess.add(convert);
15512                 ed.onSetContent.add(convert);
15513
15514                 ed.onInit.add(function() {
15515                         ed.selection.onSetContent.add(convert);
15516                 });
15517         }
15518 });
15519