thomascube
2011-04-20 a9251be2f09fb5f18a85d201c67668c70980efe3
commit | author | age
69d05c 1 (function(win) {
A 2     var whiteSpaceRe = /^\s*|\s*$/g,
a9251b 3         undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
d9344f 4
69d05c 5     var tinymce = {
A 6         majorVersion : '3',
d9344f 7
a9251b 8         minorVersion : '4.2',
58fb65 9
a9251b 10         releaseDate : '2011-04-07',
58fb65 11
69d05c 12         _init : function() {
A 13             var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
58fb65 14
69d05c 15             t.isOpera = win.opera && opera.buildNumber;
58fb65 16
69d05c 17             t.isWebKit = /WebKit/.test(ua);
58fb65 18
69d05c 19             t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
58fb65 20
69d05c 21             t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
d9344f 22
69d05c 23             t.isGecko = !t.isWebKit && /Gecko/.test(ua);
d9344f 24
69d05c 25             t.isMac = ua.indexOf('Mac') != -1;
d9344f 26
69d05c 27             t.isAir = /adobeair/i.test(ua);
2011be 28
A 29             t.isIDevice = /(iPad|iPhone)/.test(ua);
18240a 30
69d05c 31             // TinyMCE .NET webcontrol might be setting the values for TinyMCE
A 32             if (win.tinyMCEPreInit) {
33                 t.suffix = tinyMCEPreInit.suffix;
34                 t.baseURL = tinyMCEPreInit.base;
35                 t.query = tinyMCEPreInit.query;
d9344f 36                 return;
69d05c 37             }
d9344f 38
69d05c 39             // Get suffix and base
A 40             t.suffix = '';
41
42             // If base element found, add that infront of baseURL
43             nl = d.getElementsByTagName('base');
44             for (i=0; i<nl.length; i++) {
45                 if (v = nl[i].href) {
46                     // Host only value like http://site.com or http://site.com:8008
47                     if (/^https?:\/\/[^\/]+$/.test(v))
48                         v += '/';
49
50                     base = v ? v.match(/.*\//)[0] : ''; // Get only directory
51                 }
52             }
53
54             function getBase(n) {
a9251b 55                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
69d05c 56                     if (/_(src|dev)\.js/g.test(n.src))
A 57                         t.suffix = '_src';
58
59                     if ((p = n.src.indexOf('?')) != -1)
60                         t.query = n.src.substring(p + 1);
61
62                     t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
63
64                     // If path to script is relative and a base href was found add that one infront
65                     // the src property will always be an absolute one on non IE browsers and IE 8
66                     // so this logic will basically only be executed on older IE versions
67                     if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
68                         t.baseURL = base + t.baseURL;
69
70                     return t.baseURL;
71                 }
72
73                 return null;
74             };
75
76             // Check document
77             nl = d.getElementsByTagName('script');
d9344f 78             for (i=0; i<nl.length; i++) {
S 79                 if (getBase(nl[i]))
80                     return;
81             }
82
69d05c 83             // Check head
A 84             n = d.getElementsByTagName('head')[0];
85             if (n) {
86                 nl = n.getElementsByTagName('script');
87                 for (i=0; i<nl.length; i++) {
88                     if (getBase(nl[i]))
89                         return;
90                 }
d9344f 91             }
69d05c 92
A 93             return;
94         },
95
96         is : function(o, t) {
97             if (!t)
98                 return o !== undefined;
99
100             if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
101                 return true;
102
103             return typeof(o) == t;
a9251b 104         },
T 105
106         makeMap : function(items, delim, map) {
107             var i;
108
109             items = items || [];
110             delim = delim || ',';
111
112             if (typeof(items) == "string")
113                 items = items.split(delim);
114
115             map = map || {};
116
117             i = items.length;
118             while (i--)
119                 map[items[i]] = {};
120
121             return map;
69d05c 122         },
A 123
124         each : function(o, cb, s) {
125             var n, l;
126
127             if (!o)
128                 return 0;
129
130             s = s || o;
131
132             if (o.length !== undefined) {
133                 // Indexed arrays, needed for Safari
134                 for (n=0, l = o.length; n < l; n++) {
d9344f 135                     if (cb.call(s, o[n], n, o) === false)
S 136                         return 0;
137                 }
138             } else {
69d05c 139                 // Hashtables
A 140                 for (n in o) {
141                     if (o.hasOwnProperty(n)) {
142                         if (cb.call(s, o[n], n, o) === false)
143                             return 0;
144                     }
145                 }
d9344f 146             }
S 147
69d05c 148             return 1;
A 149         },
150
151
152         map : function(a, f) {
153             var o = [];
154
155             tinymce.each(a, function(v) {
156                 o.push(f(v));
d9344f 157             });
S 158
69d05c 159             return o;
A 160         },
161
162         grep : function(a, f) {
163             var o = [];
164
165             tinymce.each(a, function(v) {
166                 if (!f || f(v))
167                     o.push(v);
168             });
169
170             return o;
171         },
172
173         inArray : function(a, v) {
174             var i, l;
175
176             if (a) {
177                 for (i = 0, l = a.length; i < l; i++) {
178                     if (a[i] === v)
179                         return i;
180                 }
181             }
182
183             return -1;
184         },
185
186         extend : function(o, e) {
187             var i, l, a = arguments;
188
189             for (i = 1, l = a.length; i < l; i++) {
190                 e = a[i];
191
192                 tinymce.each(e, function(v, n) {
193                     if (v !== undefined)
194                         o[n] = v;
195                 });
196             }
197
198             return o;
199         },
200
201
202         trim : function(s) {
203             return (s ? '' + s : '').replace(whiteSpaceRe, '');
204         },
205
a9251b 206         create : function(s, p, root) {
69d05c 207             var t = this, sp, ns, cn, scn, c, de = 0;
A 208
209             // Parse : <prefix> <class>:<super class>
210             s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
211             cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
212
213             // Create namespace for new class
a9251b 214             ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
69d05c 215
A 216             // Class already exists
217             if (ns[cn])
218                 return;
219
220             // Make pure static class
221             if (s[2] == 'static') {
222                 ns[cn] = p;
223
224                 if (this.onCreate)
225                     this.onCreate(s[2], s[3], ns[cn]);
226
227                 return;
228             }
229
230             // Create default constructor
231             if (!p[cn]) {
232                 p[cn] = function() {};
233                 de = 1;
234             }
235
236             // Add constructor and methods
237             ns[cn] = p[cn];
238             t.extend(ns[cn].prototype, p);
239
240             // Extend
241             if (s[5]) {
242                 sp = t.resolve(s[5]).prototype;
243                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
244
245                 // Extend constructor
246                 c = ns[cn];
247                 if (de) {
248                     // Add passthrough constructor
249                     ns[cn] = function() {
250                         return sp[scn].apply(this, arguments);
d9344f 251                     };
S 252                 } else {
69d05c 253                     // Add inherit constructor
A 254                     ns[cn] = function() {
255                         this.parent = sp[scn];
256                         return c.apply(this, arguments);
18240a 257                     };
A 258                 }
69d05c 259                 ns[cn].prototype[cn] = ns[cn];
d9344f 260
69d05c 261                 // Add super methods
A 262                 t.each(sp, function(f, n) {
263                     ns[cn].prototype[n] = sp[n];
264                 });
d9344f 265
69d05c 266                 // Add overridden methods
A 267                 t.each(p, function(f, n) {
268                     // Extend methods if needed
269                     if (sp[n]) {
270                         ns[cn].prototype[n] = function() {
271                             this.parent = sp[n];
272                             return f.apply(this, arguments);
273                         };
274                     } else {
275                         if (n != cn)
276                             ns[cn].prototype[n] = f;
277                     }
278                 });
d9344f 279             }
S 280
69d05c 281             // Add static methods
A 282             t.each(p['static'], function(f, n) {
283                 ns[cn][n] = f;
284             });
d9344f 285
69d05c 286             if (this.onCreate)
A 287                 this.onCreate(s[2], s[3], ns[cn].prototype);
288         },
18240a 289
69d05c 290         walk : function(o, f, n, s) {
A 291             s = s || this;
18240a 292
69d05c 293             if (o) {
A 294                 if (n)
295                     o = o[n];
18240a 296
69d05c 297                 tinymce.each(o, function(o, i) {
A 298                     if (f.call(s, o, i, n) === false)
299                         return false;
18240a 300
69d05c 301                     tinymce.walk(o, f, n, s);
A 302                 });
303             }
304         },
18240a 305
69d05c 306         createNS : function(n, o) {
A 307             var i, v;
d9344f 308
69d05c 309             o = o || win;
d9344f 310
69d05c 311             n = n.split('.');
A 312             for (i=0; i<n.length; i++) {
313                 v = n[i];
d9344f 314
69d05c 315                 if (!o[v])
A 316                     o[v] = {};
317
318                 o = o[v];
319             }
320
321             return o;
322         },
323
324         resolve : function(n, o) {
325             var i, l;
326
327             o = o || win;
328
329             n = n.split('.');
330             for (i = 0, l = n.length; i < l; i++) {
331                 o = o[n[i]];
332
333                 if (!o)
334                     break;
335             }
336
337             return o;
338         },
339
340         addUnload : function(f, s) {
341             var t = this;
342
343             f = {func : f, scope : s || this};
344
345             if (!t.unloads) {
346                 function unload() {
347                     var li = t.unloads, o, n;
348
349                     if (li) {
350                         // Call unload handlers
351                         for (n in li) {
352                             o = li[n];
353
354                             if (o && o.func)
355                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
356                         }
357
358                         // Detach unload function
359                         if (win.detachEvent) {
360                             win.detachEvent('onbeforeunload', fakeUnload);
361                             win.detachEvent('onunload', unload);
362                         } else if (win.removeEventListener)
363                             win.removeEventListener('unload', unload, false);
364
365                         // Destroy references
366                         t.unloads = o = li = w = unload = 0;
367
368                         // Run garbarge collector on IE
369                         if (win.CollectGarbage)
370                             CollectGarbage();
371                     }
372                 };
373
374                 function fakeUnload() {
375                     var d = document;
376
377                     // Is there things still loading, then do some magic
378                     if (d.readyState == 'interactive') {
379                         function stop() {
380                             // Prevent memory leak
381                             d.detachEvent('onstop', stop);
382
383                             // Call unload handler
384                             if (unload)
385                                 unload();
386
387                             d = 0;
388                         };
389
390                         // Fire unload when the currently loading page is stopped
391                         if (d)
392                             d.attachEvent('onstop', stop);
393
394                         // Remove onstop listener after a while to prevent the unload function
395                         // to execute if the user presses cancel in an onbeforeunload
396                         // confirm dialog and then presses the browser stop button
397                         win.setTimeout(function() {
398                             if (d)
399                                 d.detachEvent('onstop', stop);
400                         }, 0);
401                     }
402                 };
403
404                 // Attach unload handler
405                 if (win.attachEvent) {
406                     win.attachEvent('onunload', unload);
407                     win.attachEvent('onbeforeunload', fakeUnload);
408                 } else if (win.addEventListener)
409                     win.addEventListener('unload', unload, false);
410
411                 // Setup initial unload handler array
412                 t.unloads = [f];
413             } else
414                 t.unloads.push(f);
415
416             return f;
417         },
418
419         removeUnload : function(f) {
420             var u = this.unloads, r = null;
421
422             tinymce.each(u, function(o, i) {
423                 if (o && o.func == f) {
424                     u.splice(i, 1);
425                     r = f;
426                     return false;
427                 }
428             });
429
430             return r;
431         },
432
433         explode : function(s, d) {
434             return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
435         },
436
437         _addVer : function(u) {
438             var v;
439
440             if (!this.query)
441                 return u;
442
443             v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
444
445             if (u.indexOf('#') == -1)
446                 return u + v;
447
448             return u.replace('#', v + '#');
a9251b 449         },
T 450
451         // Fix function for IE 9 where regexps isn't working correctly
452         // Todo: remove me once MS fixes the bug
453         _replace : function(find, replace, str) {
454             // On IE9 we have to fake $x replacement
455             if (isRegExpBroken) {
456                 return str.replace(find, function() {
457                     var val = replace, args = arguments, i;
458
459                     for (i = 0; i < args.length - 2; i++) {
460                         if (args[i] === undefined) {
461                             val = val.replace(new RegExp('\\$' + i, 'g'), '');
462                         } else {
463                             val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
464                         }
465                     }
466
467                     return val;
468                 });
469             }
470
471             return str.replace(find, replace);
69d05c 472         }
A 473
474         };
475
476     // Initialize the API
477     tinymce._init();
478
479     // Expose tinymce namespace to the global namespace (window)
480     win.tinymce = win.tinyMCE = tinymce;
a9251b 481
T 482     // Describe the different namespaces
483
484     })(window);
69d05c 485
A 486
d9344f 487 tinymce.create('tinymce.util.Dispatcher', {
S 488     scope : null,
489     listeners : null,
490
491     Dispatcher : function(s) {
492         this.scope = s || this;
493         this.listeners = [];
494     },
495
496     add : function(cb, s) {
497         this.listeners.push({cb : cb, scope : s || this.scope});
498
499         return cb;
500     },
501
502     addToTop : function(cb, s) {
503         this.listeners.unshift({cb : cb, scope : s || this.scope});
504
505         return cb;
506     },
507
508     remove : function(cb) {
509         var l = this.listeners, o = null;
510
511         tinymce.each(l, function(c, i) {
512             if (cb == c.cb) {
513                 o = cb;
514                 l.splice(i, 1);
515                 return false;
516             }
517         });
518
519         return o;
520     },
521
522     dispatch : function() {
523         var s, a = arguments, i, li = this.listeners, c;
524
525         // Needs to be a real loop since the listener count might change while looping
526         // And this is also more efficient
527         for (i = 0; i<li.length; i++) {
528             c = li[i];
529             s = c.cb.apply(c.scope, a);
530
531             if (s === false)
532                 break;
533         }
534
535         return s;
536     }
537
538     });
69d05c 539
d9344f 540 (function() {
S 541     var each = tinymce.each;
542
543     tinymce.create('tinymce.util.URI', {
544         URI : function(u, s) {
545             var t = this, o, a, b;
546
58fb65 547             // Trim whitespace
A 548             u = tinymce.trim(u);
549
d9344f 550             // Default settings
S 551             s = t.settings = s || {};
552
553             // Strange app protocol or local anchor
58fb65 554             if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
d9344f 555                 t.source = u;
S 556                 return;
557             }
558
559             // Absolute path with no host, fake host and protocol
560             if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
561                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
562
58fb65 563             // Relative path http:// or protocol relative //path
A 564             if (!/^\w*:?\/\//.test(u))
d9344f 565                 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
S 566
567             // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
568             u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
569             u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
570             each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
571                 var s = u[i];
572
573                 // Zope 3 workaround, they use @@something
574                 if (s)
575                     s = s.replace(/\(mce_at\)/g, '@@');
576
577                 t[v] = s;
578             });
579
580             if (b = s.base_uri) {
581                 if (!t.protocol)
582                     t.protocol = b.protocol;
583
584                 if (!t.userInfo)
585                     t.userInfo = b.userInfo;
586
587                 if (!t.port && t.host == 'mce_host')
588                     t.port = b.port;
589
590                 if (!t.host || t.host == 'mce_host')
591                     t.host = b.host;
592
593                 t.source = '';
594             }
595
596             //t.path = t.path || '/';
597         },
598
599         setPath : function(p) {
600             var t = this;
601
602             p = /^(.*?)\/?(\w+)?$/.exec(p);
603
604             // Update path parts
605             t.path = p[0];
606             t.directory = p[1];
607             t.file = p[2];
608
609             // Rebuild source
610             t.source = '';
611             t.getURI();
612         },
613
614         toRelative : function(u) {
615             var t = this, o;
616
29da64 617             if (u === "./")
A 618                 return u;
619
d9344f 620             u = new tinymce.util.URI(u, {base_uri : t});
S 621
622             // Not on same domain/port or protocol
623             if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
624                 return u.getURI();
625
626             o = t.toRelPath(t.path, u.path);
627
628             // Add query
629             if (u.query)
630                 o += '?' + u.query;
631
632             // Add anchor
633             if (u.anchor)
634                 o += '#' + u.anchor;
635
636             return o;
637         },
638     
639         toAbsolute : function(u, nh) {
640             var u = new tinymce.util.URI(u, {base_uri : this});
641
58fb65 642             return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
d9344f 643         },
S 644
645         toRelPath : function(base, path) {
29da64 646             var items, bp = 0, out = '', i, l;
d9344f 647
S 648             // Split the paths
649             base = base.substring(0, base.lastIndexOf('/'));
650             base = base.split('/');
651             items = path.split('/');
652
653             if (base.length >= items.length) {
29da64 654                 for (i = 0, l = base.length; i < l; i++) {
d9344f 655                     if (i >= items.length || base[i] != items[i]) {
S 656                         bp = i + 1;
657                         break;
658                     }
659                 }
660             }
661
662             if (base.length < items.length) {
29da64 663                 for (i = 0, l = items.length; i < l; i++) {
d9344f 664                     if (i >= base.length || base[i] != items[i]) {
S 665                         bp = i + 1;
666                         break;
667                     }
668                 }
669             }
670
671             if (bp == 1)
672                 return path;
673
29da64 674             for (i = 0, l = base.length - (bp - 1); i < l; i++)
d9344f 675                 out += "../";
S 676
29da64 677             for (i = bp - 1, l = items.length; i < l; i++) {
d9344f 678                 if (i != bp - 1)
S 679                     out += "/" + items[i];
680                 else
681                     out += items[i];
682             }
683
684             return out;
685         },
686
687         toAbsPath : function(base, path) {
58fb65 688             var i, nb = 0, o = [], tr, outPath;
d9344f 689
S 690             // Split paths
29da64 691             tr = /\/$/.test(path) ? '/' : '';
d9344f 692             base = base.split('/');
S 693             path = path.split('/');
694
695             // Remove empty chunks
696             each(base, function(k) {
697                 if (k)
698                     o.push(k);
699             });
700
701             base = o;
702
703             // Merge relURLParts chunks
704             for (i = path.length - 1, o = []; i >= 0; i--) {
705                 // Ignore empty or .
706                 if (path[i].length == 0 || path[i] == ".")
707                     continue;
708
709                 // Is parent
710                 if (path[i] == '..') {
711                     nb++;
712                     continue;
713                 }
714
715                 // Move up
716                 if (nb > 0) {
717                     nb--;
718                     continue;
719                 }
720
721                 o.push(path[i]);
722             }
723
724             i = base.length - nb;
725
726             // If /a/b/c or /
727             if (i <= 0)
58fb65 728                 outPath = o.reverse().join('/');
A 729             else
730                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
d9344f 731
58fb65 732             // Add front / if it's needed
A 733             if (outPath.indexOf('/') !== 0)
734                 outPath = '/' + outPath;
735
736             // Add traling / if it's needed
737             if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
738                 outPath += tr;
739
740             return outPath;
d9344f 741         },
S 742
743         getURI : function(nh) {
744             var s, t = this;
745
746             // Rebuild source
747             if (!t.source || nh) {
748                 s = '';
749
750                 if (!nh) {
751                     if (t.protocol)
752                         s += t.protocol + '://';
753
754                     if (t.userInfo)
755                         s += t.userInfo + '@';
756
757                     if (t.host)
758                         s += t.host;
759
760                     if (t.port)
761                         s += ':' + t.port;
762                 }
763
764                 if (t.path)
765                     s += t.path;
766
767                 if (t.query)
768                     s += '?' + t.query;
769
770                 if (t.anchor)
771                     s += '#' + t.anchor;
772
773                 t.source = s;
774             }
775
776             return t.source;
777         }
58fb65 778     });
d9344f 779 })();
69d05c 780
d9344f 781 (function() {
S 782     var each = tinymce.each;
783
784     tinymce.create('static tinymce.util.Cookie', {
785         getHash : function(n) {
786             var v = this.get(n), h;
787
788             if (v) {
789                 each(v.split('&'), function(v) {
790                     v = v.split('=');
791                     h = h || {};
792                     h[unescape(v[0])] = unescape(v[1]);
793                 });
794             }
795
796             return h;
797         },
798
799         setHash : function(n, v, e, p, d, s) {
800             var o = '';
801
802             each(v, function(v, k) {
803                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
804             });
805
806             this.set(n, o, e, p, d, s);
807         },
808
809         get : function(n) {
810             var c = document.cookie, e, p = n + "=", b;
811
812             // Strict mode
813             if (!c)
814                 return;
815
816             b = c.indexOf("; " + p);
817
818             if (b == -1) {
819                 b = c.indexOf(p);
820
821                 if (b != 0)
822                     return null;
823             } else
824                 b += 2;
825
826             e = c.indexOf(";", b);
827
828             if (e == -1)
829                 e = c.length;
830
831             return unescape(c.substring(b + p.length, e));
832         },
833
834         set : function(n, v, e, p, d, s) {
835             document.cookie = n + "=" + escape(v) +
836                 ((e) ? "; expires=" + e.toGMTString() : "") +
837                 ((p) ? "; path=" + escape(p) : "") +
838                 ((d) ? "; domain=" + d : "") +
839                 ((s) ? "; secure" : "");
840         },
841
842         remove : function(n, p) {
843             var d = new Date();
844
845             d.setTime(d.getTime() - 1000);
846
847             this.set(n, '', d, p, d);
848         }
58fb65 849     });
d9344f 850 })();
69d05c 851
a9251b 852 (function() {
T 853     function serialize(o, quote) {
854         var i, v, t;
855
856         quote = quote || '"';
d9344f 857
S 858         if (o == null)
859             return 'null';
860
861         t = typeof o;
862
863         if (t == 'string') {
864             v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
865
a9251b 866             return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
T 867                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
868                 if (quote === '"' && a === "'")
869                     return a;
870
d9344f 871                 i = v.indexOf(b);
S 872
873                 if (i + 1)
874                     return '\\' + v.charAt(i + 1);
875
876                 a = b.charCodeAt().toString(16);
877
878                 return '\\u' + '0000'.substring(a.length) + a;
a9251b 879             }) + quote;
d9344f 880         }
S 881
882         if (t == 'object') {
29da64 883             if (o.hasOwnProperty && o instanceof Array) {
d9344f 884                     for (i=0, v = '['; i<o.length; i++)
a9251b 885                         v += (i > 0 ? ',' : '') + serialize(o[i], quote);
d9344f 886
S 887                     return v + ']';
888                 }
889
890                 v = '{';
891
892                 for (i in o)
a9251b 893                     v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
d9344f 894
S 895                 return v + '}';
896         }
897
898         return '' + o;
a9251b 899     };
d9344f 900
a9251b 901     tinymce.util.JSON = {
T 902         serialize: serialize,
903
904         parse: function(s) {
905             try {
906                 return eval('(' + s + ')');
907             } catch (ex) {
908                 // Ignore
909             }
d9344f 910         }
S 911
a9251b 912         };
T 913 })();
d9344f 914 tinymce.create('static tinymce.util.XHR', {
S 915     send : function(o) {
916         var x, t, w = window, c = 0;
917
918         // Default settings
919         o.scope = o.scope || this;
920         o.success_scope = o.success_scope || o.scope;
921         o.error_scope = o.error_scope || o.scope;
922         o.async = o.async === false ? false : true;
923         o.data = o.data || '';
924
925         function get(s) {
926             x = 0;
927
928             try {
929                 x = new ActiveXObject(s);
930             } catch (ex) {
931             }
932
933             return x;
934         };
935
936         x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
937
938         if (x) {
939             if (x.overrideMimeType)
940                 x.overrideMimeType(o.content_type);
941
942             x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
943
944             if (o.content_type)
945                 x.setRequestHeader('Content-Type', o.content_type);
946
58fb65 947             x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
A 948
d9344f 949             x.send(o.data);
S 950
18240a 951             function ready() {
A 952                 if (!o.async || x.readyState == 4 || c++ > 10000) {
d9344f 953                     if (o.success && c < 10000 && x.status == 200)
S 954                         o.success.call(o.success_scope, '' + x.responseText, x, o);
955                     else if (o.error)
956                         o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
957
958                     x = null;
18240a 959                 } else
A 960                     w.setTimeout(ready, 10);
961             };
962
963             // Syncronous request
964             if (!o.async)
965                 return ready();
966
967             // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
968             t = w.setTimeout(ready, 10);
d9344f 969         }
58fb65 970     }
d9344f 971 });
69d05c 972
d9344f 973 (function() {
S 974     var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
975
976     tinymce.create('tinymce.util.JSONRequest', {
977         JSONRequest : function(s) {
978             this.settings = extend({
979             }, s);
980             this.count = 0;
981         },
982
983         send : function(o) {
984             var ecb = o.error, scb = o.success;
985
986             o = extend(this.settings, o);
987
988             o.success = function(c, x) {
989                 c = JSON.parse(c);
990
991                 if (typeof(c) == 'undefined') {
992                     c = {
993                         error : 'JSON Parse error.'
994                     };
995                 }
996
997                 if (c.error)
998                     ecb.call(o.error_scope || o.scope, c.error, x);
999                 else
1000                     scb.call(o.success_scope || o.scope, c.result);
1001             };
1002
1003             o.error = function(ty, x) {
a9251b 1004                 if (ecb)
T 1005                     ecb.call(o.error_scope || o.scope, ty, x);
d9344f 1006             };
S 1007
1008             o.data = JSON.serialize({
1009                 id : o.id || 'c' + (this.count++),
1010                 method : o.method,
1011                 params : o.params
1012             });
1013
1014             // JSON content type for Ruby on rails. Bug: #1883287
1015             o.content_type = 'application/json';
1016
1017             XHR.send(o);
1018         },
1019
1020         'static' : {
1021             sendRPC : function(o) {
1022                 return new tinymce.util.JSONRequest().send(o);
1023             }
1024         }
58fb65 1025     });
69d05c 1026 }());
A 1027 (function(tinymce) {
a9251b 1028     var namedEntities, baseEntities, reverseEntities,
T 1029         attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1030         textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1031         rawCharsRegExp = /[<>&\"\']/g,
1032         entityRegExp = /&(#)?([\w]+);/g,
1033         asciiMap = {
1034                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1035                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1036                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1037                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1038                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1039         };
1040
1041     // Raw entities
1042     baseEntities = {
1043         '"' : '&quot;',
1044         "'" : '&#39;',
1045         '<' : '&lt;',
1046         '>' : '&gt;',
1047         '&' : '&amp;'
1048     };
1049
1050     // Reverse lookup table for raw entities
1051     reverseEntities = {
1052         '&lt;' : '<',
1053         '&gt;' : '>',
1054         '&amp;' : '&',
1055         '&quot;' : '"',
1056         '&apos;' : "'"
1057     };
1058
1059     // Decodes text by using the browser
1060     function nativeDecode(text) {
1061         var elm;
1062
1063         elm = document.createElement("div");
1064         elm.innerHTML = text;
1065
1066         return elm.textContent || elm.innerText || text;
1067     };
1068
1069     // Build a two way lookup table for the entities
1070     function buildEntitiesLookup(items, radix) {
1071         var i, chr, entity, lookup = {};
1072
1073         if (items) {
1074             items = items.split(',');
1075             radix = radix || 10;
1076
1077             // Build entities lookup table
1078             for (i = 0; i < items.length; i += 2) {
1079                 chr = String.fromCharCode(parseInt(items[i], radix));
1080
1081                 // Only add non base entities
1082                 if (!baseEntities[chr]) {
1083                     entity = '&' + items[i + 1] + ';';
1084                     lookup[chr] = entity;
1085                     lookup[entity] = chr;
1086                 }
1087             }
1088
1089             return lookup;
1090         }
1091     };
1092
1093     // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1094     namedEntities = buildEntitiesLookup(
1095         '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1096         '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1097         '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1098         '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1099         '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1100         '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1101         '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1102         '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1103         '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1104         '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1105         'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1106         'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1107         't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1108         'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1109         'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1110         '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1111         '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1112         '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1113         '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1114         '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1115         'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1116         'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1117         'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1118         '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1119         '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1120     , 32);
1121
1122     tinymce.html = tinymce.html || {};
1123
1124     tinymce.html.Entities = {
1125         encodeRaw : function(text, attr) {
1126             return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1127                 return baseEntities[chr] || chr;
1128             });
1129         },
1130
1131         encodeAllRaw : function(text) {
1132             return ('' + text).replace(rawCharsRegExp, function(chr) {
1133                 return baseEntities[chr] || chr;
1134             });
1135         },
1136
1137         encodeNumeric : function(text, attr) {
1138             return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1139                 // Multi byte sequence convert it to a single entity
1140                 if (chr.length > 1)
1141                     return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1142
1143                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1144             });
1145         },
1146
1147         encodeNamed : function(text, attr, entities) {
1148             entities = entities || namedEntities;
1149
1150             return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1151                 return baseEntities[chr] || entities[chr] || chr;
1152             });
1153         },
1154
1155         getEncodeFunc : function(name, entities) {
1156             var Entities = tinymce.html.Entities;
1157
1158             entities = buildEntitiesLookup(entities) || namedEntities;
1159
1160             function encodeNamedAndNumeric(text, attr) {
1161                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1162                     return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1163                 });
1164             };
1165
1166             function encodeCustomNamed(text, attr) {
1167                 return Entities.encodeNamed(text, attr, entities);
1168             };
1169
1170             // Replace + with , to be compatible with previous TinyMCE versions
1171             name = tinymce.makeMap(name.replace(/\+/g, ','));
1172
1173             // Named and numeric encoder
1174             if (name.named && name.numeric)
1175                 return encodeNamedAndNumeric;
1176
1177             // Named encoder
1178             if (name.named) {
1179                 // Custom names
1180                 if (entities)
1181                     return encodeCustomNamed;
1182
1183                 return Entities.encodeNamed;
1184             }
1185
1186             // Numeric
1187             if (name.numeric)
1188                 return Entities.encodeNumeric;
1189
1190             // Raw encoder
1191             return Entities.encodeRaw;
1192         },
1193
1194         decode : function(text) {
1195             return text.replace(entityRegExp, function(all, numeric, value) {
1196                 if (numeric) {
1197                     value = parseInt(value);
1198
1199                     // Support upper UTF
1200                     if (value > 0xFFFF) {
1201                         value -= 0x10000;
1202
1203                         return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1204                     } else
1205                         return asciiMap[value] || String.fromCharCode(value);
1206                 }
1207
1208                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1209             });
1210         }
1211     };
1212 })(tinymce);
1213
1214 tinymce.html.Styles = function(settings, schema) {
1215     var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1216         urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1217         styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1218         trimRightRegExp = /\s+$/,
1219         urlColorRegExp = /rgb/,
1220         undef, i, encodingLookup = {}, encodingItems;
1221
1222     settings = settings || {};
1223
1224     encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
1225     for (i = 0; i < encodingItems.length; i++) {
1226         encodingLookup[encodingItems[i]] = '_' + i;
1227         encodingLookup['_' + i] = encodingItems[i];
1228     }
1229
1230     function toHex(match, r, g, b) {
1231         function hex(val) {
1232             val = parseInt(val).toString(16);
1233
1234             return val.length > 1 ? val : '0' + val; // 0 -> 00
1235         };
1236
1237         return '#' + hex(r) + hex(g) + hex(b);
1238     };
1239
1240     return {
1241         toHex : function(color) {
1242             return color.replace(rgbRegExp, toHex);
1243         },
1244
1245         parse : function(css) {
1246             var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1247
1248             function compress(prefix, suffix) {
1249                 var top, right, bottom, left;
1250
1251                 // Get values and check it it needs compressing
1252                 top = styles[prefix + '-top' + suffix];
1253                 if (!top)
1254                     return;
1255
1256                 right = styles[prefix + '-right' + suffix];
1257                 if (top != right)
1258                     return;
1259
1260                 bottom = styles[prefix + '-bottom' + suffix];
1261                 if (right != bottom)
1262                     return;
1263
1264                 left = styles[prefix + '-left' + suffix];
1265                 if (bottom != left)
1266                     return;
1267
1268                 // Compress
1269                 styles[prefix + suffix] = left;
1270                 delete styles[prefix + '-top' + suffix];
1271                 delete styles[prefix + '-right' + suffix];
1272                 delete styles[prefix + '-bottom' + suffix];
1273                 delete styles[prefix + '-left' + suffix];
1274             };
1275
1276             function canCompress(key) {
1277                 var value = styles[key], i;
1278
1279                 if (!value || value.indexOf(' ') < 0)
1280                     return;
1281
1282                 value = value.split(' ');
1283                 i = value.length;
1284                 while (i--) {
1285                     if (value[i] !== value[0])
1286                         return false;
1287                 }
1288
1289                 styles[key] = value[0];
1290
1291                 return true;
1292             };
1293
1294             function compress2(target, a, b, c) {
1295                 if (!canCompress(a))
1296                     return;
1297
1298                 if (!canCompress(b))
1299                     return;
1300
1301                 if (!canCompress(c))
1302                     return;
1303
1304                 // Compress
1305                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1306                 delete styles[a];
1307                 delete styles[b];
1308                 delete styles[c];
1309             };
1310
1311             // Encodes the specified string by replacing all \" \' ; : with _<num>
1312             function encode(str) {
1313                 isEncoded = true;
1314
1315                 return encodingLookup[str];
1316             };
1317
1318             // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1319             // It will also decode the \" \' if keep_slashes is set to fale or omitted
1320             function decode(str, keep_slashes) {
1321                 if (isEncoded) {
1322                     str = str.replace(/_[0-9]/g, function(str) {
1323                         return encodingLookup[str];
1324                     });
1325                 }
1326
1327                 if (!keep_slashes)
1328                     str = str.replace(/\\([\'\";:])/g, "$1");
1329
1330                 return str;
1331             }
1332
1333             if (css) {
1334                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1335                 css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1336                     return str.replace(/[;:]/g, encode);
1337                 });
1338
1339                 // Parse styles
1340                 while (matches = styleRegExp.exec(css)) {
1341                     name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1342                     value = matches[2].replace(trimRightRegExp, '');
1343
1344                     if (name && value.length > 0) {
1345                         // Opera will produce 700 instead of bold in their style values
1346                         if (name === 'font-weight' && value === '700')
1347                             value = 'bold';
1348                         else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1349                             value = value.toLowerCase();        
1350
1351                         // Convert RGB colors to HEX
1352                         value = value.replace(rgbRegExp, toHex);
1353
1354                         // Convert URLs and force them into url('value') format
1355                         value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1356                             str = str || str2;
1357
1358                             if (str) {
1359                                 str = decode(str);
1360
1361                                 // Force strings into single quote format
1362                                 return "'" + str.replace(/\'/g, "\\'") + "'";
1363                             }
1364
1365                             url = decode(url || url2 || url3);
1366
1367                             // Convert the URL to relative/absolute depending on config
1368                             if (urlConverter)
1369                                 url = urlConverter.call(urlConverterScope, url, 'style');
1370
1371                             // Output new URL format
1372                             return "url('" + url.replace(/\'/g, "\\'") + "')";
1373                         });
1374
1375                         styles[name] = isEncoded ? decode(value, true) : value;
1376                     }
1377
1378                     styleRegExp.lastIndex = matches.index + matches[0].length;
1379                 }
1380
1381                 // Compress the styles to reduce it's size for example IE will expand styles
1382                 compress("border", "");
1383                 compress("border", "-width");
1384                 compress("border", "-color");
1385                 compress("border", "-style");
1386                 compress("padding", "");
1387                 compress("margin", "");
1388                 compress2('border', 'border-width', 'border-style', 'border-color');
1389
1390                 // Remove pointless border, IE produces these
1391                 if (styles.border === 'medium none')
1392                     delete styles.border;
1393             }
1394
1395             return styles;
1396         },
1397
1398         serialize : function(styles, element_name) {
1399             var css = '', name, value;
1400
1401             function serializeStyles(name) {
1402                 var styleList, i, l, name, value;
1403
1404                 styleList = schema.styles[name];
1405                 if (styleList) {
1406                     for (i = 0, l = styleList.length; i < l; i++) {
1407                         name = styleList[i];
1408                         value = styles[name];
1409
1410                         if (value !== undef && value.length > 0)
1411                             css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1412                     }
1413                 }
1414             };
1415
1416             // Serialize styles according to schema
1417             if (element_name && schema && schema.styles) {
1418                 // Serialize global styles and element specific styles
1419                 serializeStyles('*');
1420                 serializeStyles(name);
1421             } else {
1422                 // Output the styles in the order they are inside the object
1423                 for (name in styles) {
1424                     value = styles[name];
1425
1426                     if (value !== undef && value.length > 0)
1427                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1428                 }
1429             }
1430
1431             return css;
1432         }
1433     };
1434 };
1435
1436 (function(tinymce) {
1437     var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
1438         whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1439
1440     function split(str, delim) {
1441         return str.split(delim || ',');
1442     };
1443
1444     function unpack(lookup, data) {
1445         var key, elements = {};
1446
1447         function replace(value) {
1448             return value.replace(/[A-Z]+/g, function(key) {
1449                 return replace(lookup[key]);
1450             });
1451         };
1452
1453         // Unpack lookup
1454         for (key in lookup) {
1455             if (lookup.hasOwnProperty(key))
1456                 lookup[key] = replace(lookup[key]);
1457         }
1458
1459         // Unpack and parse data into object map
1460         replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1461             attributes = split(attributes, '|');
1462
1463             elements[name] = {
1464                 attributes : makeMap(attributes),
1465                 attributesOrder : attributes,
1466                 children : makeMap(children, '|', {'#comment' : {}})
1467             }
1468         });
1469
1470         return elements;
1471     };
1472
1473     // Build a lookup table for block elements both lowercase and uppercase
1474     blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + 
1475                         'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + 
1476                         'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1477     blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1478
1479     // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1480     transitional = unpack({
1481         Z : 'H|K|N|O|P',
1482         Y : 'X|form|R|Q',
1483         ZG : 'E|span|width|align|char|charoff|valign',
1484         X : 'p|T|div|U|W|isindex|fieldset|table',
1485         ZF : 'E|align|char|charoff|valign',
1486         W : 'pre|hr|blockquote|address|center|noframes',
1487         ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1488         ZD : '[E][S]',
1489         U : 'ul|ol|dl|menu|dir',
1490         ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1491         T : 'h1|h2|h3|h4|h5|h6',
1492         ZB : 'X|S|Q',
1493         S : 'R|P',
1494         ZA : 'a|G|J|M|O|P',
1495         R : 'a|H|K|N|O',
1496         Q : 'noscript|P',
1497         P : 'ins|del|script',
1498         O : 'input|select|textarea|label|button',
1499         N : 'M|L',
1500         M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1501         L : 'sub|sup',
1502         K : 'J|I',
1503         J : 'tt|i|b|u|s|strike',
1504         I : 'big|small|font|basefont',
1505         H : 'G|F',
1506         G : 'br|span|bdo',
1507         F : 'object|applet|img|map|iframe',
1508         E : 'A|B|C',
1509         D : 'accesskey|tabindex|onfocus|onblur',
1510         C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1511         B : 'lang|xml:lang|dir',
1512         A : 'id|class|style|title'
1513     }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
1514         'style[B|id|type|media|title|xml:space][]' + 
1515         'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
1516         'param[id|name|value|valuetype|type][]' + 
1517         'p[E|align][#|S]' + 
1518         'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
1519         'br[A|clear][]' + 
1520         'span[E][#|S]' + 
1521         'bdo[A|C|B][#|S]' + 
1522         'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
1523         'h1[E|align][#|S]' + 
1524         'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
1525         'map[B|C|A|name][X|form|Q|area]' + 
1526         'h2[E|align][#|S]' + 
1527         'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
1528         'h3[E|align][#|S]' + 
1529         'tt[E][#|S]' + 
1530         'i[E][#|S]' + 
1531         'b[E][#|S]' + 
1532         'u[E][#|S]' + 
1533         's[E][#|S]' + 
1534         'strike[E][#|S]' + 
1535         'big[E][#|S]' + 
1536         'small[E][#|S]' + 
1537         'font[A|B|size|color|face][#|S]' + 
1538         'basefont[id|size|color|face][]' + 
1539         'em[E][#|S]' + 
1540         'strong[E][#|S]' + 
1541         'dfn[E][#|S]' + 
1542         'code[E][#|S]' + 
1543         'q[E|cite][#|S]' + 
1544         'samp[E][#|S]' + 
1545         'kbd[E][#|S]' + 
1546         'var[E][#|S]' + 
1547         'cite[E][#|S]' + 
1548         'abbr[E][#|S]' + 
1549         'acronym[E][#|S]' + 
1550         'sub[E][#|S]' + 
1551         'sup[E][#|S]' + 
1552         'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
1553         'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
1554         'optgroup[E|disabled|label][option]' + 
1555         'option[E|selected|disabled|label|value][]' + 
1556         'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
1557         'label[E|for|accesskey|onfocus|onblur][#|S]' + 
1558         'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
1559         'h4[E|align][#|S]' + 
1560         'ins[E|cite|datetime][#|Y]' + 
1561         'h5[E|align][#|S]' + 
1562         'del[E|cite|datetime][#|Y]' + 
1563         'h6[E|align][#|S]' + 
1564         'div[E|align][#|Y]' + 
1565         'ul[E|type|compact][li]' + 
1566         'li[E|type|value][#|Y]' + 
1567         'ol[E|type|compact|start][li]' + 
1568         'dl[E|compact][dt|dd]' + 
1569         'dt[E][#|S]' + 
1570         'dd[E][#|Y]' + 
1571         'menu[E|compact][li]' + 
1572         'dir[E|compact][li]' + 
1573         'pre[E|width|xml:space][#|ZA]' + 
1574         'hr[E|align|noshade|size|width][]' + 
1575         'blockquote[E|cite][#|Y]' + 
1576         'address[E][#|S|p]' + 
1577         'center[E][#|Y]' + 
1578         'noframes[E][#|Y]' + 
1579         'isindex[A|B|prompt][]' + 
1580         'fieldset[E][#|legend|Y]' + 
1581         'legend[E|accesskey|align][#|S]' + 
1582         'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
1583         'caption[E|align][#|S]' + 
1584         'col[ZG][]' + 
1585         'colgroup[ZG][col]' + 
1586         'thead[ZF][tr]' + 
1587         'tr[ZF|bgcolor][th|td]' + 
1588         'th[E|ZE][#|Y]' + 
1589         'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
1590         'noscript[E][#|Y]' + 
1591         'td[E|ZE][#|Y]' + 
1592         'tfoot[ZF][tr]' + 
1593         'tbody[ZF][tr]' + 
1594         'area[E|D|shape|coords|href|nohref|alt|target][]' + 
1595         'base[id|href|target][]' + 
1596         'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1597     );
1598
1599     boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
1600     shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1601     nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
1602     whiteSpaceElementsMap = makeMap('pre,script,style');
1603     selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1604
1605     tinymce.html.Schema = function(settings) {
1606         var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1607
1608         settings = settings || {};
1609
1610         // Allow all elements and attributes if verify_html is set to false
1611         if (settings.verify_html === false)
1612             settings.valid_elements = '*[*]';
1613
1614         // Build styles list
1615         if (settings.valid_styles) {
1616             validStyles = {};
1617
1618             // Convert styles into a rule list
1619             each(settings.valid_styles, function(value, key) {
1620                 validStyles[key] = tinymce.explode(value);
1621             });
1622         }
1623
1624         // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1625         function patternToRegExp(str) {
1626             return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1627         };
1628
1629         // Parses the specified valid_elements string and adds to the current rules
1630         // This function is a bit hard to read since it's heavily optimized for speed
1631         function addValidElements(valid_elements) {
1632             var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1633                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1634                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1635                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1636                 hasPatternsRegExp = /[*?+]/;
1637
1638             if (valid_elements) {
1639                 // Split valid elements into an array with rules
1640                 valid_elements = split(valid_elements);
1641
1642                 if (elements['@']) {
1643                     globalAttributes = elements['@'].attributes;
1644                     globalAttributesOrder = elements['@'].attributesOrder;
1645                 }
1646
1647                 // Loop all rules
1648                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1649                     // Parse element rule
1650                     matches = elementRuleRegExp.exec(valid_elements[ei]);
1651                     if (matches) {
1652                         // Setup local names for matches
1653                         prefix = matches[1];
1654                         elementName = matches[2];
1655                         outputName = matches[3];
1656                         attrData = matches[4];
1657
1658                         // Create new attributes and attributesOrder
1659                         attributes = {};
1660                         attributesOrder = [];
1661
1662                         // Create the new element
1663                         element = {
1664                             attributes : attributes,
1665                             attributesOrder : attributesOrder
1666                         };
1667
1668                         // Padd empty elements prefix
1669                         if (prefix === '#')
1670                             element.paddEmpty = true;
1671
1672                         // Remove empty elements prefix
1673                         if (prefix === '-')
1674                             element.removeEmpty = true;
1675
1676                         // Copy attributes from global rule into current rule
1677                         if (globalAttributes) {
1678                             for (key in globalAttributes)
1679                                 attributes[key] = globalAttributes[key];
1680
1681                             attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
1682                         }
1683
1684                         // Attributes defined
1685                         if (attrData) {
1686                             attrData = split(attrData, '|');
1687                             for (ai = 0, al = attrData.length; ai < al; ai++) {
1688                                 matches = attrRuleRegExp.exec(attrData[ai]);
1689                                 if (matches) {
1690                                     attr = {};
1691                                     attrType = matches[1];
1692                                     attrName = matches[2].replace(/::/g, ':');
1693                                     prefix = matches[3];
1694                                     value = matches[4];
1695
1696                                     // Required
1697                                     if (attrType === '!') {
1698                                         element.attributesRequired = element.attributesRequired || [];
1699                                         element.attributesRequired.push(attrName);
1700                                         attr.required = true;
1701                                     }
1702
1703                                     // Denied from global
1704                                     if (attrType === '-') {
1705                                         delete attributes[attrName];
1706                                         attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
1707                                         continue;
1708                                     }
1709
1710                                     // Default value
1711                                     if (prefix) {
1712                                         // Default value
1713                                         if (prefix === '=') {
1714                                             element.attributesDefault = element.attributesDefault || [];
1715                                             element.attributesDefault.push({name: attrName, value: value});
1716                                             attr.defaultValue = value;
1717                                         }
1718
1719                                         // Forced value
1720                                         if (prefix === ':') {
1721                                             element.attributesForced = element.attributesForced || [];
1722                                             element.attributesForced.push({name: attrName, value: value});
1723                                             attr.forcedValue = value;
1724                                         }
1725
1726                                         // Required values
1727                                         if (prefix === '<')
1728                                             attr.validValues = makeMap(value, '?');
1729                                     }
1730
1731                                     // Check for attribute patterns
1732                                     if (hasPatternsRegExp.test(attrName)) {
1733                                         element.attributePatterns = element.attributePatterns || [];
1734                                         attr.pattern = patternToRegExp(attrName);
1735                                         element.attributePatterns.push(attr);
1736                                     } else {
1737                                         // Add attribute to order list if it doesn't already exist
1738                                         if (!attributes[attrName])
1739                                             attributesOrder.push(attrName);
1740
1741                                         attributes[attrName] = attr;
1742                                     }
1743                                 }
1744                             }
1745                         }
1746
1747                         // Global rule, store away these for later usage
1748                         if (!globalAttributes && elementName == '@') {
1749                             globalAttributes = attributes;
1750                             globalAttributesOrder = attributesOrder;
1751                         }
1752
1753                         // Handle substitute elements such as b/strong
1754                         if (outputName) {
1755                             element.outputName = elementName;
1756                             elements[outputName] = element;
1757                         }
1758
1759                         // Add pattern or exact element
1760                         if (hasPatternsRegExp.test(elementName)) {
1761                             element.pattern = patternToRegExp(elementName);
1762                             patternElements.push(element);
1763                         } else
1764                             elements[elementName] = element;
1765                     }
1766                 }
1767             }
1768         };
1769
1770         function setValidElements(valid_elements) {
1771             elements = {};
1772             patternElements = [];
1773
1774             addValidElements(valid_elements);
1775
1776             each(transitional, function(element, name) {
1777                 children[name] = element.children;
1778             });
1779         };
1780
1781         // Adds custom non HTML elements to the schema
1782         function addCustomElements(custom_elements) {
1783             var customElementRegExp = /^(~)?(.+)$/;
1784
1785             if (custom_elements) {
1786                 each(split(custom_elements), function(rule) {
1787                     var matches = customElementRegExp.exec(rule),
1788                         cloneName = matches[1] === '~' ? 'span' : 'div',
1789                         name = matches[2];
1790
1791                     children[name] = children[cloneName];
1792
1793                     // Add custom elements at span/div positions
1794                     each(children, function(element, child) {
1795                         if (element[cloneName])
1796                             element[name] = element[cloneName];
1797                     });
1798                 });
1799             }
1800         };
1801
1802         // Adds valid children to the schema object
1803         function addValidChildren(valid_children) {
1804             var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
1805
1806             if (valid_children) {
1807                 each(split(valid_children), function(rule) {
1808                     var matches = childRuleRegExp.exec(rule), parent, prefix;
1809
1810                     if (matches) {
1811                         prefix = matches[1];
1812
1813                         // Add/remove items from default
1814                         if (prefix)
1815                             parent = children[matches[2]];
1816                         else
1817                             parent = children[matches[2]] = {'#comment' : {}};
1818
1819                         parent = children[matches[2]];
1820
1821                         each(split(matches[3], '|'), function(child) {
1822                             if (prefix === '-')
1823                                 delete parent[child];
1824                             else
1825                                 parent[child] = {};
1826                         });
1827                     }
1828                 });
1829             }
1830         }
1831
1832         if (!settings.valid_elements) {
1833             // No valid elements defined then clone the elements from the transitional spec
1834             each(transitional, function(element, name) {
1835                 elements[name] = {
1836                     attributes : element.attributes,
1837                     attributesOrder : element.attributesOrder
1838                 };
1839
1840                 children[name] = element.children;
1841             });
1842
1843             // Switch these
1844             each(split('strong/b,em/i'), function(item) {
1845                 item = split(item, '/');
1846                 elements[item[1]].outputName = item[0];
1847             });
1848
1849             // Add default alt attribute for images
1850             elements.img.attributesDefault = [{name: 'alt', value: ''}];
1851
1852             // Remove these if they are empty by default
1853             each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
1854                 elements[name].removeEmpty = true;
1855             });
1856
1857             // Padd these by default
1858             each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
1859                 elements[name].paddEmpty = true;
1860             });
1861         } else
1862             setValidElements(settings.valid_elements);
1863
1864         addCustomElements(settings.custom_elements);
1865         addValidChildren(settings.valid_children);
1866         addValidElements(settings.extended_valid_elements);
1867
1868         // Todo: Remove this when we fix list handling to be valid
1869         addValidChildren('+ol[ul|ol],+ul[ul|ol]');
1870
1871         // Delete invalid elements
1872         if (settings.invalid_elements) {
1873             tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
1874                 if (elements[item])
1875                     delete elements[item];
1876             });
1877         }
1878
1879         self.children = children;
1880
1881         self.styles = validStyles;
1882
1883         self.getBoolAttrs = function() {
1884             return boolAttrMap;
1885         };
1886
1887         self.getBlockElements = function() {
1888             return blockElementsMap;
1889         };
1890
1891         self.getShortEndedElements = function() {
1892             return shortEndedElementsMap;
1893         };
1894
1895         self.getSelfClosingElements = function() {
1896             return selfClosingElementsMap;
1897         };
1898
1899         self.getNonEmptyElements = function() {
1900             return nonEmptyElementsMap;
1901         };
1902
1903         self.getWhiteSpaceElements = function() {
1904             return whiteSpaceElementsMap;
1905         };
1906
1907         self.isValidChild = function(name, child) {
1908             var parent = children[name];
1909
1910             return !!(parent && parent[child]);
1911         };
1912
1913         self.getElementRule = function(name) {
1914             var element = elements[name], i;
1915
1916             // Exact match found
1917             if (element)
1918                 return element;
1919
1920             // No exact match then try the patterns
1921             i = patternElements.length;
1922             while (i--) {
1923                 element = patternElements[i];
1924
1925                 if (element.pattern.test(name))
1926                     return element;
1927             }
1928         };
1929
1930         self.addValidElements = addValidElements;
1931
1932         self.setValidElements = setValidElements;
1933
1934         self.addCustomElements = addCustomElements;
1935
1936         self.addValidChildren = addValidChildren;
1937     };
1938
1939     // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
1940     tinymce.html.Schema.boolAttrMap = boolAttrMap;
1941     tinymce.html.Schema.blockElementsMap = blockElementsMap;
1942 })(tinymce);
1943
1944 (function(tinymce) {
1945     tinymce.html.SaxParser = function(settings, schema) {
1946         var self = this, noop = function() {};
1947
1948         settings = settings || {};
1949         self.schema = schema = schema || new tinymce.html.Schema();
1950
1951         if (settings.fix_self_closing !== false)
1952             settings.fix_self_closing = true;
1953
1954         // Add handler functions from settings and setup default handlers
1955         tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
1956             if (name)
1957                 self[name] = settings[name] || noop;
1958         });
1959
1960         self.parse = function(html) {
1961             var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
1962                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
1963                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
1964                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
1965
1966             function processEndTag(name) {
1967                 var pos, i;
1968
1969                 // Find position of parent of the same type
1970                 pos = stack.length;
1971                 while (pos--) {
1972                     if (stack[pos].name === name)
1973                         break;                        
1974                 }
1975
1976                 // Found parent
1977                 if (pos >= 0) {
1978                     // Close all the open elements
1979                     for (i = stack.length - 1; i >= pos; i--) {
1980                         name = stack[i];
1981
1982                         if (name.valid)
1983                             self.end(name.name);
1984                     }
1985
1986                     // Remove the open elements from the stack
1987                     stack.length = pos;
1988                 }
1989             };
1990
1991             // Precompile RegExps and map objects
1992             tokenRegExp = new RegExp('<(?:' +
1993                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
1994                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
1995                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
1996                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
1997                 '(?:\\/([^>]+)>)|' + // End element
1998                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
1999             ')', 'g');
2000
2001             attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2002             specialElements = {
2003                 'script' : /<\/script[^>]*>/gi,
2004                 'style' : /<\/style[^>]*>/gi,
2005                 'noscript' : /<\/noscript[^>]*>/gi
2006             };
2007
2008             // Setup lookup tables for empty elements and boolean attributes
2009             shortEndedElements = schema.getShortEndedElements();
2010             selfClosing = schema.getSelfClosingElements();
2011             fillAttrsMap = schema.getBoolAttrs();
2012             validate = settings.validate;
2013             fixSelfClosing = settings.fix_self_closing;
2014
2015             while (matches = tokenRegExp.exec(html)) {
2016                 // Text
2017                 if (index < matches.index)
2018                     self.text(decode(html.substr(index, matches.index - index)));
2019
2020                 if (value = matches[6]) { // End element
2021                     processEndTag(value.toLowerCase());
2022                 } else if (value = matches[7]) { // Start element
2023                     value = value.toLowerCase();
2024                     isShortEnded = value in shortEndedElements;
2025
2026                     // Is self closing tag for example an <li> after an open <li>
2027                     if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2028                         processEndTag(value);
2029
2030                     // Validate element
2031                     if (!validate || (elementRule = schema.getElementRule(value))) {
2032                         isValidElement = true;
2033
2034                         // Grab attributes map and patters when validation is enabled
2035                         if (validate) {
2036                             validAttributesMap = elementRule.attributes;
2037                             validAttributePatterns = elementRule.attributePatterns;
2038                         }
2039
2040                         // Parse attributes
2041                         if (attribsValue = matches[8]) {
2042                             attrList = [];
2043                             attrList.map = {};
2044
2045                             attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2046                                 var attrRule, i;
2047
2048                                 name = name.toLowerCase();
2049                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2050
2051                                 // Validate name and value
2052                                 if (validate && name.indexOf('data-') !== 0) {
2053                                     attrRule = validAttributesMap[name];
2054
2055                                     // Find rule by pattern matching
2056                                     if (!attrRule && validAttributePatterns) {
2057                                         i = validAttributePatterns.length;
2058                                         while (i--) {
2059                                             attrRule = validAttributePatterns[i];
2060                                             if (attrRule.pattern.test(name))
2061                                                 break;
2062                                         }
2063
2064                                         // No rule matched
2065                                         if (i === -1)
2066                                             attrRule = null;
2067                                     }
2068
2069                                     // No attribute rule found
2070                                     if (!attrRule)
2071                                         return;
2072
2073                                     // Validate value
2074                                     if (attrRule.validValues && !(value in attrRule.validValues))
2075                                         return;
2076                                 }
2077
2078                                 // Add attribute to list and map
2079                                 attrList.map[name] = value;
2080                                 attrList.push({
2081                                     name: name,
2082                                     value: value
2083                                 });
2084                             });
2085                         } else {
2086                             attrList = [];
2087                             attrList.map = {};
2088                         }
2089
2090                         // Process attributes if validation is enabled
2091                         if (validate) {
2092                             attributesRequired = elementRule.attributesRequired;
2093                             attributesDefault = elementRule.attributesDefault;
2094                             attributesForced = elementRule.attributesForced;
2095
2096                             // Handle forced attributes
2097                             if (attributesForced) {
2098                                 i = attributesForced.length;
2099                                 while (i--) {
2100                                     attr = attributesForced[i];
2101                                     name = attr.name;
2102                                     attrValue = attr.value;
2103
2104                                     if (attrValue === '{$uid}')
2105                                         attrValue = 'mce_' + idCount++;
2106
2107                                     attrList.map[name] = attrValue;
2108                                     attrList.push({name: name, value: attrValue});
2109                                 }
2110                             }
2111
2112                             // Handle default attributes
2113                             if (attributesDefault) {
2114                                 i = attributesDefault.length;
2115                                 while (i--) {
2116                                     attr = attributesDefault[i];
2117                                     name = attr.name;
2118
2119                                     if (!(name in attrList.map)) {
2120                                         attrValue = attr.value;
2121
2122                                         if (attrValue === '{$uid}')
2123                                             attrValue = 'mce_' + idCount++;
2124
2125                                         attrList.map[name] = attrValue;
2126                                         attrList.push({name: name, value: attrValue});
2127                                     }
2128                                 }
2129                             }
2130
2131                             // Handle required attributes
2132                             if (attributesRequired) {
2133                                 i = attributesRequired.length;
2134                                 while (i--) {
2135                                     if (attributesRequired[i] in attrList.map)
2136                                         break;
2137                                 }
2138
2139                                 // None of the required attributes where found
2140                                 if (i === -1)
2141                                     isValidElement = false;
2142                             }
2143
2144                             // Invalidate element if it's marked as bogus
2145                             if (attrList.map['data-mce-bogus'])
2146                                 isValidElement = false;
2147                         }
2148
2149                         if (isValidElement)
2150                             self.start(value, attrList, isShortEnded);
2151                     } else
2152                         isValidElement = false;
2153
2154                     // Treat script, noscript and style a bit different since they may include code that looks like elements
2155                     if (endRegExp = specialElements[value]) {
2156                         endRegExp.lastIndex = index = matches.index + matches[0].length;
2157
2158                         if (matches = endRegExp.exec(html)) {
2159                             if (isValidElement)
2160                                 text = html.substr(index, matches.index - index);
2161
2162                             index = matches.index + matches[0].length;
2163                         } else {
2164                             text = html.substr(index);
2165                             index = html.length;
2166                         }
2167
2168                         if (isValidElement && text.length > 0)
2169                             self.text(text, true);
2170
2171                         if (isValidElement)
2172                             self.end(value);
2173
2174                         tokenRegExp.lastIndex = index;
2175                         continue;
2176                     }
2177
2178                     // Push value on to stack
2179                     if (!isShortEnded) {
2180                         if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2181                             stack.push({name: value, valid: isValidElement});
2182                         else if (isValidElement)
2183                             self.end(value);
2184                     }
2185                 } else if (value = matches[1]) { // Comment
2186                     self.comment(value);
2187                 } else if (value = matches[2]) { // CDATA
2188                     self.cdata(value);
2189                 } else if (value = matches[3]) { // DOCTYPE
2190                     self.doctype(value);
2191                 } else if (value = matches[4]) { // PI
2192                     self.pi(value, matches[5]);
2193                 }
2194
2195                 index = matches.index + matches[0].length;
2196             }
2197
2198             // Text
2199             if (index < html.length)
2200                 self.text(decode(html.substr(index)));
2201
2202             // Close any open elements
2203             for (i = stack.length - 1; i >= 0; i--) {
2204                 value = stack[i];
2205
2206                 if (value.valid)
2207                     self.end(value.name);
2208             }
2209         };
2210     }
2211 })(tinymce);
2212
2213 (function(tinymce) {
2214     var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2215         '#text' : 3,
2216         '#comment' : 8,
2217         '#cdata' : 4,
2218         '#pi' : 7,
2219         '#doctype' : 10,
2220         '#document-fragment' : 11
2221     };
2222
2223     // Walks the tree left/right
2224     function walk(node, root_node, prev) {
2225         var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2226
2227         // Walk into nodes if it has a start
2228         if (node[startName])
2229             return node[startName];
2230
2231         // Return the sibling if it has one
2232         if (node !== root_node) {
2233             sibling = node[siblingName];
2234
2235             if (sibling)
2236                 return sibling;
2237
2238             // Walk up the parents to look for siblings
2239             for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2240                 sibling = parent[siblingName];
2241
2242                 if (sibling)
2243                     return sibling;
2244             }
2245         }
2246     };
2247
2248     function Node(name, type) {
2249         this.name = name;
2250         this.type = type;
2251
2252         if (type === 1) {
2253             this.attributes = [];
2254             this.attributes.map = {};
2255         }
2256     }
2257
2258     tinymce.extend(Node.prototype, {
2259         replace : function(node) {
2260             var self = this;
2261
2262             if (node.parent)
2263                 node.remove();
2264
2265             self.insert(node, self);
2266             self.remove();
2267
2268             return self;
2269         },
2270
2271         attr : function(name, value) {
2272             var self = this, attrs, i, undef;
2273
2274             if (typeof name !== "string") {
2275                 for (i in name)
2276                     self.attr(i, name[i]);
2277
2278                 return self;
2279             }
2280
2281             if (attrs = self.attributes) {
2282                 if (value !== undef) {
2283                     // Remove attribute
2284                     if (value === null) {
2285                         if (name in attrs.map) {
2286                             delete attrs.map[name];
2287
2288                             i = attrs.length;
2289                             while (i--) {
2290                                 if (attrs[i].name === name) {
2291                                     attrs = attrs.splice(i, 1);
2292                                     return self;
2293                                 }
2294                             }
2295                         }
2296
2297                         return self;
2298                     }
2299
2300                     // Set attribute
2301                     if (name in attrs.map) {
2302                         // Set attribute
2303                         i = attrs.length;
2304                         while (i--) {
2305                             if (attrs[i].name === name) {
2306                                 attrs[i].value = value;
2307                                 break;
2308                             }
2309                         }
2310                     } else
2311                         attrs.push({name: name, value: value});
2312
2313                     attrs.map[name] = value;
2314
2315                     return self;
2316                 } else {
2317                     return attrs.map[name];
2318                 }
2319             }
2320         },
2321
2322         clone : function() {
2323             var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2324
2325             // Clone element attributes
2326             if (selfAttrs = self.attributes) {
2327                 cloneAttrs = [];
2328                 cloneAttrs.map = {};
2329
2330                 for (i = 0, l = selfAttrs.length; i < l; i++) {
2331                     selfAttr = selfAttrs[i];
2332
2333                     // Clone everything except id
2334                     if (selfAttr.name !== 'id') {
2335                         cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2336                         cloneAttrs.map[selfAttr.name] = selfAttr.value;
2337                     }
2338                 }
2339
2340                 clone.attributes = cloneAttrs;
2341             }
2342
2343             clone.value = self.value;
2344             clone.shortEnded = self.shortEnded;
2345
2346             return clone;
2347         },
2348
2349         wrap : function(wrapper) {
2350             var self = this;
2351
2352             self.parent.insert(wrapper, self);
2353             wrapper.append(self);
2354
2355             return self;
2356         },
2357
2358         unwrap : function() {
2359             var self = this, node, next;
2360
2361             for (node = self.firstChild; node; ) {
2362                 next = node.next;
2363                 self.insert(node, self, true);
2364                 node = next;
2365             }
2366
2367             self.remove();
2368         },
2369
2370         remove : function() {
2371             var self = this, parent = self.parent, next = self.next, prev = self.prev;
2372
2373             if (parent) {
2374                 if (parent.firstChild === self) {
2375                     parent.firstChild = next;
2376
2377                     if (next)
2378                         next.prev = null;
2379                 } else {
2380                     prev.next = next;
2381                 }
2382
2383                 if (parent.lastChild === self) {
2384                     parent.lastChild = prev;
2385
2386                     if (prev)
2387                         prev.next = null;
2388                 } else {
2389                     next.prev = prev;
2390                 }
2391
2392                 self.parent = self.next = self.prev = null;
2393             }
2394
2395             return self;
2396         },
2397
2398         append : function(node) {
2399             var self = this, last;
2400
2401             if (node.parent)
2402                 node.remove();
2403
2404             last = self.lastChild;
2405             if (last) {
2406                 last.next = node;
2407                 node.prev = last;
2408                 self.lastChild = node;
2409             } else
2410                 self.lastChild = self.firstChild = node;
2411
2412             node.parent = self;
2413
2414             return node;
2415         },
2416
2417         insert : function(node, ref_node, before) {
2418             var parent;
2419
2420             if (node.parent)
2421                 node.remove();
2422
2423             parent = ref_node.parent || this;
2424
2425             if (before) {
2426                 if (ref_node === parent.firstChild)
2427                     parent.firstChild = node;
2428                 else
2429                     ref_node.prev.next = node;
2430
2431                 node.prev = ref_node.prev;
2432                 node.next = ref_node;
2433                 ref_node.prev = node;
2434             } else {
2435                 if (ref_node === parent.lastChild)
2436                     parent.lastChild = node;
2437                 else
2438                     ref_node.next.prev = node;
2439
2440                 node.next = ref_node.next;
2441                 node.prev = ref_node;
2442                 ref_node.next = node;
2443             }
2444
2445             node.parent = parent;
2446
2447             return node;
2448         },
2449
2450         getAll : function(name) {
2451             var self = this, node, collection = [];
2452
2453             for (node = self.firstChild; node; node = walk(node, self)) {
2454                 if (node.name === name)
2455                     collection.push(node);
2456             }
2457
2458             return collection;
2459         },
2460
2461         empty : function() {
2462             var self = this, nodes, i, node;
2463
2464             // Remove all children
2465             if (self.firstChild) {
2466                 nodes = [];
2467
2468                 // Collect the children
2469                 for (node = self.firstChild; node; node = walk(node, self))
2470                     nodes.push(node);
2471
2472                 // Remove the children
2473                 i = nodes.length;
2474                 while (i--) {
2475                     node = nodes[i];
2476                     node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2477                 }
2478             }
2479
2480             self.firstChild = self.lastChild = null;
2481
2482             return self;
2483         },
2484
2485         isEmpty : function(elements) {
2486             var self = this, node = self.firstChild, i, name;
2487
2488             if (node) {
2489                 do {
2490                     if (node.type === 1) {
2491                         // Ignore bogus elements
2492                         if (node.attributes.map['data-mce-bogus'])
2493                             continue;
2494
2495                         // Keep empty elements like <img />
2496                         if (elements[node.name])
2497                             return false;
2498
2499                         // Keep elements with data attributes or name attribute like <a name="1"></a>
2500                         i = node.attributes.length;
2501                         while (i--) {
2502                             name = node.attributes[i].name;
2503                             if (name === "name" || name.indexOf('data-') === 0)
2504                                 return false;
2505                         }
2506                     }
2507
2508                     // Keep non whitespace text nodes
2509                     if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2510                         return false;
2511                 } while (node = walk(node, self));
2512             }
2513
2514             return true;
2515         }
2516     });
2517
2518     tinymce.extend(Node, {
2519         create : function(name, attrs) {
2520             var node, attrName;
2521
2522             // Create node
2523             node = new Node(name, typeLookup[name] || 1);
2524
2525             // Add attributes if needed
2526             if (attrs) {
2527                 for (attrName in attrs)
2528                     node.attr(attrName, attrs[attrName]);
2529             }
2530
2531             return node;
2532         }
2533     });
2534
2535     tinymce.html.Node = Node;
2536 })(tinymce);
2537
2538 (function(tinymce) {
2539     var Node = tinymce.html.Node;
2540
2541     tinymce.html.DomParser = function(settings, schema) {
2542         var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2543
2544         settings = settings || {};
2545         settings.validate = "validate" in settings ? settings.validate : true;
2546         settings.root_name = settings.root_name || 'body';
2547         self.schema = schema = schema || new tinymce.html.Schema();
2548
2549         function fixInvalidChildren(nodes) {
2550             var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2551                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2552
2553             nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2554             nonEmptyElements = schema.getNonEmptyElements();
2555
2556             for (ni = 0; ni < nodes.length; ni++) {
2557                 node = nodes[ni];
2558
2559                 // Already removed
2560                 if (!node.parent)
2561                     continue;
2562
2563                 // Get list of all parent nodes until we find a valid parent to stick the child into
2564                 parents = [node];
2565                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2566                     parents.push(parent);
2567
2568                 // Found a suitable parent
2569                 if (parent && parents.length > 1) {
2570                     // Reverse the array since it makes looping easier
2571                     parents.reverse();
2572
2573                     // Clone the related parent and insert that after the moved node
2574                     newParent = currentNode = self.filterNode(parents[0].clone());
2575
2576                     // Start cloning and moving children on the left side of the target node
2577                     for (i = 0; i < parents.length - 1; i++) {
2578                         if (schema.isValidChild(currentNode.name, parents[i].name)) {
2579                             tempNode = self.filterNode(parents[i].clone());
2580                             currentNode.append(tempNode);
2581                         } else
2582                             tempNode = currentNode;
2583
2584                         for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2585                             nextNode = childNode.next;
2586                             tempNode.append(childNode);
2587                             childNode = nextNode;
2588                         }
2589
2590                         currentNode = tempNode;
2591                     }
2592
2593                     if (!newParent.isEmpty(nonEmptyElements)) {
2594                         parent.insert(newParent, parents[0], true);
2595                         parent.insert(node, newParent);
2596                     } else {
2597                         parent.insert(node, parents[0], true);
2598                     }
2599
2600                     // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2601                     parent = parents[0];
2602                     if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2603                         parent.empty().remove();
2604                     }
2605                 } else if (node.parent) {
2606                     // If it's an LI try to find a UL/OL for it or wrap it
2607                     if (node.name === 'li') {
2608                         sibling = node.prev;
2609                         if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2610                             sibling.append(node);
2611                             continue;
2612                         }
2613
2614                         sibling = node.next;
2615                         if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2616                             sibling.insert(node, sibling.firstChild, true);
2617                             continue;
2618                         }
2619
2620                         node.wrap(self.filterNode(new Node('ul', 1)));
2621                         continue;
2622                     }
2623
2624                     // Try wrapping the element in a DIV
2625                     if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2626                         node.wrap(self.filterNode(new Node('div', 1)));
2627                     } else {
2628                         // We failed wrapping it, then remove or unwrap it
2629                         if (node.name === 'style' || node.name === 'script')
2630                             node.empty().remove();
2631                         else
2632                             node.unwrap();
2633                     }
2634                 }
2635             }
2636         };
2637
2638         self.filterNode = function(node) {
2639             var i, name, list;
2640
2641             // Run element filters
2642             if (name in nodeFilters) {
2643                 list = matchedNodes[name];
2644
2645                 if (list)
2646                     list.push(node);
2647                 else
2648                     matchedNodes[name] = [node];
2649             }
2650
2651             // Run attribute filters
2652             i = attributeFilters.length;
2653             while (i--) {
2654                 name = attributeFilters[i].name;
2655
2656                 if (name in node.attributes.map) {
2657                     list = matchedAttributes[name];
2658
2659                     if (list)
2660                         list.push(node);
2661                     else
2662                         matchedAttributes[name] = [node];
2663                 }
2664             }
2665
2666             return node;
2667         };
2668
2669         self.addNodeFilter = function(name, callback) {
2670             tinymce.each(tinymce.explode(name), function(name) {
2671                 var list = nodeFilters[name];
2672
2673                 if (!list)
2674                     nodeFilters[name] = list = [];
2675
2676                 list.push(callback);
2677             });
2678         };
2679
2680         self.addAttributeFilter = function(name, callback) {
2681             tinymce.each(tinymce.explode(name), function(name) {
2682                 var i;
2683
2684                 for (i = 0; i < attributeFilters.length; i++) {
2685                     if (attributeFilters[i].name === name) {
2686                         attributeFilters[i].callbacks.push(callback);
2687                         return;
2688                     }
2689                 }
2690
2691                 attributeFilters.push({name: name, callbacks: [callback]});
2692             });
2693         };
2694
2695         self.parse = function(html, args) {
2696             var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
2697                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
2698                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
2699
2700             args = args || {};
2701             matchedNodes = {};
2702             matchedAttributes = {};
2703             blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
2704             nonEmptyElements = schema.getNonEmptyElements();
2705             children = schema.children;
2706             validate = settings.validate;
2707
2708             whiteSpaceElements = schema.getWhiteSpaceElements();
2709             startWhiteSpaceRegExp = /^[ \t\r\n]+/;
2710             endWhiteSpaceRegExp = /[ \t\r\n]+$/;
2711             allWhiteSpaceRegExp = /[ \t\r\n]+/g;
2712
2713             function createNode(name, type) {
2714                 var node = new Node(name, type), list;
2715
2716                 if (name in nodeFilters) {
2717                     list = matchedNodes[name];
2718
2719                     if (list)
2720                         list.push(node);
2721                     else
2722                         matchedNodes[name] = [node];
2723                 }
2724
2725                 return node;
2726             };
2727
2728             function removeWhitespaceBefore(node) {
2729                 var textNode, textVal, sibling;
2730
2731                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
2732                     textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
2733
2734                     if (textVal.length > 0) {
2735                         textNode.value = textVal;
2736                         textNode = textNode.prev;
2737                     } else {
2738                         sibling = textNode.prev;
2739                         textNode.remove();
2740                         textNode = sibling;
2741                     }
2742                 }
2743             };
2744
2745             parser = new tinymce.html.SaxParser({
2746                 validate : validate,
2747                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
2748
2749                 cdata: function(text) {
2750                     node.append(createNode('#cdata', 4)).value = text;
2751                 },
2752
2753                 text: function(text, raw) {
2754                     var textNode;
2755
2756                     // Trim all redundant whitespace on non white space elements
2757                     if (!whiteSpaceElements[node.name]) {
2758                         text = text.replace(allWhiteSpaceRegExp, ' ');
2759
2760                         if (node.lastChild && blockElements[node.lastChild.name])
2761                             text = text.replace(startWhiteSpaceRegExp, '');
2762                     }
2763
2764                     // Do we need to create the node
2765                     if (text.length !== 0) {
2766                         textNode = createNode('#text', 3);
2767                         textNode.raw = !!raw;
2768                         node.append(textNode).value = text;
2769                     }
2770                 },
2771
2772                 comment: function(text) {
2773                     node.append(createNode('#comment', 8)).value = text;
2774                 },
2775
2776                 pi: function(name, text) {
2777                     node.append(createNode(name, 7)).value = text;
2778                     removeWhitespaceBefore(node);
2779                 },
2780
2781                 doctype: function(text) {
2782                     var newNode;
2783         
2784                     newNode = node.append(createNode('#doctype', 10));
2785                     newNode.value = text;
2786                     removeWhitespaceBefore(node);
2787                 },
2788
2789                 start: function(name, attrs, empty) {
2790                     var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
2791
2792                     elementRule = validate ? schema.getElementRule(name) : {};
2793                     if (elementRule) {
2794                         newNode = createNode(elementRule.outputName || name, 1);
2795                         newNode.attributes = attrs;
2796                         newNode.shortEnded = empty;
2797
2798                         node.append(newNode);
2799
2800                         // Check if node is valid child of the parent node is the child is
2801                         // unknown we don't collect it since it's probably a custom element
2802                         parent = children[node.name];
2803                         if (parent && children[newNode.name] && !parent[newNode.name])
2804                             invalidChildren.push(newNode);
2805
2806                         attrFiltersLen = attributeFilters.length;
2807                         while (attrFiltersLen--) {
2808                             attrName = attributeFilters[attrFiltersLen].name;
2809
2810                             if (attrName in attrs.map) {
2811                                 list = matchedAttributes[attrName];
2812
2813                                 if (list)
2814                                     list.push(newNode);
2815                                 else
2816                                     matchedAttributes[attrName] = [newNode];
2817                             }
2818                         }
2819
2820                         // Trim whitespace before block
2821                         if (blockElements[name])
2822                             removeWhitespaceBefore(newNode);
2823
2824                         // Change current node if the element wasn't empty i.e not <br /> or <img />
2825                         if (!empty)
2826                             node = newNode;
2827                     }
2828                 },
2829
2830                 end: function(name) {
2831                     var textNode, elementRule, text, sibling, tempNode;
2832
2833                     elementRule = validate ? schema.getElementRule(name) : {};
2834                     if (elementRule) {
2835                         if (blockElements[name]) {
2836                             if (!whiteSpaceElements[node.name]) {
2837                                 // Trim whitespace at beginning of block
2838                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
2839                                     text = textNode.value.replace(startWhiteSpaceRegExp, '');
2840
2841                                     if (text.length > 0) {
2842                                         textNode.value = text;
2843                                         textNode = textNode.next;
2844                                     } else {
2845                                         sibling = textNode.next;
2846                                         textNode.remove();
2847                                         textNode = sibling;
2848                                     }
2849                                 }
2850
2851                                 // Trim whitespace at end of block
2852                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
2853                                     text = textNode.value.replace(endWhiteSpaceRegExp, '');
2854
2855                                     if (text.length > 0) {
2856                                         textNode.value = text;
2857                                         textNode = textNode.prev;
2858                                     } else {
2859                                         sibling = textNode.prev;
2860                                         textNode.remove();
2861                                         textNode = sibling;
2862                                     }
2863                                 }
2864                             }
2865
2866                             // Trim start white space
2867                             textNode = node.prev;
2868                             if (textNode && textNode.type === 3) {
2869                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
2870
2871                                 if (text.length > 0)
2872                                     textNode.value = text;
2873                                 else
2874                                     textNode.remove();
2875                             }
2876                         }
2877
2878                         // Handle empty nodes
2879                         if (elementRule.removeEmpty || elementRule.paddEmpty) {
2880                             if (node.isEmpty(nonEmptyElements)) {
2881                                 if (elementRule.paddEmpty)
2882                                     node.empty().append(new Node('#text', '3')).value = '\u00a0';
2883                                 else {
2884                                     // Leave nodes that have a name like <a name="name">
2885                                     if (!node.attributes.map.name) {
2886                                         tempNode = node.parent;
2887                                         node.empty().remove();
2888                                         node = tempNode;
2889                                         return;
2890                                     }
2891                                 }
2892                             }
2893                         }
2894
2895                         node = node.parent;
2896                     }
2897                 }
2898             }, schema);
2899
2900             rootNode = node = new Node(settings.root_name, 11);
2901
2902             parser.parse(html);
2903
2904             if (validate)
2905                 fixInvalidChildren(invalidChildren);
2906
2907             // Run node filters
2908             for (name in matchedNodes) {
2909                 list = nodeFilters[name];
2910                 nodes = matchedNodes[name];
2911
2912                 // Remove already removed children
2913                 fi = nodes.length;
2914                 while (fi--) {
2915                     if (!nodes[fi].parent)
2916                         nodes.splice(fi, 1);
2917                 }
2918
2919                 for (i = 0, l = list.length; i < l; i++)
2920                     list[i](nodes, name, args);
2921             }
2922
2923             // Run attribute filters
2924             for (i = 0, l = attributeFilters.length; i < l; i++) {
2925                 list = attributeFilters[i];
2926
2927                 if (list.name in matchedAttributes) {
2928                     nodes = matchedAttributes[list.name];
2929
2930                     // Remove already removed children
2931                     fi = nodes.length;
2932                     while (fi--) {
2933                         if (!nodes[fi].parent)
2934                             nodes.splice(fi, 1);
2935                     }
2936
2937                     for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
2938                         list.callbacks[fi](nodes, list.name, args);
2939                 }
2940             }
2941
2942             return rootNode;
2943         };
2944
2945         // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
2946         // make it possible to place the caret inside empty blocks. This logic tries to remove
2947         // these elements and keep br elements that where intended to be there intact
2948         if (settings.remove_trailing_brs) {
2949             self.addNodeFilter('br', function(nodes, name) {
2950                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
2951                     nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
2952
2953                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
2954                 for (i = 0; i < l; i++) {
2955                     node = nodes[i];
2956                     parent = node.parent;
2957
2958                     if (blockElements[node.parent.name] && node === parent.lastChild) {
2959                         // Loop all nodes to the right of the current node and check for other BR elements
2960                         // excluding bookmarks since they are invisible
2961                         prev = node.prev;
2962                         while (prev) {
2963                             prevName = prev.name;
2964
2965                             // Ignore bookmarks
2966                             if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
2967                                 // Found a non BR element
2968                                 if (prevName !== "br")
2969                                     break;
2970     
2971                                 // Found another br it's a <br><br> structure then don't remove anything
2972                                 if (prevName === 'br') {
2973                                     node = null;
2974                                     break;
2975                                 }
2976                             }
2977
2978                             prev = prev.prev;
2979                         }
2980
2981                         if (node) {
2982                             node.remove();
2983
2984                             // Is the parent to be considered empty after we removed the BR
2985                             if (parent.isEmpty(nonEmptyElements)) {
2986                                 elementRule = schema.getElementRule(parent.name);
2987
2988                                 // Remove or padd the element depending on schema rule
2989                                 if (elementRule.removeEmpty)
2990                                     parent.remove();
2991                                 else if (elementRule.paddEmpty) 
2992                                     parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
2993                             }
2994                         }
2995                     }
2996                 }
2997             });
2998         }
2999     }
3000 })(tinymce);
3001
3002 tinymce.html.Writer = function(settings) {
3003     var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3004
3005     settings = settings || {};
3006     indent = settings.indent;
3007     indentBefore = tinymce.makeMap(settings.indent_before || '');
3008     indentAfter = tinymce.makeMap(settings.indent_after || '');
3009     encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3010     htmlOutput = settings.element_format == "html";
3011
3012     return {
3013         start: function(name, attrs, empty) {
3014             var i, l, attr, value;
3015
3016             if (indent && indentBefore[name] && html.length > 0) {
3017                 value = html[html.length - 1];
3018
3019                 if (value.length > 0 && value !== '\n')
3020                     html.push('\n');
3021             }
3022
3023             html.push('<', name);
3024
3025             if (attrs) {
3026                 for (i = 0, l = attrs.length; i < l; i++) {
3027                     attr = attrs[i];
3028                     html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3029                 }
3030             }
3031
3032             if (!empty || htmlOutput)
3033                 html[html.length] = '>';
3034             else
3035                 html[html.length] = ' />';
3036
3037             if (empty && indent && indentAfter[name] && html.length > 0) {
3038                 value = html[html.length - 1];
3039
3040                 if (value.length > 0 && value !== '\n')
3041                     html.push('\n');
3042             }
3043         },
3044
3045         end: function(name) {
3046             var value;
3047
3048             /*if (indent && indentBefore[name] && html.length > 0) {
3049                 value = html[html.length - 1];
3050
3051                 if (value.length > 0 && value !== '\n')
3052                     html.push('\n');
3053             }*/
3054
3055             html.push('</', name, '>');
3056
3057             if (indent && indentAfter[name] && html.length > 0) {
3058                 value = html[html.length - 1];
3059
3060                 if (value.length > 0 && value !== '\n')
3061                     html.push('\n');
3062             }
3063         },
3064
3065         text: function(text, raw) {
3066             if (text.length > 0)
3067                 html[html.length] = raw ? text : encode(text);
3068         },
3069
3070         cdata: function(text) {
3071             html.push('<![CDATA[', text, ']]>');
3072         },
3073
3074         comment: function(text) {
3075             html.push('<!--', text, '-->');
3076         },
3077
3078         pi: function(name, text) {
3079             if (text)
3080                 html.push('<?', name, ' ', text, '?>');
3081             else
3082                 html.push('<?', name, '?>');
3083
3084             if (indent)
3085                 html.push('\n');
3086         },
3087
3088         doctype: function(text) {
3089             html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3090         },
3091
3092         reset: function() {
3093             html.length = 0;
3094         },
3095
3096         getContent: function() {
3097             return html.join('').replace(/\n$/, '');
3098         }
3099     };
3100 };
3101
3102 (function(tinymce) {
3103     tinymce.html.Serializer = function(settings, schema) {
3104         var self = this, writer = new tinymce.html.Writer(settings);
3105
3106         settings = settings || {};
3107         settings.validate = "validate" in settings ? settings.validate : true;
3108
3109         self.schema = schema = schema || new tinymce.html.Schema();
3110         self.writer = writer;
3111
3112         self.serialize = function(node) {
3113             var handlers, validate;
3114
3115             validate = settings.validate;
3116
3117             handlers = {
3118                 // #text
3119                 3: function(node, raw) {
3120                     writer.text(node.value, node.raw);
3121                 },
3122
3123                 // #comment
3124                 8: function(node) {
3125                     writer.comment(node.value);
3126                 },
3127
3128                 // Processing instruction
3129                 7: function(node) {
3130                     writer.pi(node.name, node.value);
3131                 },
3132
3133                 // Doctype
3134                 10: function(node) {
3135                     writer.doctype(node.value);
3136                 },
3137
3138                 // CDATA
3139                 4: function(node) {
3140                     writer.cdata(node.value);
3141                 },
3142
3143                  // Document fragment
3144                 11: function(node) {
3145                     if ((node = node.firstChild)) {
3146                         do {
3147                             walk(node);
3148                         } while (node = node.next);
3149                     }
3150                 }
3151             };
3152
3153             writer.reset();
3154
3155             function walk(node) {
3156                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3157
3158                 if (!handler) {
3159                     name = node.name;
3160                     isEmpty = node.shortEnded;
3161                     attrs = node.attributes;
3162
3163                     // Sort attributes
3164                     if (validate && attrs && attrs.length > 1) {
3165                         sortedAttrs = [];
3166                         sortedAttrs.map = {};
3167
3168                         elementRule = schema.getElementRule(node.name);
3169                         for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3170                             attrName = elementRule.attributesOrder[i];
3171
3172                             if (attrName in attrs.map) {
3173                                 attrValue = attrs.map[attrName];
3174                                 sortedAttrs.map[attrName] = attrValue;
3175                                 sortedAttrs.push({name: attrName, value: attrValue});
3176                             }
3177                         }
3178
3179                         for (i = 0, l = attrs.length; i < l; i++) {
3180                             attrName = attrs[i].name;
3181
3182                             if (!(attrName in sortedAttrs.map)) {
3183                                 attrValue = attrs.map[attrName];
3184                                 sortedAttrs.map[attrName] = attrValue;
3185                                 sortedAttrs.push({name: attrName, value: attrValue});
3186                             }
3187                         }
3188
3189                         attrs = sortedAttrs;
3190                     }
3191
3192                     writer.start(node.name, attrs, isEmpty);
3193
3194                     if (!isEmpty) {
3195                         if ((node = node.firstChild)) {
3196                             do {
3197                                 walk(node);
3198                             } while (node = node.next);
3199                         }
3200
3201                         writer.end(name);
3202                     }
3203                 } else
3204                     handler(node);
3205             }
3206
3207             // Serialize element and treat all non elements as fragments
3208             if (node.type == 1 && !settings.inner)
3209                 walk(node);
3210             else
3211                 handlers[11](node);
3212
3213             return writer.getContent();
3214         };
3215     }
3216 })(tinymce);
3217
3218 (function(tinymce) {
d9344f 3219     // Shorten names
69d05c 3220     var each = tinymce.each,
A 3221         is = tinymce.is,
3222         isWebKit = tinymce.isWebKit,
3223         isIE = tinymce.isIE,
a9251b 3224         Entities = tinymce.html.Entities,
69d05c 3225         simpleSelectorRe = /^([a-z0-9],?)+$/i,
a9251b 3226         blockElementsMap = tinymce.html.Schema.blockElementsMap,
T 3227         whiteSpaceRegExp = /^[ \t\r\n]*$/;
d9344f 3228
S 3229     tinymce.create('tinymce.dom.DOMUtils', {
3230         doc : null,
3231         root : null,
3232         files : null,
3233         pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
29da64 3234         props : {
A 3235             "for" : "htmlFor",
3236             "class" : "className",
3237             className : "className",
3238             checked : "checked",
3239             disabled : "disabled",
3240             maxlength : "maxLength",
3241             readonly : "readOnly",
3242             selected : "selected",
3243             value : "value",
3244             id : "id",
3245             name : "name",
3246             type : "type"
3247         },
d9344f 3248
S 3249         DOMUtils : function(d, s) {
69d05c 3250             var t = this, globalStyle;
d9344f 3251
S 3252             t.doc = d;
3253             t.win = window;
3254             t.files = {};
3255             t.cssFlicker = false;
3256             t.counter = 0;
a9251b 3257             t.stdMode = !tinymce.isIE || d.documentMode >= 8;
T 3258             t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3259             t.hasOuterHTML = "outerHTML" in d.createElement("a");
d9344f 3260
58fb65 3261             t.settings = s = tinymce.extend({
d9344f 3262                 keep_values : false,
a9251b 3263                 hex_colors : 1
d9344f 3264             }, s);
a9251b 3265             
T 3266             t.schema = s.schema;
3267             t.styles = new tinymce.html.Styles({
3268                 url_converter : s.url_converter,
3269                 url_converter_scope : s.url_converter_scope
3270             }, s.schema);
d9344f 3271
S 3272             // Fix IE6SP2 flicker and check it failed for pre SP2
3273             if (tinymce.isIE6) {
3274                 try {
3275                     d.execCommand('BackgroundImageCache', false, true);
3276                 } catch (e) {
3277                     t.cssFlicker = true;
3278                 }
69d05c 3279             }
A 3280
a9251b 3281             if (isIE) {
T 3282                 // Add missing HTML 4/5 elements to IE
3283                 ('abbr article aside audio canvas ' +
3284                 'details figcaption figure footer ' +
3285                 'header hgroup mark menu meter nav ' +
3286                 'output progress section summary ' +
3287                 'time video').replace(/\w+/g, function(name) {
3288                     d.createElement(name);
69d05c 3289                 });
d9344f 3290             }
S 3291
3292             tinymce.addUnload(t.destroy, t);
3293         },
3294
3295         getRoot : function() {
3296             var t = this, s = t.settings;
3297
3298             return (s && t.get(s.root_element)) || t.doc.body;
3299         },
3300
3301         getViewPort : function(w) {
3302             var d, b;
3303
3304             w = !w ? this.win : w;
3305             d = w.document;
3306             b = this.boxModel ? d.documentElement : d.body;
3307
3308             // Returns viewport size excluding scrollbars
3309             return {
3310                 x : w.pageXOffset || b.scrollLeft,
3311                 y : w.pageYOffset || b.scrollTop,
3312                 w : w.innerWidth || b.clientWidth,
3313                 h : w.innerHeight || b.clientHeight
3314             };
3315         },
3316
3317         getRect : function(e) {
29da64 3318             var p, t = this, sr;
d9344f 3319
S 3320             e = t.get(e);
3321             p = t.getPos(e);
29da64 3322             sr = t.getSize(e);
A 3323
3324             return {
3325                 x : p.x,
3326                 y : p.y,
3327                 w : sr.w,
3328                 h : sr.h
3329             };
3330         },
3331
3332         getSize : function(e) {
3333             var t = this, w, h;
3334
3335             e = t.get(e);
d9344f 3336             w = t.getStyle(e, 'width');
S 3337             h = t.getStyle(e, 'height');
3338
3339             // Non pixel value, then force offset/clientWidth
3340             if (w.indexOf('px') === -1)
3341                 w = 0;
3342
3343             // Non pixel value, then force offset/clientWidth
3344             if (h.indexOf('px') === -1)
3345                 h = 0;
3346
3347             return {
3348                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3349                 h : parseInt(h) || e.offsetHeight || e.clientHeight
3350             };
3351         },
3352
29da64 3353         getParent : function(n, f, r) {
A 3354             return this.getParents(n, f, r, false);
3355         },
3356
3357         getParents : function(n, f, r, c) {
3358             var t = this, na, se = t.settings, o = [];
3359
3360             n = t.get(n);
3361             c = c === undefined;
d9344f 3362
S 3363             if (se.strict_root)
29da64 3364                 r = r || t.getRoot();
d9344f 3365
S 3366             // Wrap node name as func
3367             if (is(f, 'string')) {
29da64 3368                 na = f;
d9344f 3369
29da64 3370                 if (f === '*') {
A 3371                     f = function(n) {return n.nodeType == 1;};
3372                 } else {
3373                     f = function(n) {
3374                         return t.is(n, na);
3375                     };
3376                 }
d9344f 3377             }
S 3378
3379             while (n) {
29da64 3380                 if (n == r || !n.nodeType || n.nodeType === 9)
A 3381                     break;
d9344f 3382
29da64 3383                 if (!f || f(n)) {
A 3384                     if (c)
3385                         o.push(n);
3386                     else
3387                         return n;
3388                 }
d9344f 3389
S 3390                 n = n.parentNode;
3391             }
3392
29da64 3393             return c ? o : null;
d9344f 3394         },
S 3395
3396         get : function(e) {
3397             var n;
3398
18240a 3399             if (e && this.doc && typeof(e) == 'string') {
d9344f 3400                 n = e;
S 3401                 e = this.doc.getElementById(e);
3402
3403                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3404                 if (e && e.id !== n)
3405                     return this.doc.getElementsByName(n)[1];
3406             }
3407
3408             return e;
3409         },
3410
58fb65 3411         getNext : function(node, selector) {
A 3412             return this._findSib(node, selector, 'nextSibling');
3413         },
3414
3415         getPrev : function(node, selector) {
3416             return this._findSib(node, selector, 'previousSibling');
3417         },
3418
d9344f 3419
S 3420         select : function(pa, s) {
29da64 3421             var t = this;
d9344f 3422
29da64 3423             return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
58fb65 3424         },
A 3425
69d05c 3426         is : function(n, selector) {
A 3427             var i;
3428
3429             // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
3430             if (n.length === undefined) {
3431                 // Simple all selector
3432                 if (selector === '*')
3433                     return n.nodeType == 1;
3434
3435                 // Simple selector just elements
3436                 if (simpleSelectorRe.test(selector)) {
3437                     selector = selector.toLowerCase().split(/,/);
3438                     n = n.nodeName.toLowerCase();
3439
3440                     for (i = selector.length - 1; i >= 0; i--) {
3441                         if (selector[i] == n)
3442                             return true;
3443                     }
3444
3445                     return false;
3446                 }
3447             }
3448
3449             return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
d9344f 3450         },
S 3451
3452
3453         add : function(p, n, a, h, c) {
3454             var t = this;
3455
3456             return this.run(p, function(p) {
3457                 var e, k;
3458
3459                 e = is(n, 'string') ? t.doc.createElement(n) : n;
29da64 3460                 t.setAttribs(e, a);
d9344f 3461
S 3462                 if (h) {
3463                     if (h.nodeType)
3464                         e.appendChild(h);
3465                     else
3466                         t.setHTML(e, h);
3467                 }
3468
3469                 return !c ? p.appendChild(e) : e;
3470             });
3471         },
3472
3473         create : function(n, a, h) {
3474             return this.add(this.doc.createElement(n), n, a, h, 1);
3475         },
3476
3477         createHTML : function(n, a, h) {
3478             var o = '', t = this, k;
3479
3480             o += '<' + n;
3481
3482             for (k in a) {
3483                 if (a.hasOwnProperty(k))
3484                     o += ' ' + k + '="' + t.encode(a[k]) + '"';
3485             }
3486
a9251b 3487             // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
T 3488             if (typeof(h) != "undefined")
d9344f 3489                 return o + '>' + h + '</' + n + '>';
S 3490
3491             return o + ' />';
3492         },
3493
69d05c 3494         remove : function(node, keep_children) {
A 3495             return this.run(node, function(node) {
a9251b 3496                 var child, parent = node.parentNode;
d9344f 3497
69d05c 3498                 if (!parent)
d9344f 3499                     return null;
S 3500
69d05c 3501                 if (keep_children) {
A 3502                     while (child = node.firstChild) {
3503                         // IE 8 will crash if you don't remove completely empty text nodes
2011be 3504                         if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
69d05c 3505                             parent.insertBefore(child, node);
A 3506                         else
3507                             node.removeChild(child);
3508                     }
d9344f 3509                 }
S 3510
69d05c 3511                 return parent.removeChild(node);
d9344f 3512             });
S 3513         },
3514
3515         setStyle : function(n, na, v) {
3516             var t = this;
3517
3518             return t.run(n, function(e) {
3519                 var s, i;
3520
3521                 s = e.style;
3522
3523                 // Camelcase it, if needed
3524                 na = na.replace(/-(\D)/g, function(a, b){
3525                     return b.toUpperCase();
3526                 });
3527
3528                 // Default px suffix on these
3529                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3530                     v += 'px';
3531
3532                 switch (na) {
3533                     case 'opacity':
3534                         // IE specific opacity
3535                         if (isIE) {
3536                             s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3537
3538                             if (!n.currentStyle || !n.currentStyle.hasLayout)
3539                                 s.display = 'inline-block';
3540                         }
3541
3542                         // Fix for older browsers
3543                         s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3544                         break;
3545
3546                     case 'float':
3547                         isIE ? s.styleFloat = v : s.cssFloat = v;
3548                         break;
3549                     
3550                     default:
3551                         s[na] = v || '';
3552                 }
3553
3554                 // Force update of the style data
3555                 if (t.settings.update_styles)
a9251b 3556                     t.setAttrib(e, 'data-mce-style');
d9344f 3557             });
S 3558         },
3559
3560         getStyle : function(n, na, c) {
3561             n = this.get(n);
3562
3563             if (!n)
a9251b 3564                 return;
d9344f 3565
S 3566             // Gecko
3567             if (this.doc.defaultView && c) {
3568                 // Remove camelcase
3569                 na = na.replace(/[A-Z]/g, function(a){
3570                     return '-' + a;
3571                 });
3572
3573                 try {
3574                     return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3575                 } catch (ex) {
3576                     // Old safari might fail
3577                     return null;
3578                 }
3579             }
3580
3581             // Camelcase it, if needed
3582             na = na.replace(/-(\D)/g, function(a, b){
3583                 return b.toUpperCase();
3584             });
3585
3586             if (na == 'float')
3587                 na = isIE ? 'styleFloat' : 'cssFloat';
3588
3589             // IE & Opera
3590             if (n.currentStyle && c)
3591                 return n.currentStyle[na];
3592
a9251b 3593             return n.style ? n.style[na] : undefined;
d9344f 3594         },
S 3595
3596         setStyles : function(e, o) {
3597             var t = this, s = t.settings, ol;
3598
3599             ol = s.update_styles;
3600             s.update_styles = 0;
3601
3602             each(o, function(v, n) {
3603                 t.setStyle(e, n, v);
3604             });
3605
3606             // Update style info
3607             s.update_styles = ol;
3608             if (s.update_styles)
3609                 t.setAttrib(e, s.cssText);
a9251b 3610         },
T 3611
3612         removeAllAttribs: function(e) {
3613             return this.run(e, function(e) {
3614                 var i, attrs = e.attributes;
3615                 for (i = attrs.length - 1; i >= 0; i--) {
3616                     e.removeAttributeNode(attrs.item(i));
3617                 }
3618             });
d9344f 3619         },
S 3620
3621         setAttrib : function(e, n, v) {
3622             var t = this;
3623
29da64 3624             // Whats the point
A 3625             if (!e || !n)
3626                 return;
3627
d9344f 3628             // Strict XML mode
S 3629             if (t.settings.strict)
3630                 n = n.toLowerCase();
3631
3632             return this.run(e, function(e) {
3633                 var s = t.settings;
3634
3635                 switch (n) {
3636                     case "style":
29da64 3637                         if (!is(v, 'string')) {
A 3638                             each(v, function(v, n) {
3639                                 t.setStyle(e, n, v);
3640                             });
3641
3642                             return;
3643                         }
3644
18240a 3645                         // No mce_style for elements with these since they might get resized by the user
d9344f 3646                         if (s.keep_values) {
18240a 3647                             if (v && !t._isRes(v))
a9251b 3648                                 e.setAttribute('data-mce-style', v, 2);
d9344f 3649                             else
a9251b 3650                                 e.removeAttribute('data-mce-style', 2);
d9344f 3651                         }
S 3652
3653                         e.style.cssText = v;
3654                         break;
3655
3656                     case "class":
3657                         e.className = v || ''; // Fix IE null bug
3658                         break;
3659
3660                     case "src":
3661                     case "href":
3662                         if (s.keep_values) {
3663                             if (s.url_converter)
3664                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3665
a9251b 3666                             t.setAttrib(e, 'data-mce-' + n, v, 2);
d9344f 3667                         }
S 3668
18240a 3669                         break;
a9251b 3670
18240a 3671                     case "shape":
a9251b 3672                         e.setAttribute('data-mce-style', v);
d9344f 3673                         break;
S 3674                 }
3675
3676                 if (is(v) && v !== null && v.length !== 0)
3677                     e.setAttribute(n, '' + v, 2);
3678                 else
3679                     e.removeAttribute(n, 2);
3680             });
3681         },
3682
3683         setAttribs : function(e, o) {
3684             var t = this;
3685
3686             return this.run(e, function(e) {
3687                 each(o, function(v, n) {
3688                     t.setAttrib(e, n, v);
3689                 });
3690             });
3691         },
3692
3693         getAttrib : function(e, n, dv) {
3694             var v, t = this;
3695
3696             e = t.get(e);
3697
18240a 3698             if (!e || e.nodeType !== 1)
d9344f 3699                 return false;
S 3700
3701             if (!is(dv))
29da64 3702                 dv = '';
d9344f 3703
S 3704             // Try the mce variant for these
18240a 3705             if (/^(src|href|style|coords|shape)$/.test(n)) {
a9251b 3706                 v = e.getAttribute("data-mce-" + n);
d9344f 3707
S 3708                 if (v)
3709                     return v;
3710             }
3711
29da64 3712             if (isIE && t.props[n]) {
A 3713                 v = e[t.props[n]];
3714                 v = v && v.nodeValue ? v.nodeValue : v;
d9344f 3715             }
S 3716
29da64 3717             if (!v)
A 3718                 v = e.getAttribute(n, 2);
58fb65 3719
A 3720             // Check boolean attribs
3721             if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
3722                 if (e[t.props[n]] === true && v === '')
3723                     return n;
3724
3725                 return v ? n : '';
3726             }
3727
3728             // Inner input elements will override attributes on form elements
3729             if (e.nodeName === "FORM" && e.getAttributeNode(n))
3730                 return e.getAttributeNode(n).nodeValue;
d9344f 3731
29da64 3732             if (n === 'style') {
A 3733                 v = v || e.style.cssText;
d9344f 3734
29da64 3735                 if (v) {
69d05c 3736                     v = t.serializeStyle(t.parseStyle(v), e.nodeName);
d9344f 3737
29da64 3738                     if (t.settings.keep_values && !t._isRes(v))
a9251b 3739                         e.setAttribute('data-mce-style', v);
29da64 3740                 }
d9344f 3741             }
S 3742
3743             // Remove Apple and WebKit stuff
3744             if (isWebKit && n === "class" && v)
3745                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3746
3747             // Handle IE issues
3748             if (isIE) {
3749                 switch (n) {
3750                     case 'rowspan':
3751                     case 'colspan':
3752                         // IE returns 1 as default value
3753                         if (v === 1)
3754                             v = '';
3755
3756                         break;
3757
3758                     case 'size':
3759                         // IE returns +0 as default value for size
29da64 3760                         if (v === '+0' || v === 20 || v === 0)
A 3761                             v = '';
3762
3763                         break;
3764
3765                     case 'width':
3766                     case 'height':
3767                     case 'vspace':
3768                     case 'checked':
3769                     case 'disabled':
3770                     case 'readonly':
3771                         if (v === 0)
d9344f 3772                             v = '';
S 3773
3774                         break;
3775
3776                     case 'hspace':
3777                         // IE returns -1 as default value
3778                         if (v === -1)
3779                             v = '';
3780
3781                         break;
3782
29da64 3783                     case 'maxlength':
d9344f 3784                     case 'tabindex':
18240a 3785                         // IE returns default value
29da64 3786                         if (v === 32768 || v === 2147483647 || v === '32768')
18240a 3787                             v = '';
A 3788
3789                         break;
3790
29da64 3791                     case 'multiple':
A 3792                     case 'compact':
3793                     case 'noshade':
3794                     case 'nowrap':
3795                         if (v === 65535)
3796                             return n;
d9344f 3797
29da64 3798                         return dv;
d9344f 3799
S 3800                     case 'shape':
3801                         v = v.toLowerCase();
3802                         break;
3803
3804                     default:
3805                         // IE has odd anonymous function for event attributes
3806                         if (n.indexOf('on') === 0 && v)
a9251b 3807                             v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
d9344f 3808                 }
S 3809             }
3810
29da64 3811             return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
d9344f 3812         },
S 3813
29da64 3814         getPos : function(n, ro) {
d9344f 3815             var t = this, x = 0, y = 0, e, d = t.doc, r;
S 3816
3817             n = t.get(n);
29da64 3818             ro = ro || d.body;
d9344f 3819
29da64 3820             if (n) {
A 3821                 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
3822                 if (isIE && !t.stdMode) {
3823                     n = n.getBoundingClientRect();
3824                     e = t.boxModel ? d.documentElement : d.body;
3825                     x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
3826                     x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
d9344f 3827
29da64 3828                     return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
d9344f 3829                 }
S 3830
29da64 3831                 r = n;
A 3832                 while (r && r != ro && r.nodeType) {
3833                     x += r.offsetLeft || 0;
3834                     y += r.offsetTop || 0;
3835                     r = r.offsetParent;
3836                 }
d9344f 3837
29da64 3838                 r = n.parentNode;
A 3839                 while (r && r != ro && r.nodeType) {
3840                     x -= r.scrollLeft || 0;
3841                     y -= r.scrollTop || 0;
3842                     r = r.parentNode;
3843                 }
d9344f 3844             }
S 3845
3846             return {x : x, y : y};
3847         },
3848
3849         parseStyle : function(st) {
a9251b 3850             return this.styles.parse(st);
d9344f 3851         },
S 3852
69d05c 3853         serializeStyle : function(o, name) {
a9251b 3854             return this.styles.serialize(o, name);
d9344f 3855         },
S 3856
3857         loadCSS : function(u) {
58fb65 3858             var t = this, d = t.doc, head;
d9344f 3859
S 3860             if (!u)
3861                 u = '';
3862
58fb65 3863             head = t.select('head')[0];
A 3864
d9344f 3865             each(u.split(','), function(u) {
58fb65 3866                 var link;
A 3867
d9344f 3868                 if (t.files[u])
S 3869                     return;
3870
3871                 t.files[u] = true;
58fb65 3872                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
A 3873
3874                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
3875                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
3876                 // It's ugly but it seems to work fine.
a9251b 3877                 if (isIE && d.documentMode && d.recalc) {
58fb65 3878                     link.onload = function() {
a9251b 3879                         if (d.recalc)
T 3880                             d.recalc();
3881
58fb65 3882                         link.onload = null;
A 3883                     };
3884                 }
3885
3886                 head.appendChild(link);
d9344f 3887             });
S 3888         },
3889
3890         addClass : function(e, c) {
3891             return this.run(e, function(e) {
3892                 var o;
3893
3894                 if (!c)
3895                     return 0;
3896
3897                 if (this.hasClass(e, c))
3898                     return e.className;
3899
3900                 o = this.removeClass(e, c);
3901
3902                 return e.className = (o != '' ? (o + ' ') : '') + c;
3903             });
3904         },
3905
3906         removeClass : function(e, c) {
3907             var t = this, re;
3908
3909             return t.run(e, function(e) {
3910                 var v;
3911
3912                 if (t.hasClass(e, c)) {
3913                     if (!re)
3914                         re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
3915
3916                     v = e.className.replace(re, ' ');
69d05c 3917                     v = tinymce.trim(v != ' ' ? v : '');
d9344f 3918
69d05c 3919                     e.className = v;
A 3920
3921                     // Empty class attr
3922                     if (!v) {
3923                         e.removeAttribute('class');
3924                         e.removeAttribute('className');
3925                     }
3926
3927                     return v;
d9344f 3928                 }
S 3929
3930                 return e.className;
3931             });
3932         },
3933
3934         hasClass : function(n, c) {
3935             n = this.get(n);
3936
3937             if (!n || !c)
3938                 return false;
3939
3940             return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
3941         },
3942
3943         show : function(e) {
3944             return this.setStyle(e, 'display', 'block');
3945         },
3946
3947         hide : function(e) {
3948             return this.setStyle(e, 'display', 'none');
3949         },
3950
3951         isHidden : function(e) {
3952             e = this.get(e);
3953
29da64 3954             return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
d9344f 3955         },
S 3956
3957         uniqueId : function(p) {
3958             return (!p ? 'mce_' : p) + (this.counter++);
3959         },
3960
a9251b 3961         setHTML : function(element, html) {
T 3962             var self = this;
d9344f 3963
a9251b 3964             return self.run(element, function(element) {
d9344f 3965                 if (isIE) {
a9251b 3966                     // Remove all child nodes, IE keeps empty text nodes in DOM
T 3967                     while (element.firstChild)
3968                         element.removeChild(element.firstChild);
69d05c 3969
a9251b 3970                     try {
T 3971                         // IE will remove comments from the beginning
3972                         // unless you padd the contents with something
3973                         element.innerHTML = '<br />' + html;
3974                         element.removeChild(element.firstChild);
3975                     } catch (ex) {
3976                         // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
3977                         // This seems to fix this problem
d9344f 3978
a9251b 3979                         // Create new div with HTML contents and a BR infront to keep comments
T 3980                         element = self.create('div');
3981                         element.innerHTML = '<br />' + html;
d9344f 3982
a9251b 3983                         // Add all children from div to target
T 3984                         each (element.childNodes, function(node, i) {
3985                             // Skip br element
3986                             if (i)
3987                                 element.appendChild(node);
3988                         });
d9344f 3989                     }
S 3990                 } else
a9251b 3991                     element.innerHTML = html;
d9344f 3992
a9251b 3993                 return html;
d9344f 3994             });
S 3995         },
3996
a9251b 3997         getOuterHTML : function(elm) {
T 3998             var doc, self = this;
d9344f 3999
a9251b 4000             elm = self.get(elm);
d9344f 4001
a9251b 4002             if (!elm)
d9344f 4003                 return null;
S 4004
a9251b 4005             if (elm.nodeType === 1 && self.hasOuterHTML)
T 4006                 return elm.outerHTML;
d9344f 4007
a9251b 4008             doc = (elm.ownerDocument || self.doc).createElement("body");
T 4009             doc.appendChild(elm.cloneNode(true));
d9344f 4010
a9251b 4011             return doc.innerHTML;
d9344f 4012         },
S 4013
4014         setOuterHTML : function(e, h, d) {
4015             var t = this;
4016
58fb65 4017             function setHTML(e, h, d) {
d9344f 4018                 var n, tp;
69d05c 4019
58fb65 4020                 tp = d.createElement("body");
A 4021                 tp.innerHTML = h;
d9344f 4022
58fb65 4023                 n = tp.lastChild;
A 4024                 while (n) {
4025                     t.insertAfter(n.cloneNode(true), e);
4026                     n = n.previousSibling;
4027                 }
4028
4029                 t.remove(e);
4030             };
4031
4032             return this.run(e, function(e) {
d9344f 4033                 e = t.get(e);
S 4034
58fb65 4035                 // Only set HTML on elements
A 4036                 if (e.nodeType == 1) {
4037                     d = d || e.ownerDocument || t.doc;
d9344f 4038
58fb65 4039                     if (isIE) {
A 4040                         try {
4041                             // Try outerHTML for IE it sometimes produces an unknown runtime error
4042                             if (isIE && e.nodeType == 1)
4043                                 e.outerHTML = h;
4044                             else
4045                                 setHTML(e, h, d);
4046                         } catch (ex) {
4047                             // Fix for unknown runtime error
4048                             setHTML(e, h, d);
4049                         }
4050                     } else
4051                         setHTML(e, h, d);
d9344f 4052                 }
S 4053             });
4054         },
4055
a9251b 4056         decode : Entities.decode,
d9344f 4057
a9251b 4058         encode : Entities.encodeAllRaw,
d9344f 4059
69d05c 4060         insertAfter : function(node, reference_node) {
A 4061             reference_node = this.get(reference_node);
d9344f 4062
69d05c 4063             return this.run(node, function(node) {
A 4064                 var parent, nextSibling;
d9344f 4065
69d05c 4066                 parent = reference_node.parentNode;
A 4067                 nextSibling = reference_node.nextSibling;
d9344f 4068
69d05c 4069                 if (nextSibling)
A 4070                     parent.insertBefore(node, nextSibling);
d9344f 4071                 else
69d05c 4072                     parent.appendChild(node);
d9344f 4073
69d05c 4074                 return node;
d9344f 4075             });
S 4076         },
4077
a9251b 4078         isBlock : function(node) {
T 4079             var type = node.nodeType;
d9344f 4080
a9251b 4081             // If it's a node then check the type and use the nodeName
T 4082             if (type)
4083                 return !!(type === 1 && blockElementsMap[node.nodeName]);
d9344f 4084
a9251b 4085             return !!blockElementsMap[node];
d9344f 4086         },
S 4087
4088         replace : function(n, o, k) {
29da64 4089             var t = this;
A 4090
d9344f 4091             if (is(o, 'array'))
S 4092                 n = n.cloneNode(true);
4093
29da64 4094             return t.run(o, function(o) {
d9344f 4095                 if (k) {
69d05c 4096                     each(tinymce.grep(o.childNodes), function(c) {
A 4097                         n.appendChild(c);
d9344f 4098                     });
29da64 4099                 }
d9344f 4100
S 4101                 return o.parentNode.replaceChild(n, o);
4102             });
69d05c 4103         },
A 4104
4105         rename : function(elm, name) {
4106             var t = this, newElm;
4107
4108             if (elm.nodeName != name.toUpperCase()) {
4109                 // Rename block element
4110                 newElm = t.create(name);
4111
4112                 // Copy attribs to new block
4113                 each(t.getAttribs(elm), function(attr_node) {
4114                     t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4115                 });
4116
4117                 // Replace block
4118                 t.replace(newElm, elm, 1);
4119             }
4120
4121             return newElm || elm;
d9344f 4122         },
29da64 4123
A 4124         findCommonAncestor : function(a, b) {
4125             var ps = a, pe;
4126
4127             while (ps) {
4128                 pe = b;
4129
4130                 while (pe && ps != pe)
4131                     pe = pe.parentNode;
4132
4133                 if (ps == pe)
4134                     break;
4135
4136                 ps = ps.parentNode;
4137             }
4138
4139             if (!ps && a.ownerDocument)
4140                 return a.ownerDocument.documentElement;
4141
4142             return ps;
4143         },
d9344f 4144
S 4145         toHex : function(s) {
4146             var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4147
4148             function hex(s) {
4149                 s = parseInt(s).toString(16);
4150
4151                 return s.length > 1 ? s : '0' + s; // 0 -> 00
4152             };
4153
4154             if (c) {
4155                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4156
4157                 return s;
4158             }
4159
4160             return s;
4161         },
4162
4163         getClasses : function() {
4164             var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4165
4166             if (t.classes)
4167                 return t.classes;
4168
4169             function addClasses(s) {
4170                 // IE style imports
4171                 each(s.imports, function(r) {
4172                     addClasses(r);
4173                 });
4174
4175                 each(s.cssRules || s.rules, function(r) {
4176                     // Real type or fake it on IE
4177                     switch (r.type || 1) {
4178                         // Rule
4179                         case 1:
4180                             if (r.selectorText) {
4181                                 each(r.selectorText.split(','), function(v) {
4182                                     v = v.replace(/^\s*|\s*$|^\s\./g, "");
4183
4184                                     // Is internal or it doesn't contain a class
4185                                     if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4186                                         return;
4187
4188                                     // Remove everything but class name
4189                                     ov = v;
a9251b 4190                                     v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
d9344f 4191
S 4192                                     // Filter classes
4193                                     if (f && !(v = f(v, ov)))
4194                                         return;
4195
4196                                     if (!lo[v]) {
4197                                         cl.push({'class' : v});
4198                                         lo[v] = 1;
4199                                     }
4200                                 });
4201                             }
4202                             break;
4203
4204                         // Import
4205                         case 3:
4206                             addClasses(r.styleSheet);
4207                             break;
4208                     }
4209                 });
4210             };
4211
4212             try {
4213                 each(t.doc.styleSheets, addClasses);
4214             } catch (ex) {
4215                 // Ignore
4216             }
4217
4218             if (cl.length > 0)
4219                 t.classes = cl;
4220
4221             return cl;
4222         },
4223
4224         run : function(e, f, s) {
4225             var t = this, o;
4226
4227             if (t.doc && typeof(e) === 'string')
29da64 4228                 e = t.get(e);
d9344f 4229
S 4230             if (!e)
4231                 return false;
4232
4233             s = s || this;
4234             if (!e.nodeType && (e.length || e.length === 0)) {
4235                 o = [];
4236
4237                 each(e, function(e, i) {
4238                     if (e) {
4239                         if (typeof(e) == 'string')
4240                             e = t.doc.getElementById(e);
4241
4242                         o.push(f.call(s, e, i));
4243                     }
4244                 });
4245
4246                 return o;
4247             }
4248
4249             return f.call(s, e);
4250         },
4251
4252         getAttribs : function(n) {
4253             var o;
4254
4255             n = this.get(n);
4256
4257             if (!n)
4258                 return [];
4259
4260             if (isIE) {
4261                 o = [];
4262
4263                 // Object will throw exception in IE
4264                 if (n.nodeName == 'OBJECT')
4265                     return n.attributes;
4266
58fb65 4267                 // IE doesn't keep the selected attribute if you clone option elements
A 4268                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4269                     o.push({specified : 1, nodeName : 'selected'});
4270
d9344f 4271                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
69d05c 4272                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
58fb65 4273                     o.push({specified : 1, nodeName : a});
d9344f 4274                 });
S 4275
4276                 return o;
4277             }
4278
4279             return n.attributes;
4280         },
4281
a9251b 4282         isEmpty : function(node, elements) {
T 4283             var self = this, i, attributes, type, walker, name;
4284
4285             node = node.firstChild;
4286             if (node) {
4287                 walker = new tinymce.dom.TreeWalker(node);
4288                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4289
4290                 do {
4291                     type = node.nodeType;
4292
4293                     if (type === 1) {
4294                         // Ignore bogus elements
4295                         if (node.getAttribute('data-mce-bogus'))
4296                             continue;
4297
4298                         // Keep empty elements like <img />
4299                         if (elements && elements[node.nodeName.toLowerCase()])
4300                             return false;
4301
4302                         // Keep elements with data attributes or name attribute like <a name="1"></a>
4303                         attributes = self.getAttribs(node);
4304                         i = node.attributes.length;
4305                         while (i--) {
4306                             name = node.attributes[i].nodeName;
4307                             if (name === "name" || name.indexOf('data-') === 0)
4308                                 return false;
4309                         }
4310                     }
4311
4312                     // Keep non whitespace text nodes
4313                     if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4314                         return false;
4315                 } while (node = walker.next());
4316             }
4317
4318             return true;
4319         },
4320
d9344f 4321         destroy : function(s) {
S 4322             var t = this;
4323
58fb65 4324             if (t.events)
A 4325                 t.events.destroy();
4326
4327             t.win = t.doc = t.root = t.events = null;
d9344f 4328
S 4329             // Manual destroy then remove unload handler
4330             if (!s)
4331                 tinymce.removeUnload(t.destroy);
18240a 4332         },
A 4333
29da64 4334         createRng : function() {
A 4335             var d = this.doc;
4336
4337             return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4338         },
4339
69d05c 4340         nodeIndex : function(node, normalized) {
a9251b 4341             var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
69d05c 4342
A 4343             if (node) {
4344                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4345                     nodeType = node.nodeType;
4346
2011be 4347                     // Normalize text nodes
A 4348                     if (normalized && nodeType == 3) {
a9251b 4349                         // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
T 4350                         // (the nodeValue attribute will not exist, and will error here).
4351                         nodeValueExists = false;
4352                         try {nodeValueExists = node.nodeValue.length} catch (c) {}
4353                         if (nodeType == lastNodeType || !nodeValueExists)
2011be 4354                             continue;
A 4355                     }
4356                     idx++;
69d05c 4357                     lastNodeType = nodeType;
A 4358                 }
4359             }
4360
4361             return idx;
4362         },
4363
29da64 4364         split : function(pe, e, re) {
A 4365             var t = this, r = t.createRng(), bef, aft, pa;
4366
69d05c 4367             // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
A 4368             // but we don't want that in our code since it serves no purpose for the end user
29da64 4369             // For example if this is chopped:
A 4370             //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4371             // would produce:
4372             //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4373             // this function will then trim of empty edges and produce:
4374             //   <p>text 1</p><b>CHOP</b><p>text 2</p>
69d05c 4375             function trim(node) {
a9251b 4376                 var i, children = node.childNodes, type = node.nodeType;
29da64 4377
a9251b 4378                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
69d05c 4379                     return;
29da64 4380
69d05c 4381                 for (i = children.length - 1; i >= 0; i--)
A 4382                     trim(children[i]);
29da64 4383
a9251b 4384                 if (type != 9) {
69d05c 4385                     // Keep non whitespace text nodes
a9251b 4386                     if (type == 3 && node.nodeValue.length > 0) {
T 4387                         // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4388                         if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4389                             return;
4390                     } else if (type == 1) {
69d05c 4391                         // If the only child is a bookmark then move it up
A 4392                         children = node.childNodes;
a9251b 4393                         if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
69d05c 4394                             node.parentNode.insertBefore(children[0], node);
58fb65 4395
69d05c 4396                         // Keep non empty elements or img, hr etc
A 4397                         if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4398                             return;
4399                     }
4400
4401                     t.remove(node);
58fb65 4402                 }
A 4403
69d05c 4404                 return node;
58fb65 4405             };
A 4406
29da64 4407             if (pe && e) {
A 4408                 // Get before chunk
69d05c 4409                 r.setStart(pe.parentNode, t.nodeIndex(pe));
A 4410                 r.setEnd(e.parentNode, t.nodeIndex(e));
29da64 4411                 bef = r.extractContents();
A 4412
4413                 // Get after chunk
4414                 r = t.createRng();
69d05c 4415                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
A 4416                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
29da64 4417                 aft = r.extractContents();
A 4418
69d05c 4419                 // Insert before chunk
29da64 4420                 pa = pe.parentNode;
69d05c 4421                 pa.insertBefore(trim(bef), pe);
29da64 4422
69d05c 4423                 // Insert middle chunk
29da64 4424                 if (re)
A 4425                     pa.replaceChild(re, e);
4426                 else
4427                     pa.insertBefore(e, pe);
4428
69d05c 4429                 // Insert after chunk
A 4430                 pa.insertBefore(trim(aft), pe);
29da64 4431                 t.remove(pe);
A 4432
4433                 return re || e;
4434             }
4435         },
4436
58fb65 4437         bind : function(target, name, func, scope) {
A 4438             var t = this;
4439
4440             if (!t.events)
4441                 t.events = new tinymce.dom.EventUtils();
4442
4443             return t.events.add(target, name, func, scope || this);
4444         },
4445
4446         unbind : function(target, name, func) {
4447             var t = this;
4448
4449             if (!t.events)
4450                 t.events = new tinymce.dom.EventUtils();
4451
4452             return t.events.remove(target, name, func);
4453         },
4454
4455
4456         _findSib : function(node, selector, name) {
4457             var t = this, f = selector;
4458
4459             if (node) {
4460                 // If expression make a function of it using is
4461                 if (is(f, 'string')) {
4462                     f = function(node) {
4463                         return t.is(node, selector);
4464                     };
4465                 }
4466
4467                 // Loop all siblings
4468                 for (node = node[name]; node; node = node[name]) {
4469                     if (f(node))
4470                         return node;
4471                 }
4472             }
4473
4474             return null;
4475         },
29da64 4476
18240a 4477         _isRes : function(c) {
A 4478             // Is live resizble element
4479             return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
d9344f 4480         }
S 4481
4482         /*
4483         walk : function(n, f, s) {
4484             var d = this.doc, w;
4485
4486             if (d.createTreeWalker) {
4487                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4488
4489                 while ((n = w.nextNode()) != null)
4490                     f.call(s || this, n);
4491             } else
4492                 tinymce.walk(n, f, 'childNodes', s);
4493         }
4494         */
4495
4496         /*
4497         toRGB : function(s) {
4498             var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4499
4500             if (c) {
4501                 // #FFF -> #FFFFFF
4502                 if (!is(c[3]))
4503                     c[3] = c[2] = c[1];
4504
4505                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4506             }
4507
4508             return s;
4509         }
4510         */
58fb65 4511     });
d9344f 4512
S 4513     tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
29da64 4514 })(tinymce);
69d05c 4515
29da64 4516 (function(ns) {
A 4517     // Range constructor
4518     function Range(dom) {
69d05c 4519         var t = this,
A 4520             doc = dom.doc,
4521             EXTRACT = 0,
4522             CLONE = 1,
4523             DELETE = 2,
4524             TRUE = true,
4525             FALSE = false,
4526             START_OFFSET = 'startOffset',
4527             START_CONTAINER = 'startContainer',
4528             END_CONTAINER = 'endContainer',
4529             END_OFFSET = 'endOffset',
4530             extend = tinymce.extend,
4531             nodeIndex = dom.nodeIndex;
29da64 4532
69d05c 4533         extend(t, {
29da64 4534             // Inital states
69d05c 4535             startContainer : doc,
29da64 4536             startOffset : 0,
69d05c 4537             endContainer : doc,
29da64 4538             endOffset : 0,
69d05c 4539             collapsed : TRUE,
A 4540             commonAncestorContainer : doc,
29da64 4541
A 4542             // Range constants
4543             START_TO_START : 0,
4544             START_TO_END : 1,
4545             END_TO_END : 2,
69d05c 4546             END_TO_START : 3,
A 4547
4548             // Public methods
4549             setStart : setStart,
4550             setEnd : setEnd,
4551             setStartBefore : setStartBefore,
4552             setStartAfter : setStartAfter,
4553             setEndBefore : setEndBefore,
4554             setEndAfter : setEndAfter,
4555             collapse : collapse,
4556             selectNode : selectNode,
4557             selectNodeContents : selectNodeContents,
4558             compareBoundaryPoints : compareBoundaryPoints,
4559             deleteContents : deleteContents,
4560             extractContents : extractContents,
4561             cloneContents : cloneContents,
4562             insertNode : insertNode,
4563             surroundContents : surroundContents,
4564             cloneRange : cloneRange
29da64 4565         });
A 4566
69d05c 4567         function setStart(n, o) {
A 4568             _setEndPoint(TRUE, n, o);
4569         };
29da64 4570
69d05c 4571         function setEnd(n, o) {
A 4572             _setEndPoint(FALSE, n, o);
4573         };
29da64 4574
69d05c 4575         function setStartBefore(n) {
A 4576             setStart(n.parentNode, nodeIndex(n));
4577         };
29da64 4578
69d05c 4579         function setStartAfter(n) {
A 4580             setStart(n.parentNode, nodeIndex(n) + 1);
4581         };
29da64 4582
69d05c 4583         function setEndBefore(n) {
A 4584             setEnd(n.parentNode, nodeIndex(n));
4585         };
29da64 4586
69d05c 4587         function setEndAfter(n) {
A 4588             setEnd(n.parentNode, nodeIndex(n) + 1);
4589         };
29da64 4590
69d05c 4591         function collapse(ts) {
29da64 4592             if (ts) {
69d05c 4593                 t[END_CONTAINER] = t[START_CONTAINER];
A 4594                 t[END_OFFSET] = t[START_OFFSET];
29da64 4595             } else {
69d05c 4596                 t[START_CONTAINER] = t[END_CONTAINER];
A 4597                 t[START_OFFSET] = t[END_OFFSET];
29da64 4598             }
A 4599
69d05c 4600             t.collapsed = TRUE;
A 4601         };
29da64 4602
69d05c 4603         function selectNode(n) {
A 4604             setStartBefore(n);
4605             setEndAfter(n);
4606         };
29da64 4607
69d05c 4608         function selectNodeContents(n) {
A 4609             setStart(n, 0);
4610             setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
4611         };
29da64 4612
69d05c 4613         function compareBoundaryPoints(h, r) {
a9251b 4614             var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
T 4615             rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
29da64 4616
A 4617             // Check START_TO_START
4618             if (h === 0)
a9251b 4619                 return _compareBoundaryPoints(sc, so, rsc, rso);
T 4620     
29da64 4621             // Check START_TO_END
A 4622             if (h === 1)
a9251b 4623                 return _compareBoundaryPoints(ec, eo, rsc, rso);
T 4624     
29da64 4625             // Check END_TO_END
A 4626             if (h === 2)
a9251b 4627                 return _compareBoundaryPoints(ec, eo, rec, reo);
T 4628     
29da64 4629             // Check END_TO_START
a9251b 4630             if (h === 3) 
T 4631                 return _compareBoundaryPoints(sc, so, rec, reo);
69d05c 4632         };
29da64 4633
69d05c 4634         function deleteContents() {
A 4635             _traverse(DELETE);
4636         };
29da64 4637
69d05c 4638         function extractContents() {
A 4639             return _traverse(EXTRACT);
4640         };
29da64 4641
69d05c 4642         function cloneContents() {
A 4643             return _traverse(CLONE);
4644         };
29da64 4645
69d05c 4646         function insertNode(n) {
A 4647             var startContainer = this[START_CONTAINER],
4648                 startOffset = this[START_OFFSET], nn, o;
29da64 4649
A 4650             // Node is TEXT_NODE or CDATA
69d05c 4651             if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
A 4652                 if (!startOffset) {
4653                     // At the start of text
4654                     startContainer.parentNode.insertBefore(n, startContainer);
4655                 } else if (startOffset >= startContainer.nodeValue.length) {
4656                     // At the end of text
4657                     dom.insertAfter(n, startContainer);
4658                 } else {
4659                     // Middle, need to split
4660                     nn = startContainer.splitText(startOffset);
4661                     startContainer.parentNode.insertBefore(n, nn);
4662                 }
29da64 4663             } else {
A 4664                 // Insert element node
69d05c 4665                 if (startContainer.childNodes.length > 0)
A 4666                     o = startContainer.childNodes[startOffset];
29da64 4667
69d05c 4668                 if (o)
A 4669                     startContainer.insertBefore(n, o);
4670                 else
4671                     startContainer.appendChild(n);
29da64 4672             }
69d05c 4673         };
29da64 4674
69d05c 4675         function surroundContents(n) {
A 4676             var f = t.extractContents();
29da64 4677
A 4678             t.insertNode(n);
4679             n.appendChild(f);
4680             t.selectNode(n);
69d05c 4681         };
29da64 4682
69d05c 4683         function cloneRange() {
A 4684             return extend(new Range(dom), {
4685                 startContainer : t[START_CONTAINER],
4686                 startOffset : t[START_OFFSET],
4687                 endContainer : t[END_CONTAINER],
4688                 endOffset : t[END_OFFSET],
29da64 4689                 collapsed : t.collapsed,
A 4690                 commonAncestorContainer : t.commonAncestorContainer
4691             });
69d05c 4692         };
29da64 4693
69d05c 4694         // Private methods
29da64 4695
69d05c 4696         function _getSelectedNode(container, offset) {
A 4697             var child;
29da64 4698
69d05c 4699             if (container.nodeType == 3 /* TEXT_NODE */)
A 4700                 return container;
4701
4702             if (offset < 0)
4703                 return container;
4704
4705             child = container.firstChild;
4706             while (child && offset > 0) {
4707                 --offset;
4708                 child = child.nextSibling;
4709             }
4710
4711             if (child)
4712                 return child;
4713
4714             return container;
4715         };
4716
4717         function _isCollapsed() {
4718             return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
4719         };
4720
4721         function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
29da64 4722             var c, offsetC, n, cmnRoot, childA, childB;
a9251b 4723             
69d05c 4724             // In the first case the boundary-points have the same container. A is before B
A 4725             // if its offset is less than the offset of B, A is equal to B if its offset is
4726             // equal to the offset of B, and A is after B if its offset is greater than the
29da64 4727             // offset of B.
A 4728             if (containerA == containerB) {
69d05c 4729                 if (offsetA == offsetB)
29da64 4730                     return 0; // equal
69d05c 4731
A 4732                 if (offsetA < offsetB)
29da64 4733                     return -1; // before
69d05c 4734
A 4735                 return 1; // after
29da64 4736             }
A 4737
69d05c 4738             // In the second case a child node C of the container of A is an ancestor
A 4739             // container of B. In this case, A is before B if the offset of A is less than or
29da64 4740             // equal to the index of the child node C and A is after B otherwise.
A 4741             c = containerB;
69d05c 4742             while (c && c.parentNode != containerA)
29da64 4743                 c = c.parentNode;
69d05c 4744
29da64 4745             if (c) {
A 4746                 offsetC = 0;
4747                 n = containerA.firstChild;
4748
4749                 while (n != c && offsetC < offsetA) {
4750                     offsetC++;
4751                     n = n.nextSibling;
4752                 }
4753
69d05c 4754                 if (offsetA <= offsetC)
29da64 4755                     return -1; // before
69d05c 4756
A 4757                 return 1; // after
29da64 4758             }
A 4759
69d05c 4760             // In the third case a child node C of the container of B is an ancestor container
A 4761             // of A. In this case, A is before B if the index of the child node C is less than
29da64 4762             // the offset of B and A is after B otherwise.
A 4763             c = containerA;
4764             while (c && c.parentNode != containerB) {
4765                 c = c.parentNode;
4766             }
4767
4768             if (c) {
4769                 offsetC = 0;
4770                 n = containerB.firstChild;
4771
4772                 while (n != c && offsetC < offsetB) {
4773                     offsetC++;
4774                     n = n.nextSibling;
4775                 }
4776
69d05c 4777                 if (offsetC < offsetB)
29da64 4778                     return -1; // before
69d05c 4779
A 4780                 return 1; // after
29da64 4781             }
A 4782
69d05c 4783             // In the fourth case, none of three other cases hold: the containers of A and B
A 4784             // are siblings or descendants of sibling nodes. In this case, A is before B if
29da64 4785             // the container of A is before the container of B in a pre-order traversal of the
A 4786             // Ranges' context tree and A is after B otherwise.
69d05c 4787             cmnRoot = dom.findCommonAncestor(containerA, containerB);
29da64 4788             childA = containerA;
A 4789
69d05c 4790             while (childA && childA.parentNode != cmnRoot)
A 4791                 childA = childA.parentNode;
29da64 4792
69d05c 4793             if (!childA)
29da64 4794                 childA = cmnRoot;
A 4795
4796             childB = containerB;
69d05c 4797             while (childB && childB.parentNode != cmnRoot)
29da64 4798                 childB = childB.parentNode;
A 4799
69d05c 4800             if (!childB)
29da64 4801                 childB = cmnRoot;
A 4802
69d05c 4803             if (childA == childB)
29da64 4804                 return 0; // equal
A 4805
4806             n = cmnRoot.firstChild;
4807             while (n) {
69d05c 4808                 if (n == childA)
29da64 4809                     return -1; // before
A 4810
69d05c 4811                 if (n == childB)
29da64 4812                     return 1; // after
A 4813
4814                 n = n.nextSibling;
4815             }
69d05c 4816         };
29da64 4817
69d05c 4818         function _setEndPoint(st, n, o) {
A 4819             var ec, sc;
29da64 4820
A 4821             if (st) {
69d05c 4822                 t[START_CONTAINER] = n;
A 4823                 t[START_OFFSET] = o;
29da64 4824             } else {
69d05c 4825                 t[END_CONTAINER] = n;
A 4826                 t[END_OFFSET] = o;
29da64 4827             }
A 4828
69d05c 4829             // If one boundary-point of a Range is set to have a root container
A 4830             // other than the current one for the Range, the Range is collapsed to
29da64 4831             // the new position. This enforces the restriction that both boundary-
A 4832             // points of a Range must have the same root container.
69d05c 4833             ec = t[END_CONTAINER];
29da64 4834             while (ec.parentNode)
A 4835                 ec = ec.parentNode;
4836
69d05c 4837             sc = t[START_CONTAINER];
29da64 4838             while (sc.parentNode)
A 4839                 sc = sc.parentNode;
4840
69d05c 4841             if (sc == ec) {
A 4842                 // The start position of a Range is guaranteed to never be after the
4843                 // end position. To enforce this restriction, if the start is set to
4844                 // be at a position after the end, the Range is collapsed to that
29da64 4845                 // position.
69d05c 4846                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
29da64 4847                     t.collapse(st);
69d05c 4848             } else
A 4849                 t.collapse(st);
29da64 4850
69d05c 4851             t.collapsed = _isCollapsed();
A 4852             t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
4853         };
29da64 4854
69d05c 4855         function _traverse(how) {
A 4856             var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
29da64 4857
69d05c 4858             if (t[START_CONTAINER] == t[END_CONTAINER])
A 4859                 return _traverseSameContainer(how);
29da64 4860
69d05c 4861             for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
A 4862                 if (p == t[START_CONTAINER])
4863                     return _traverseCommonStartContainer(c, how);
29da64 4864
A 4865                 ++endContainerDepth;
4866             }
4867
69d05c 4868             for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
A 4869                 if (p == t[END_CONTAINER])
4870                     return _traverseCommonEndContainer(c, how);
29da64 4871
A 4872                 ++startContainerDepth;
4873             }
4874
4875             depthDiff = startContainerDepth - endContainerDepth;
4876
69d05c 4877             startNode = t[START_CONTAINER];
29da64 4878             while (depthDiff > 0) {
A 4879                 startNode = startNode.parentNode;
4880                 depthDiff--;
4881             }
4882
69d05c 4883             endNode = t[END_CONTAINER];
29da64 4884             while (depthDiff < 0) {
A 4885                 endNode = endNode.parentNode;
4886                 depthDiff++;
4887             }
4888
4889             // ascend the ancestor hierarchy until we have a common parent.
4890             for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
4891                 startNode = sp;
4892                 endNode = ep;
4893             }
4894
69d05c 4895             return _traverseCommonAncestors(startNode, endNode, how);
A 4896         };
29da64 4897
69d05c 4898          function _traverseSameContainer(how) {
A 4899             var frag, s, sub, n, cnt, sibling, xferNode;
29da64 4900
A 4901             if (how != DELETE)
69d05c 4902                 frag = doc.createDocumentFragment();
29da64 4903
A 4904             // If selection is empty, just return the fragment
69d05c 4905             if (t[START_OFFSET] == t[END_OFFSET])
29da64 4906                 return frag;
A 4907
4908             // Text node needs special case handling
69d05c 4909             if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
29da64 4910                 // get the substring
69d05c 4911                 s = t[START_CONTAINER].nodeValue;
A 4912                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
29da64 4913
A 4914                 // set the original text node to its new value
4915                 if (how != CLONE) {
69d05c 4916                     t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
29da64 4917
A 4918                     // Nothing is partially selected, so collapse to start point
69d05c 4919                     t.collapse(TRUE);
29da64 4920                 }
A 4921
4922                 if (how == DELETE)
69d05c 4923                     return;
29da64 4924
69d05c 4925                 frag.appendChild(doc.createTextNode(sub));
29da64 4926                 return frag;
A 4927             }
4928
4929             // Copy nodes between the start/end offsets.
69d05c 4930             n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
A 4931             cnt = t[END_OFFSET] - t[START_OFFSET];
29da64 4932
A 4933             while (cnt > 0) {
4934                 sibling = n.nextSibling;
69d05c 4935                 xferNode = _traverseFullySelected(n, how);
29da64 4936
A 4937                 if (frag)
4938                     frag.appendChild( xferNode );
4939
4940                 --cnt;
4941                 n = sibling;
4942             }
4943
4944             // Nothing is partially selected, so collapse to start point
4945             if (how != CLONE)
69d05c 4946                 t.collapse(TRUE);
29da64 4947
A 4948             return frag;
69d05c 4949         };
29da64 4950
69d05c 4951         function _traverseCommonStartContainer(endAncestor, how) {
A 4952             var frag, n, endIdx, cnt, sibling, xferNode;
29da64 4953
A 4954             if (how != DELETE)
69d05c 4955                 frag = doc.createDocumentFragment();
29da64 4956
69d05c 4957             n = _traverseRightBoundary(endAncestor, how);
29da64 4958
A 4959             if (frag)
4960                 frag.appendChild(n);
4961
69d05c 4962             endIdx = nodeIndex(endAncestor);
A 4963             cnt = endIdx - t[START_OFFSET];
29da64 4964
A 4965             if (cnt <= 0) {
69d05c 4966                 // Collapse to just before the endAncestor, which
29da64 4967                 // is partially selected.
A 4968                 if (how != CLONE) {
4969                     t.setEndBefore(endAncestor);
69d05c 4970                     t.collapse(FALSE);
29da64 4971                 }
A 4972
4973                 return frag;
4974             }
4975
4976             n = endAncestor.previousSibling;
4977             while (cnt > 0) {
4978                 sibling = n.previousSibling;
69d05c 4979                 xferNode = _traverseFullySelected(n, how);
29da64 4980
A 4981                 if (frag)
4982                     frag.insertBefore(xferNode, frag.firstChild);
4983
4984                 --cnt;
4985                 n = sibling;
4986             }
4987
69d05c 4988             // Collapse to just before the endAncestor, which
29da64 4989             // is partially selected.
A 4990             if (how != CLONE) {
4991                 t.setEndBefore(endAncestor);
69d05c 4992                 t.collapse(FALSE);
29da64 4993             }
A 4994
4995             return frag;
69d05c 4996         };
29da64 4997
69d05c 4998         function _traverseCommonEndContainer(startAncestor, how) {
A 4999             var frag, startIdx, n, cnt, sibling, xferNode;
29da64 5000
A 5001             if (how != DELETE)
69d05c 5002                 frag = doc.createDocumentFragment();
29da64 5003
69d05c 5004             n = _traverseLeftBoundary(startAncestor, how);
29da64 5005             if (frag)
A 5006                 frag.appendChild(n);
5007
69d05c 5008             startIdx = nodeIndex(startAncestor);
a9251b 5009             ++startIdx; // Because we already traversed it
29da64 5010
69d05c 5011             cnt = t[END_OFFSET] - startIdx;
29da64 5012             n = startAncestor.nextSibling;
A 5013             while (cnt > 0) {
5014                 sibling = n.nextSibling;
69d05c 5015                 xferNode = _traverseFullySelected(n, how);
29da64 5016
A 5017                 if (frag)
5018                     frag.appendChild(xferNode);
5019
5020                 --cnt;
5021                 n = sibling;
5022             }
5023
5024             if (how != CLONE) {
5025                 t.setStartAfter(startAncestor);
69d05c 5026                 t.collapse(TRUE);
29da64 5027             }
A 5028
5029             return frag;
69d05c 5030         };
29da64 5031
69d05c 5032         function _traverseCommonAncestors(startAncestor, endAncestor, how) {
A 5033             var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
29da64 5034
A 5035             if (how != DELETE)
69d05c 5036                 frag = doc.createDocumentFragment();
29da64 5037
69d05c 5038             n = _traverseLeftBoundary(startAncestor, how);
29da64 5039             if (frag)
A 5040                 frag.appendChild(n);
5041
5042             commonParent = startAncestor.parentNode;
69d05c 5043             startOffset = nodeIndex(startAncestor);
A 5044             endOffset = nodeIndex(endAncestor);
29da64 5045             ++startOffset;
A 5046
5047             cnt = endOffset - startOffset;
5048             sibling = startAncestor.nextSibling;
5049
5050             while (cnt > 0) {
5051                 nextSibling = sibling.nextSibling;
69d05c 5052                 n = _traverseFullySelected(sibling, how);
29da64 5053
A 5054                 if (frag)
5055                     frag.appendChild(n);
5056
5057                 sibling = nextSibling;
5058                 --cnt;
5059             }
5060
69d05c 5061             n = _traverseRightBoundary(endAncestor, how);
29da64 5062
A 5063             if (frag)
5064                 frag.appendChild(n);
5065
5066             if (how != CLONE) {
5067                 t.setStartAfter(startAncestor);
69d05c 5068                 t.collapse(TRUE);
29da64 5069             }
A 5070
5071             return frag;
69d05c 5072         };
29da64 5073
69d05c 5074         function _traverseRightBoundary(root, how) {
A 5075             var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
29da64 5076
A 5077             if (next == root)
69d05c 5078                 return _traverseNode(next, isFullySelected, FALSE, how);
29da64 5079
A 5080             parent = next.parentNode;
69d05c 5081             clonedParent = _traverseNode(parent, FALSE, FALSE, how);
29da64 5082
69d05c 5083             while (parent) {
A 5084                 while (next) {
29da64 5085                     prevSibling = next.previousSibling;
69d05c 5086                     clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
29da64 5087
A 5088                     if (how != DELETE)
5089                         clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5090
69d05c 5091                     isFullySelected = TRUE;
29da64 5092                     next = prevSibling;
A 5093                 }
5094
5095                 if (parent == root)
5096                     return clonedParent;
5097
5098                 next = parent.previousSibling;
5099                 parent = parent.parentNode;
5100
69d05c 5101                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
29da64 5102
A 5103                 if (how != DELETE)
5104                     clonedGrandParent.appendChild(clonedParent);
5105
5106                 clonedParent = clonedGrandParent;
5107             }
69d05c 5108         };
29da64 5109
69d05c 5110         function _traverseLeftBoundary(root, how) {
A 5111             var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
29da64 5112
A 5113             if (next == root)
69d05c 5114                 return _traverseNode(next, isFullySelected, TRUE, how);
29da64 5115
A 5116             parent = next.parentNode;
69d05c 5117             clonedParent = _traverseNode(parent, FALSE, TRUE, how);
29da64 5118
69d05c 5119             while (parent) {
A 5120                 while (next) {
29da64 5121                     nextSibling = next.nextSibling;
69d05c 5122                     clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
29da64 5123
A 5124                     if (how != DELETE)
5125                         clonedParent.appendChild(clonedChild);
5126
69d05c 5127                     isFullySelected = TRUE;
29da64 5128                     next = nextSibling;
A 5129                 }
5130
5131                 if (parent == root)
5132                     return clonedParent;
5133
5134                 next = parent.nextSibling;
5135                 parent = parent.parentNode;
5136
69d05c 5137                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
29da64 5138
A 5139                 if (how != DELETE)
5140                     clonedGrandParent.appendChild(clonedParent);
5141
5142                 clonedParent = clonedGrandParent;
5143             }
69d05c 5144         };
29da64 5145
69d05c 5146         function _traverseNode(n, isFullySelected, isLeft, how) {
A 5147             var txtValue, newNodeValue, oldNodeValue, offset, newNode;
29da64 5148
A 5149             if (isFullySelected)
69d05c 5150                 return _traverseFullySelected(n, how);
29da64 5151
A 5152             if (n.nodeType == 3 /* TEXT_NODE */) {
5153                 txtValue = n.nodeValue;
5154
5155                 if (isLeft) {
69d05c 5156                     offset = t[START_OFFSET];
29da64 5157                     newNodeValue = txtValue.substring(offset);
A 5158                     oldNodeValue = txtValue.substring(0, offset);
5159                 } else {
69d05c 5160                     offset = t[END_OFFSET];
29da64 5161                     newNodeValue = txtValue.substring(0, offset);
A 5162                     oldNodeValue = txtValue.substring(offset);
5163                 }
5164
5165                 if (how != CLONE)
5166                     n.nodeValue = oldNodeValue;
5167
5168                 if (how == DELETE)
69d05c 5169                     return;
29da64 5170
69d05c 5171                 newNode = n.cloneNode(FALSE);
29da64 5172                 newNode.nodeValue = newNodeValue;
A 5173
5174                 return newNode;
5175             }
5176
5177             if (how == DELETE)
69d05c 5178                 return;
29da64 5179
69d05c 5180             return n.cloneNode(FALSE);
A 5181         };
29da64 5182
69d05c 5183         function _traverseFullySelected(n, how) {
29da64 5184             if (how != DELETE)
69d05c 5185                 return how == CLONE ? n.cloneNode(TRUE) : n;
29da64 5186
A 5187             n.parentNode.removeChild(n);
69d05c 5188         };
A 5189     };
29da64 5190
A 5191     ns.Range = Range;
5192 })(tinymce.dom);
69d05c 5193
29da64 5194 (function() {
A 5195     function Selection(selection) {
69d05c 5196         var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
29da64 5197
69d05c 5198         // Returns a W3C DOM compatible range object by using the IE Range API
29da64 5199         function getRange() {
2011be 5200             var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
29da64 5201
A 5202             // If selection is outside the current document just return an empty range
5203             element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5204             if (element.ownerDocument != dom.doc)
5205                 return domRange;
5206
a9251b 5207             collapsed = selection.isCollapsed();
T 5208
29da64 5209             // Handle control selection or text selection of a image
A 5210             if (ieRange.item || !element.hasChildNodes()) {
a9251b 5211                 if (collapsed) {
T 5212                     domRange.setStart(element, 0);
5213                     domRange.setEnd(element, 0);
5214                 } else {
5215                     domRange.setStart(element.parentNode, dom.nodeIndex(element));
5216                     domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5217                 }
29da64 5218
A 5219                 return domRange;
5220             }
5221
2011be 5222             function findEndPoint(start) {
A 5223                 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
29da64 5224
2011be 5225                 // Setup temp range and collapse it
A 5226                 checkRng = ieRange.duplicate();
5227                 checkRng.collapse(start);
29da64 5228
2011be 5229                 // Create marker and insert it at the end of the endpoints parent
A 5230                 marker = dom.create('a');
5231                 parent = checkRng.parentElement();
69d05c 5232
2011be 5233                 // If parent doesn't have any children then set the container to that parent and the index to 0
A 5234                 if (!parent.hasChildNodes()) {
5235                     domRange[start ? 'setStart' : 'setEnd'](parent, 0);
5236                     return;
5237                 }
69d05c 5238
2011be 5239                 parent.appendChild(marker);
A 5240                 checkRng.moveToElementText(marker);
5241                 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5242                 if (position > 0) {
5243                     // The position is after the end of the parent element.
5244                     // This is the case where IE puts the caret to the left edge of a table.
5245                     domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
5246                     dom.remove(marker);
5247                     return;
5248                 }
5249
5250                 // Setup node list and endIndex
5251                 nodes = tinymce.grep(parent.childNodes);
5252                 endIndex = nodes.length - 1;
5253                 // Perform a binary search for the position
5254                 while (startIndex <= endIndex) {
5255                     index = Math.floor((startIndex + endIndex) / 2);
5256
5257                     // Insert marker and check it's position relative to the selection
5258                     parent.insertBefore(marker, nodes[index]);
5259                     checkRng.moveToElementText(marker);
5260                     position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5261                     if (position > 0) {
5262                         // Marker is to the right
5263                         startIndex = index + 1;
5264                     } else if (position < 0) {
5265                         // Marker is to the left
5266                         endIndex = index - 1;
5267                     } else {
5268                         // Maker is where we are
5269                         found = true;
5270                         break;
5271                     }
5272                 }
5273
5274                 // Setup container
5275                 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
5276
5277                 // Handle element selection
5278                 if (container.nodeType == 1) {
69d05c 5279                     dom.remove(marker);
A 5280
2011be 5281                     // Find offset and container
A 5282                     offset = dom.nodeIndex(container);
5283                     container = container.parentNode;
5284
5285                     // Move the offset if we are setting the end or the position is after an element
5286                     if (!start || index > 0)
5287                         offset++;
69d05c 5288                 } else {
2011be 5289                     // Calculate offset within text node
A 5290                     if (position > 0 || index == 0) {
5291                         checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
5292                         offset = checkRng.text.length;
69d05c 5293                     } else {
2011be 5294                         checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
A 5295                         offset = container.nodeValue.length - checkRng.text.length;
69d05c 5296                     }
A 5297
5298                     dom.remove(marker);
5299                 }
5300
2011be 5301                 domRange[start ? 'setStart' : 'setEnd'](container, offset);
69d05c 5302             };
A 5303
2011be 5304             // Find start point
A 5305             findEndPoint(true);
69d05c 5306
2011be 5307             // Find end point if needed
69d05c 5308             if (!collapsed)
2011be 5309                 findEndPoint();
29da64 5310
A 5311             return domRange;
5312         };
5313
5314         this.addRange = function(rng) {
a9251b 5315             var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
69d05c 5316
a9251b 5317             function setEndPoint(start) {
T 5318                 var container, offset, marker, tmpRng, nodes;
5319
5320                 marker = dom.create('a');
5321                 container = start ? startContainer : endContainer;
5322                 offset = start ? startOffset : endOffset;
5323                 tmpRng = ieRng.duplicate();
5324
5325                 if (container == doc || container == doc.documentElement) {
5326                     container = body;
5327                     offset = 0;
5328                 }
5329
5330                 if (container.nodeType == 3) {
5331                     container.parentNode.insertBefore(marker, container);
5332                     tmpRng.moveToElementText(marker);
5333                     tmpRng.moveStart('character', offset);
5334                     dom.remove(marker);
5335                     ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5336                 } else {
5337                     nodes = container.childNodes;
5338
5339                     if (nodes.length) {
5340                         if (offset >= nodes.length) {
5341                             dom.insertAfter(marker, nodes[nodes.length - 1]);
5342                         } else {
5343                             container.insertBefore(marker, nodes[offset]);
5344                         }
5345
5346                         tmpRng.moveToElementText(marker);
5347                     } else {
5348                         // Empty node selection for example <div>|</div>
5349                         marker = doc.createTextNode(invisibleChar);
5350                         container.appendChild(marker);
5351                         tmpRng.moveToElementText(marker.parentNode);
5352                         tmpRng.collapse(TRUE);
5353                     }
5354
5355                     ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5356                     dom.remove(marker);
5357                 }
5358             }
5359
5360             // Destroy cached range
69d05c 5361             this.destroy();
29da64 5362
A 5363             // Setup some shorter versions
a9251b 5364             startContainer = rng.startContainer;
T 5365             startOffset = rng.startOffset;
5366             endContainer = rng.endContainer;
5367             endOffset = rng.endOffset;
29da64 5368             ieRng = body.createTextRange();
A 5369
a9251b 5370             // If single element selection then try making a control selection out of it
T 5371             if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
5372                 if (startOffset == endOffset - 1) {
5373                     try {
5374                         ctrlRng = body.createControlRange();
5375                         ctrlRng.addElement(startContainer.childNodes[startOffset]);
5376                         ctrlRng.select();
5377                         return;
5378                     } catch (ex) {
5379                         // Ignore
29da64 5380                     }
A 5381                 }
5382             }
5383
a9251b 5384             // Set start/end point of selection
T 5385             setEndPoint(true);
5386             setEndPoint();
29da64 5387
a9251b 5388             // Select the new range and scroll it into view
69d05c 5389             ieRng.select();
29da64 5390         };
A 5391
5392         this.getRangeAt = function() {
5393             // Setup new range if the cache is empty
2011be 5394             if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
29da64 5395                 range = getRange();
A 5396
5397                 // Store away text range for next call
5398                 lastIERng = selection.getRng();
2011be 5399             }
A 5400
5401             // IE will say that the range is equal then produce an invalid argument exception
5402             // if you perform specific operations in a keyup event. For example Ctrl+Del.
5403             // This hack will invalidate the range cache if the exception occurs
5404             try {
5405                 range.startContainer.nextSibling;
5406             } catch (ex) {
5407                 range = getRange();
5408                 lastIERng = null;
29da64 5409             }
A 5410
5411             // Return cached range
5412             return range;
5413         };
5414
5415         this.destroy = function() {
5416             // Destroy cached range and last IE range to avoid memory leaks
5417             lastIERng = range = null;
5418         };
5419     };
5420
5421     // Expose the selection object
5422     tinymce.dom.TridentSelection = Selection;
d9344f 5423 })();
69d05c 5424
d9344f 5425
29da64 5426 /*
A 5427  * Sizzle CSS Selector Engine - v1.0
5428  *  Copyright 2009, The Dojo Foundation
5429  *  Released under the MIT, BSD, and GPL Licenses.
5430  *  More information: http://sizzlejs.com/
5431  */
5432 (function(){
d9344f 5433
2011be 5434 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
29da64 5435     done = 0,
A 5436     toString = Object.prototype.toString,
2011be 5437     hasDuplicate = false,
A 5438     baseHasDuplicate = true;
5439
5440 // Here we check if the JavaScript engine is using some sort of
5441 // optimization where it does not always call our comparision
5442 // function. If that is the case, discard the hasDuplicate value.
5443 //   Thus far that includes Google Chrome.
5444 [0, 0].sort(function(){
5445     baseHasDuplicate = false;
5446     return 0;
5447 });
29da64 5448
A 5449 var Sizzle = function(selector, context, results, seed) {
5450     results = results || [];
2011be 5451     context = context || document;
A 5452
5453     var origContext = context;
29da64 5454
A 5455     if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
5456         return [];
5457     }
5458     
5459     if ( !selector || typeof selector !== "string" ) {
5460         return results;
5461     }
5462
2011be 5463     var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
A 5464         soFar = selector, ret, cur, pop, i;
29da64 5465     
A 5466     // Reset the position of the chunker regexp (start from head)
2011be 5467     do {
A 5468         chunker.exec("");
5469         m = chunker.exec(soFar);
5470
5471         if ( m ) {
5472             soFar = m[3];
29da64 5473         
2011be 5474             parts.push( m[1] );
A 5475         
5476             if ( m[2] ) {
5477                 extra = m[3];
5478                 break;
5479             }
29da64 5480         }
2011be 5481     } while ( m );
29da64 5482
A 5483     if ( parts.length > 1 && origPOS.exec( selector ) ) {
5484         if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
5485             set = posProcess( parts[0] + parts[1], context );
5486         } else {
5487             set = Expr.relative[ parts[0] ] ?
5488                 [ context ] :
5489                 Sizzle( parts.shift(), context );
5490
5491             while ( parts.length ) {
5492                 selector = parts.shift();
5493
2011be 5494                 if ( Expr.relative[ selector ] ) {
29da64 5495                     selector += parts.shift();
2011be 5496                 }
A 5497                 
29da64 5498                 set = posProcess( selector, set );
A 5499             }
5500         }
5501     } else {
5502         // Take a shortcut and set the context if the root selector is an ID
5503         // (but not if it'll be faster if the inner selector is an ID)
5504         if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
5505                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
2011be 5506             ret = Sizzle.find( parts.shift(), context, contextXML );
29da64 5507             context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
A 5508         }
5509
5510         if ( context ) {
2011be 5511             ret = seed ?
29da64 5512                 { expr: parts.pop(), set: makeArray(seed) } :
A 5513                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
5514             set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
5515
5516             if ( parts.length > 0 ) {
5517                 checkSet = makeArray(set);
5518             } else {
5519                 prune = false;
5520             }
5521
5522             while ( parts.length ) {
2011be 5523                 cur = parts.pop();
A 5524                 pop = cur;
29da64 5525
A 5526                 if ( !Expr.relative[ cur ] ) {
5527                     cur = "";
5528                 } else {
5529                     pop = parts.pop();
5530                 }
5531
5532                 if ( pop == null ) {
5533                     pop = context;
5534                 }
5535
5536                 Expr.relative[ cur ]( checkSet, pop, contextXML );
5537             }
5538         } else {
5539             checkSet = parts = [];
5540         }
5541     }
5542
5543     if ( !checkSet ) {
5544         checkSet = set;
5545     }
5546
5547     if ( !checkSet ) {
2011be 5548         Sizzle.error( cur || selector );
29da64 5549     }
A 5550
5551     if ( toString.call(checkSet) === "[object Array]" ) {
5552         if ( !prune ) {
5553             results.push.apply( results, checkSet );
5554         } else if ( context && context.nodeType === 1 ) {
2011be 5555             for ( i = 0; checkSet[i] != null; i++ ) {
A 5556                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
29da64 5557                     results.push( set[i] );
A 5558                 }
5559             }
5560         } else {
2011be 5561             for ( i = 0; checkSet[i] != null; i++ ) {
29da64 5562                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
A 5563                     results.push( set[i] );
5564                 }
5565             }
5566         }
5567     } else {
5568         makeArray( checkSet, results );
5569     }
5570
5571     if ( extra ) {
5572         Sizzle( extra, origContext, results, seed );
5573         Sizzle.uniqueSort( results );
5574     }
5575
5576     return results;
5577 };
5578
5579 Sizzle.uniqueSort = function(results){
5580     if ( sortOrder ) {
2011be 5581         hasDuplicate = baseHasDuplicate;
29da64 5582         results.sort(sortOrder);
A 5583
5584         if ( hasDuplicate ) {
5585             for ( var i = 1; i < results.length; i++ ) {
5586                 if ( results[i] === results[i-1] ) {
5587                     results.splice(i--, 1);
5588                 }
5589             }
5590         }
5591     }
2011be 5592
A 5593     return results;
29da64 5594 };
A 5595
5596 Sizzle.matches = function(expr, set){
5597     return Sizzle(expr, null, null, set);
5598 };
5599
5600 Sizzle.find = function(expr, context, isXML){
2011be 5601     var set;
29da64 5602
A 5603     if ( !expr ) {
5604         return [];
5605     }
5606
5607     for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
5608         var type = Expr.order[i], match;
5609         
2011be 5610         if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
A 5611             var left = match[1];
5612             match.splice(1,1);
29da64 5613
A 5614             if ( left.substr( left.length - 1 ) !== "\\" ) {
5615                 match[1] = (match[1] || "").replace(/\\/g, "");
5616                 set = Expr.find[ type ]( match, context, isXML );
5617                 if ( set != null ) {
5618                     expr = expr.replace( Expr.match[ type ], "" );
5619                     break;
5620                 }
5621             }
5622         }
5623     }
5624
5625     if ( !set ) {
5626         set = context.getElementsByTagName("*");
5627     }
5628
5629     return {set: set, expr: expr};
5630 };
5631
5632 Sizzle.filter = function(expr, set, inplace, not){
5633     var old = expr, result = [], curLoop = set, match, anyFound,
2011be 5634         isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
29da64 5635
A 5636     while ( expr && set.length ) {
5637         for ( var type in Expr.filter ) {
2011be 5638             if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
A 5639                 var filter = Expr.filter[ type ], found, item, left = match[1];
29da64 5640                 anyFound = false;
A 5641
2011be 5642                 match.splice(1,1);
A 5643
5644                 if ( left.substr( left.length - 1 ) === "\\" ) {
5645                     continue;
5646                 }
5647
5648                 if ( curLoop === result ) {
29da64 5649                     result = [];
A 5650                 }
5651
5652                 if ( Expr.preFilter[ type ] ) {
5653                     match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
5654
5655                     if ( !match ) {
5656                         anyFound = found = true;
5657                     } else if ( match === true ) {
5658                         continue;
5659                     }
5660                 }
5661
5662                 if ( match ) {
5663                     for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
5664                         if ( item ) {
5665                             found = filter( item, match, i, curLoop );
5666                             var pass = not ^ !!found;
5667
5668                             if ( inplace && found != null ) {
5669                                 if ( pass ) {
5670                                     anyFound = true;
5671                                 } else {
5672                                     curLoop[i] = false;
5673                                 }
5674                             } else if ( pass ) {
5675                                 result.push( item );
5676                                 anyFound = true;
5677                             }
5678                         }
5679                     }
5680                 }
5681
5682                 if ( found !== undefined ) {
5683                     if ( !inplace ) {
5684                         curLoop = result;
5685                     }
5686
5687                     expr = expr.replace( Expr.match[ type ], "" );
5688
5689                     if ( !anyFound ) {
5690                         return [];
5691                     }
5692
5693                     break;
5694                 }
5695             }
5696         }
5697
5698         // Improper expression
2011be 5699         if ( expr === old ) {
29da64 5700             if ( anyFound == null ) {
2011be 5701                 Sizzle.error( expr );
29da64 5702             } else {
A 5703                 break;
5704             }
5705         }
5706
5707         old = expr;
5708     }
5709
5710     return curLoop;
5711 };
5712
2011be 5713 Sizzle.error = function( msg ) {
A 5714     throw "Syntax error, unrecognized expression: " + msg;
5715 };
5716
29da64 5717 var Expr = Sizzle.selectors = {
A 5718     order: [ "ID", "NAME", "TAG" ],
5719     match: {
2011be 5720         ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
A 5721         CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
5722         NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
5723         ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
5724         TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
5725         CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
5726         POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
5727         PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
29da64 5728     },
2011be 5729     leftMatch: {},
29da64 5730     attrMap: {
A 5731         "class": "className",
5732         "for": "htmlFor"
5733     },
5734     attrHandle: {
5735         href: function(elem){
5736             return elem.getAttribute("href");
5737         }
5738     },
5739     relative: {
2011be 5740         "+": function(checkSet, part){
29da64 5741             var isPartStr = typeof part === "string",
A 5742                 isTag = isPartStr && !/\W/.test(part),
5743                 isPartStrNotTag = isPartStr && !isTag;
5744
2011be 5745             if ( isTag ) {
A 5746                 part = part.toLowerCase();
29da64 5747             }
A 5748
5749             for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
5750                 if ( (elem = checkSet[i]) ) {
5751                     while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
5752
2011be 5753                     checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
29da64 5754                         elem || false :
A 5755                         elem === part;
5756                 }
5757             }
5758
5759             if ( isPartStrNotTag ) {
5760                 Sizzle.filter( part, checkSet, true );
5761             }
5762         },
2011be 5763         ">": function(checkSet, part){
A 5764             var isPartStr = typeof part === "string",
5765                 elem, i = 0, l = checkSet.length;
29da64 5766
A 5767             if ( isPartStr && !/\W/.test(part) ) {
2011be 5768                 part = part.toLowerCase();
29da64 5769
2011be 5770                 for ( ; i < l; i++ ) {
A 5771                     elem = checkSet[i];
29da64 5772                     if ( elem ) {
A 5773                         var parent = elem.parentNode;
2011be 5774                         checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
29da64 5775                     }
A 5776                 }
5777             } else {
2011be 5778                 for ( ; i < l; i++ ) {
A 5779                     elem = checkSet[i];
29da64 5780                     if ( elem ) {
A 5781                         checkSet[i] = isPartStr ?
5782                             elem.parentNode :
5783                             elem.parentNode === part;
5784                     }
5785                 }
5786
5787                 if ( isPartStr ) {
5788                     Sizzle.filter( part, checkSet, true );
5789                 }
5790             }
5791         },
5792         "": function(checkSet, part, isXML){
2011be 5793             var doneName = done++, checkFn = dirCheck, nodeCheck;
29da64 5794
2011be 5795             if ( typeof part === "string" && !/\W/.test(part) ) {
A 5796                 part = part.toLowerCase();
5797                 nodeCheck = part;
29da64 5798                 checkFn = dirNodeCheck;
A 5799             }
5800
5801             checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
5802         },
5803         "~": function(checkSet, part, isXML){
2011be 5804             var doneName = done++, checkFn = dirCheck, nodeCheck;
29da64 5805
2011be 5806             if ( typeof part === "string" && !/\W/.test(part) ) {
A 5807                 part = part.toLowerCase();
5808                 nodeCheck = part;
29da64 5809                 checkFn = dirNodeCheck;
A 5810             }
5811
5812             checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
5813         }
5814     },
5815     find: {
5816         ID: function(match, context, isXML){
5817             if ( typeof context.getElementById !== "undefined" && !isXML ) {
5818                 var m = context.getElementById(match[1]);
5819                 return m ? [m] : [];
5820             }
5821         },
2011be 5822         NAME: function(match, context){
29da64 5823             if ( typeof context.getElementsByName !== "undefined" ) {
A 5824                 var ret = [], results = context.getElementsByName(match[1]);
5825
5826                 for ( var i = 0, l = results.length; i < l; i++ ) {
5827                     if ( results[i].getAttribute("name") === match[1] ) {
5828                         ret.push( results[i] );
5829                     }
5830                 }
5831
5832                 return ret.length === 0 ? null : ret;
5833             }
5834         },
5835         TAG: function(match, context){
5836             return context.getElementsByTagName(match[1]);
5837         }
5838     },
5839     preFilter: {
5840         CLASS: function(match, curLoop, inplace, result, not, isXML){
5841             match = " " + match[1].replace(/\\/g, "") + " ";
5842
5843             if ( isXML ) {
5844                 return match;
5845             }
5846
5847             for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
5848                 if ( elem ) {
2011be 5849                     if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
A 5850                         if ( !inplace ) {
29da64 5851                             result.push( elem );
2011be 5852                         }
29da64 5853                     } else if ( inplace ) {
A 5854                         curLoop[i] = false;
5855                     }
5856                 }
5857             }
5858
5859             return false;
5860         },
5861         ID: function(match){
5862             return match[1].replace(/\\/g, "");
5863         },
5864         TAG: function(match, curLoop){
2011be 5865             return match[1].toLowerCase();
29da64 5866         },
A 5867         CHILD: function(match){
2011be 5868             if ( match[1] === "nth" ) {
29da64 5869                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
A 5870                 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
2011be 5871                     match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
29da64 5872                     !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
A 5873
5874                 // calculate the numbers (first)n+(last) including if they are negative
5875                 match[2] = (test[1] + (test[2] || 1)) - 0;
5876                 match[3] = test[3] - 0;
5877             }
5878
5879             // TODO: Move to normal caching system
5880             match[0] = done++;
5881
5882             return match;
5883         },
5884         ATTR: function(match, curLoop, inplace, result, not, isXML){
5885             var name = match[1].replace(/\\/g, "");
5886             
5887             if ( !isXML && Expr.attrMap[name] ) {
5888                 match[1] = Expr.attrMap[name];
5889             }
5890
5891             if ( match[2] === "~=" ) {
5892                 match[4] = " " + match[4] + " ";
5893             }
5894
5895             return match;
5896         },
5897         PSEUDO: function(match, curLoop, inplace, result, not){
5898             if ( match[1] === "not" ) {
5899                 // If we're dealing with a complex expression, or a simple one
2011be 5900                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
29da64 5901                     match[3] = Sizzle(match[3], null, null, curLoop);
A 5902                 } else {
5903                     var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
5904                     if ( !inplace ) {
5905                         result.push.apply( result, ret );
5906                     }
5907                     return false;
5908                 }
5909             } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
5910                 return true;
5911             }
5912             
5913             return match;
5914         },
5915         POS: function(match){
5916             match.unshift( true );
5917             return match;
5918         }
5919     },
5920     filters: {
5921         enabled: function(elem){
5922             return elem.disabled === false && elem.type !== "hidden";
5923         },
5924         disabled: function(elem){
5925             return elem.disabled === true;
5926         },
5927         checked: function(elem){
5928             return elem.checked === true;
5929         },
5930         selected: function(elem){
5931             // Accessing this property makes selected-by-default
5932             // options in Safari work properly
5933             elem.parentNode.selectedIndex;
5934             return elem.selected === true;
5935         },
5936         parent: function(elem){
5937             return !!elem.firstChild;
5938         },
5939         empty: function(elem){
5940             return !elem.firstChild;
5941         },
5942         has: function(elem, i, match){
5943             return !!Sizzle( match[3], elem ).length;
5944         },
5945         header: function(elem){
2011be 5946             return (/h\d/i).test( elem.nodeName );
29da64 5947         },
A 5948         text: function(elem){
5949             return "text" === elem.type;
5950         },
5951         radio: function(elem){
5952             return "radio" === elem.type;
5953         },
5954         checkbox: function(elem){
5955             return "checkbox" === elem.type;
5956         },
5957         file: function(elem){
5958             return "file" === elem.type;
5959         },
5960         password: function(elem){
5961             return "password" === elem.type;
5962         },
5963         submit: function(elem){
5964             return "submit" === elem.type;
5965         },
5966         image: function(elem){
5967             return "image" === elem.type;
5968         },
5969         reset: function(elem){
5970             return "reset" === elem.type;
5971         },
5972         button: function(elem){
2011be 5973             return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
29da64 5974         },
A 5975         input: function(elem){
2011be 5976             return (/input|select|textarea|button/i).test(elem.nodeName);
29da64 5977         }
A 5978     },
5979     setFilters: {
5980         first: function(elem, i){
5981             return i === 0;
5982         },
5983         last: function(elem, i, match, array){
5984             return i === array.length - 1;
5985         },
5986         even: function(elem, i){
5987             return i % 2 === 0;
5988         },
5989         odd: function(elem, i){
5990             return i % 2 === 1;
5991         },
5992         lt: function(elem, i, match){
5993             return i < match[3] - 0;
5994         },
5995         gt: function(elem, i, match){
5996             return i > match[3] - 0;
5997         },
5998         nth: function(elem, i, match){
2011be 5999             return match[3] - 0 === i;
29da64 6000         },
A 6001         eq: function(elem, i, match){
2011be 6002             return match[3] - 0 === i;
29da64 6003         }
A 6004     },
6005     filter: {
6006         PSEUDO: function(elem, match, i, array){
6007             var name = match[1], filter = Expr.filters[ name ];
6008
6009             if ( filter ) {
6010                 return filter( elem, i, match, array );
6011             } else if ( name === "contains" ) {
2011be 6012                 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
29da64 6013             } else if ( name === "not" ) {
A 6014                 var not = match[3];
6015
2011be 6016                 for ( var j = 0, l = not.length; j < l; j++ ) {
A 6017                     if ( not[j] === elem ) {
29da64 6018                         return false;
A 6019                     }
6020                 }
6021
6022                 return true;
2011be 6023             } else {
A 6024                 Sizzle.error( "Syntax error, unrecognized expression: " + name );
29da64 6025             }
A 6026         },
6027         CHILD: function(elem, match){
6028             var type = match[1], node = elem;
6029             switch (type) {
6030                 case 'only':
6031                 case 'first':
2011be 6032                     while ( (node = node.previousSibling) )     {
A 6033                         if ( node.nodeType === 1 ) { 
6034                             return false; 
6035                         }
29da64 6036                     }
2011be 6037                     if ( type === "first" ) { 
A 6038                         return true; 
6039                     }
29da64 6040                     node = elem;
A 6041                 case 'last':
2011be 6042                     while ( (node = node.nextSibling) )     {
A 6043                         if ( node.nodeType === 1 ) { 
6044                             return false; 
6045                         }
29da64 6046                     }
A 6047                     return true;
6048                 case 'nth':
6049                     var first = match[2], last = match[3];
6050
2011be 6051                     if ( first === 1 && last === 0 ) {
29da64 6052                         return true;
A 6053                     }
6054                     
6055                     var doneName = match[0],
6056                         parent = elem.parentNode;
6057     
6058                     if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
6059                         var count = 0;
6060                         for ( node = parent.firstChild; node; node = node.nextSibling ) {
6061                             if ( node.nodeType === 1 ) {
6062                                 node.nodeIndex = ++count;
6063                             }
6064                         } 
6065                         parent.sizcache = doneName;
6066                     }
6067                     
6068                     var diff = elem.nodeIndex - last;
2011be 6069                     if ( first === 0 ) {
A 6070                         return diff === 0;
29da64 6071                     } else {
2011be 6072                         return ( diff % first === 0 && diff / first >= 0 );
29da64 6073                     }
A 6074             }
6075         },
6076         ID: function(elem, match){
6077             return elem.nodeType === 1 && elem.getAttribute("id") === match;
6078         },
6079         TAG: function(elem, match){
2011be 6080             return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
29da64 6081         },
A 6082         CLASS: function(elem, match){
6083             return (" " + (elem.className || elem.getAttribute("class")) + " ")
6084                 .indexOf( match ) > -1;
6085         },
6086         ATTR: function(elem, match){
6087             var name = match[1],
6088                 result = Expr.attrHandle[ name ] ?
6089                     Expr.attrHandle[ name ]( elem ) :
6090                     elem[ name ] != null ?
6091                         elem[ name ] :
6092                         elem.getAttribute( name ),
6093                 value = result + "",
6094                 type = match[2],
6095                 check = match[4];
6096
6097             return result == null ?
6098                 type === "!=" :
6099                 type === "=" ?
6100                 value === check :
6101                 type === "*=" ?
6102                 value.indexOf(check) >= 0 :
6103                 type === "~=" ?
6104                 (" " + value + " ").indexOf(check) >= 0 :
6105                 !check ?
6106                 value && result !== false :
6107                 type === "!=" ?
2011be 6108                 value !== check :
29da64 6109                 type === "^=" ?
A 6110                 value.indexOf(check) === 0 :
6111                 type === "$=" ?
6112                 value.substr(value.length - check.length) === check :
6113                 type === "|=" ?
6114                 value === check || value.substr(0, check.length + 1) === check + "-" :
6115                 false;
6116         },
6117         POS: function(elem, match, i, array){
6118             var name = match[2], filter = Expr.setFilters[ name ];
6119
6120             if ( filter ) {
6121                 return filter( elem, i, match, array );
6122             }
6123         }
6124     }
6125 };
6126
2011be 6127 var origPOS = Expr.match.POS,
A 6128     fescape = function(all, num){
6129         return "\\" + (num - 0 + 1);
6130     };
29da64 6131
A 6132 for ( var type in Expr.match ) {
2011be 6133     Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
A 6134     Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
29da64 6135 }
A 6136
6137 var makeArray = function(array, results) {
2011be 6138     array = Array.prototype.slice.call( array, 0 );
29da64 6139
A 6140     if ( results ) {
6141         results.push.apply( results, array );
6142         return results;
6143     }
6144     
6145     return array;
6146 };
6147
6148 // Perform a simple check to determine if the browser is capable of
6149 // converting a NodeList to an array using builtin methods.
2011be 6150 // Also verifies that the returned array holds DOM nodes
A 6151 // (which is not the case in the Blackberry browser)
29da64 6152 try {
2011be 6153     Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
29da64 6154
A 6155 // Provide a fallback method if it does not work
6156 } catch(e){
6157     makeArray = function(array, results) {
2011be 6158         var ret = results || [], i = 0;
29da64 6159
A 6160         if ( toString.call(array) === "[object Array]" ) {
6161             Array.prototype.push.apply( ret, array );
6162         } else {
6163             if ( typeof array.length === "number" ) {
2011be 6164                 for ( var l = array.length; i < l; i++ ) {
29da64 6165                     ret.push( array[i] );
A 6166                 }
6167             } else {
2011be 6168                 for ( ; array[i]; i++ ) {
29da64 6169                     ret.push( array[i] );
A 6170                 }
6171             }
6172         }
6173
6174         return ret;
6175     };
6176 }
6177
6178 var sortOrder;
6179
6180 if ( document.documentElement.compareDocumentPosition ) {
6181     sortOrder = function( a, b ) {
2011be 6182         if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
A 6183             if ( a == b ) {
6184                 hasDuplicate = true;
6185             }
6186             return a.compareDocumentPosition ? -1 : 1;
6187         }
6188
29da64 6189         var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
A 6190         if ( ret === 0 ) {
6191             hasDuplicate = true;
6192         }
6193         return ret;
6194     };
6195 } else if ( "sourceIndex" in document.documentElement ) {
6196     sortOrder = function( a, b ) {
2011be 6197         if ( !a.sourceIndex || !b.sourceIndex ) {
A 6198             if ( a == b ) {
6199                 hasDuplicate = true;
6200             }
6201             return a.sourceIndex ? -1 : 1;
6202         }
6203
29da64 6204         var ret = a.sourceIndex - b.sourceIndex;
A 6205         if ( ret === 0 ) {
6206             hasDuplicate = true;
6207         }
6208         return ret;
6209     };
6210 } else if ( document.createRange ) {
6211     sortOrder = function( a, b ) {
2011be 6212         if ( !a.ownerDocument || !b.ownerDocument ) {
A 6213             if ( a == b ) {
6214                 hasDuplicate = true;
6215             }
6216             return a.ownerDocument ? -1 : 1;
6217         }
6218
29da64 6219         var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
A 6220         aRange.setStart(a, 0);
6221         aRange.setEnd(a, 0);
6222         bRange.setStart(b, 0);
6223         bRange.setEnd(b, 0);
6224         var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
6225         if ( ret === 0 ) {
6226             hasDuplicate = true;
6227         }
6228         return ret;
6229     };
6230 }
6231
2011be 6232 // Utility function for retreiving the text value of an array of DOM nodes
A 6233 Sizzle.getText = function( elems ) {
6234     var ret = "", elem;
6235
6236     for ( var i = 0; elems[i]; i++ ) {
6237         elem = elems[i];
6238
6239         // Get the text from text nodes and CDATA nodes
6240         if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
6241             ret += elem.nodeValue;
6242
6243         // Traverse everything else, except comment nodes
6244         } else if ( elem.nodeType !== 8 ) {
6245             ret += Sizzle.getText( elem.childNodes );
6246         }
6247     }
6248
6249     return ret;
6250 };
6251
29da64 6252 // Check to see if the browser returns elements by name when
A 6253 // querying by getElementById (and provide a workaround)
6254 (function(){
6255     // We're going to inject a fake input element with a specified name
6256     var form = document.createElement("div"),
2011be 6257         id = "script" + (new Date()).getTime();
29da64 6258     form.innerHTML = "<a name='" + id + "'/>";
A 6259
6260     // Inject it into the root element, check its status, and remove it quickly
6261     var root = document.documentElement;
6262     root.insertBefore( form, root.firstChild );
6263
6264     // The workaround has to do additional checks after a getElementById
6265     // Which slows things down for other browsers (hence the branching)
2011be 6266     if ( document.getElementById( id ) ) {
29da64 6267         Expr.find.ID = function(match, context, isXML){
A 6268             if ( typeof context.getElementById !== "undefined" && !isXML ) {
6269                 var m = context.getElementById(match[1]);
6270                 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
6271             }
6272         };
6273
6274         Expr.filter.ID = function(elem, match){
6275             var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
6276             return elem.nodeType === 1 && node && node.nodeValue === match;
6277         };
6278     }
6279
6280     root.removeChild( form );
2011be 6281     root = form = null; // release memory in IE
29da64 6282 })();
A 6283
6284 (function(){
6285     // Check to see if the browser returns only elements
6286     // when doing getElementsByTagName("*")
6287
6288     // Create a fake element
6289     var div = document.createElement("div");
6290     div.appendChild( document.createComment("") );
6291
6292     // Make sure no comments are found
6293     if ( div.getElementsByTagName("*").length > 0 ) {
6294         Expr.find.TAG = function(match, context){
6295             var results = context.getElementsByTagName(match[1]);
6296
6297             // Filter out possible comments
6298             if ( match[1] === "*" ) {
6299                 var tmp = [];
6300
6301                 for ( var i = 0; results[i]; i++ ) {
6302                     if ( results[i].nodeType === 1 ) {
6303                         tmp.push( results[i] );
6304                     }
6305                 }
6306
6307                 results = tmp;
6308             }
6309
6310             return results;
6311         };
6312     }
6313
6314     // Check to see if an attribute returns normalized href attributes
6315     div.innerHTML = "<a href='#'></a>";
6316     if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
6317             div.firstChild.getAttribute("href") !== "#" ) {
6318         Expr.attrHandle.href = function(elem){
6319             return elem.getAttribute("href", 2);
6320         };
6321     }
2011be 6322
A 6323     div = null; // release memory in IE
29da64 6324 })();
A 6325
2011be 6326 if ( document.querySelectorAll ) {
A 6327     (function(){
6328         var oldSizzle = Sizzle, div = document.createElement("div");
6329         div.innerHTML = "<p class='TEST'></p>";
29da64 6330
2011be 6331         // Safari can't handle uppercase or unicode characters when
A 6332         // in quirks mode.
6333         if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
6334             return;
29da64 6335         }
2011be 6336     
A 6337         Sizzle = function(query, context, extra, seed){
6338             context = context || document;
6339
6340             // Only use querySelectorAll on non-XML documents
6341             // (ID selectors don't work in non-HTML documents)
6342             if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
6343                 try {
6344                     return makeArray( context.querySelectorAll(query), extra );
6345                 } catch(e){}
6346             }
29da64 6347         
2011be 6348             return oldSizzle(query, context, extra, seed);
A 6349         };
29da64 6350
2011be 6351         for ( var prop in oldSizzle ) {
A 6352             Sizzle[ prop ] = oldSizzle[ prop ];
6353         }
29da64 6354
2011be 6355         div = null; // release memory in IE
A 6356     })();
6357 }
6358
6359 (function(){
29da64 6360     var div = document.createElement("div");
2011be 6361
29da64 6362     div.innerHTML = "<div class='test e'></div><div class='test'></div>";
A 6363
6364     // Opera can't find a second classname (in 9.6)
2011be 6365     // Also, make sure that getElementsByClassName actually exists
A 6366     if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
29da64 6367         return;
2011be 6368     }
29da64 6369
A 6370     // Safari caches class attributes, doesn't catch changes (in 3.2)
6371     div.lastChild.className = "e";
6372
2011be 6373     if ( div.getElementsByClassName("e").length === 1 ) {
29da64 6374         return;
2011be 6375     }
A 6376     
29da64 6377     Expr.order.splice(1, 0, "CLASS");
A 6378     Expr.find.CLASS = function(match, context, isXML) {
6379         if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
6380             return context.getElementsByClassName(match[1]);
6381         }
6382     };
2011be 6383
A 6384     div = null; // release memory in IE
29da64 6385 })();
A 6386
6387 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6388     for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6389         var elem = checkSet[i];
6390         if ( elem ) {
6391             elem = elem[dir];
6392             var match = false;
6393
6394             while ( elem ) {
6395                 if ( elem.sizcache === doneName ) {
6396                     match = checkSet[elem.sizset];
6397                     break;
6398                 }
6399
6400                 if ( elem.nodeType === 1 && !isXML ){
6401                     elem.sizcache = doneName;
6402                     elem.sizset = i;
6403                 }
6404
2011be 6405                 if ( elem.nodeName.toLowerCase() === cur ) {
29da64 6406                     match = elem;
A 6407                     break;
6408                 }
6409
6410                 elem = elem[dir];
6411             }
6412
6413             checkSet[i] = match;
6414         }
6415     }
6416 }
6417
6418 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6419     for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6420         var elem = checkSet[i];
6421         if ( elem ) {
6422             elem = elem[dir];
6423             var match = false;
6424
6425             while ( elem ) {
6426                 if ( elem.sizcache === doneName ) {
6427                     match = checkSet[elem.sizset];
6428                     break;
6429                 }
6430
6431                 if ( elem.nodeType === 1 ) {
6432                     if ( !isXML ) {
6433                         elem.sizcache = doneName;
6434                         elem.sizset = i;
6435                     }
6436                     if ( typeof cur !== "string" ) {
6437                         if ( elem === cur ) {
6438                             match = true;
6439                             break;
6440                         }
6441
6442                     } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
6443                         match = elem;
6444                         break;
6445                     }
6446                 }
6447
6448                 elem = elem[dir];
6449             }
6450
6451             checkSet[i] = match;
6452         }
6453     }
6454 }
6455
2011be 6456 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
A 6457     return !!(a.compareDocumentPosition(b) & 16);
29da64 6458 } : function(a, b){
A 6459     return a !== b && (a.contains ? a.contains(b) : true);
6460 };
6461
2011be 6462 Sizzle.isXML = function(elem){
A 6463     // documentElement is verified for cases where it doesn't yet exist
6464     // (such as loading iframes in IE - #4833) 
6465     var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
6466     return documentElement ? documentElement.nodeName !== "HTML" : false;
29da64 6467 };
A 6468
6469 var posProcess = function(selector, context){
6470     var tmpSet = [], later = "", match,
6471         root = context.nodeType ? [context] : context;
6472
6473     // Position selectors must be done after the filter
6474     // And so must :not(positional) so we move all PSEUDOs to the end
6475     while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
6476         later += match[0];
6477         selector = selector.replace( Expr.match.PSEUDO, "" );
6478     }
6479
6480     selector = Expr.relative[selector] ? selector + "*" : selector;
6481
6482     for ( var i = 0, l = root.length; i < l; i++ ) {
6483         Sizzle( selector, root[i], tmpSet );
6484     }
6485
6486     return Sizzle.filter( later, tmpSet );
6487 };
6488
6489 // EXPOSE
6490
6491 window.tinymce.dom.Sizzle = Sizzle;
6492
6493 })();
6494
69d05c 6495
29da64 6496 (function(tinymce) {
d9344f 6497     // Shorten names
S 6498     var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
6499
58fb65 6500     tinymce.create('tinymce.dom.EventUtils', {
A 6501         EventUtils : function() {
6502             this.inits = [];
6503             this.events = [];
6504         },
d9344f 6505
S 6506         add : function(o, n, f, s) {
6507             var cb, t = this, el = t.events, r;
58fb65 6508
A 6509             if (n instanceof Array) {
6510                 r = [];
6511
6512                 each(n, function(n) {
6513                     r.push(t.add(o, n, f, s));
6514                 });
6515
6516                 return r;
6517             }
d9344f 6518
S 6519             // Handle array
29da64 6520             if (o && o.hasOwnProperty && o instanceof Array) {
d9344f 6521                 r = [];
S 6522
6523                 each(o, function(o) {
6524                     o = DOM.get(o);
6525                     r.push(t.add(o, n, f, s));
6526                 });
6527
6528                 return r;
6529             }
6530
6531             o = DOM.get(o);
6532
6533             if (!o)
6534                 return;
6535
6536             // Setup event callback
6537             cb = function(e) {
58fb65 6538                 // Is all events disabled
A 6539                 if (t.disabled)
6540                     return;
6541
d9344f 6542                 e = e || window.event;
S 6543
58fb65 6544                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
A 6545                 if (e && isIE) {
6546                     if (!e.target)
6547                         e.target = e.srcElement;
6548
6549                     // Patch in preventDefault, stopPropagation methods for W3C compatibility
6550                     tinymce.extend(e, t._stoppers);
6551                 }
d9344f 6552
S 6553                 if (!s)
6554                     return f(e);
6555
6556                 return f.call(s, e);
6557             };
6558
6559             if (n == 'unload') {
6560                 tinymce.unloads.unshift({func : cb});
6561                 return cb;
6562             }
6563
6564             if (n == 'init') {
6565                 if (t.domLoaded)
6566                     cb();
6567                 else
6568                     t.inits.push(cb);
6569
6570                 return cb;
6571             }
6572
6573             // Store away listener reference
6574             el.push({
6575                 obj : o,
6576                 name : n,
6577                 func : f,
6578                 cfunc : cb,
6579                 scope : s
6580             });
6581
6582             t._add(o, n, cb);
6583
6584             return f;
6585         },
6586
6587         remove : function(o, n, f) {
6588             var t = this, a = t.events, s = false, r;
6589
6590             // Handle array
29da64 6591             if (o && o.hasOwnProperty && o instanceof Array) {
d9344f 6592                 r = [];
S 6593
6594                 each(o, function(o) {
6595                     o = DOM.get(o);
6596                     r.push(t.remove(o, n, f));
6597                 });
6598
6599                 return r;
6600             }
6601
6602             o = DOM.get(o);
6603
6604             each(a, function(e, i) {
6605                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
6606                     a.splice(i, 1);
6607                     t._remove(o, n, e.cfunc);
6608                     s = true;
6609                     return false;
6610                 }
6611             });
6612
6613             return s;
6614         },
6615
6616         clear : function(o) {
6617             var t = this, a = t.events, i, e;
6618
6619             if (o) {
6620                 o = DOM.get(o);
6621
6622                 for (i = a.length - 1; i >= 0; i--) {
6623                     e = a[i];
6624
6625                     if (e.obj === o) {
6626                         t._remove(e.obj, e.name, e.cfunc);
6627                         e.obj = e.cfunc = null;
6628                         a.splice(i, 1);
6629                     }
6630                 }
6631             }
6632         },
6633
6634         cancel : function(e) {
6635             if (!e)
6636                 return false;
6637
6638             this.stop(e);
58fb65 6639
d9344f 6640             return this.prevent(e);
S 6641         },
6642
6643         stop : function(e) {
6644             if (e.stopPropagation)
6645                 e.stopPropagation();
6646             else
6647                 e.cancelBubble = true;
6648
6649             return false;
6650         },
6651
6652         prevent : function(e) {
6653             if (e.preventDefault)
6654                 e.preventDefault();
6655             else
6656                 e.returnValue = false;
6657
6658             return false;
6659         },
6660
58fb65 6661         destroy : function() {
A 6662             var t = this;
d9344f 6663
S 6664             each(t.events, function(e, i) {
6665                 t._remove(e.obj, e.name, e.cfunc);
6666                 e.obj = e.cfunc = null;
6667             });
6668
6669             t.events = [];
6670             t = null;
6671         },
6672
6673         _add : function(o, n, f) {
6674             if (o.attachEvent)
6675                 o.attachEvent('on' + n, f);
6676             else if (o.addEventListener)
6677                 o.addEventListener(n, f, false);
6678             else
6679                 o['on' + n] = f;
6680         },
6681
6682         _remove : function(o, n, f) {
6683             if (o) {
6684                 try {
6685                     if (o.detachEvent)
6686                         o.detachEvent('on' + n, f);
6687                     else if (o.removeEventListener)
6688                         o.removeEventListener(n, f, false);
6689                     else
6690                         o['on' + n] = null;
6691                 } catch (ex) {
6692                     // Might fail with permission denined on IE so we just ignore that
6693                 }
6694             }
6695         },
6696
58fb65 6697         _pageInit : function(win) {
A 6698             var t = this;
d9344f 6699
29da64 6700             // Keep it from running more than once
58fb65 6701             if (t.domLoaded)
29da64 6702                 return;
A 6703
58fb65 6704             t.domLoaded = true;
d9344f 6705
58fb65 6706             each(t.inits, function(c) {
d9344f 6707                 c();
S 6708             });
6709
58fb65 6710             t.inits = [];
d9344f 6711         },
S 6712
58fb65 6713         _wait : function(win) {
A 6714             var t = this, doc = win.document;
6715
d9344f 6716             // No need since the document is already loaded
58fb65 6717             if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
A 6718                 t.domLoaded = 1;
d9344f 6719                 return;
18240a 6720             }
d9344f 6721
29da64 6722             // Use IE method
58fb65 6723             if (doc.attachEvent) {
A 6724                 doc.attachEvent("onreadystatechange", function() {
6725                     if (doc.readyState === "complete") {
6726                         doc.detachEvent("onreadystatechange", arguments.callee);
6727                         t._pageInit(win);
d9344f 6728                     }
29da64 6729                 });
A 6730
58fb65 6731                 if (doc.documentElement.doScroll && win == win.top) {
29da64 6732                     (function() {
58fb65 6733                         if (t.domLoaded)
29da64 6734                             return;
A 6735
6736                         try {
6737                             // If IE is used, use the trick by Diego Perini
6738                             // http://javascript.nwbox.com/IEContentLoaded/
58fb65 6739                             doc.documentElement.doScroll("left");
29da64 6740                         } catch (ex) {
A 6741                             setTimeout(arguments.callee, 0);
6742                             return;
6743                         }
6744
58fb65 6745                         t._pageInit(win);
29da64 6746                     })();
A 6747                 }
58fb65 6748             } else if (doc.addEventListener) {
A 6749                 t._add(win, 'DOMContentLoaded', function() {
6750                     t._pageInit(win);
6751                 });
6752             }
d9344f 6753
58fb65 6754             t._add(win, 'load', function() {
A 6755                 t._pageInit(win);
6756             });
6757         },
6758
6759         _stoppers : {
a9251b 6760             preventDefault : function() {
58fb65 6761                 this.returnValue = false;
A 6762             },
6763
6764             stopPropagation : function() {
6765                 this.cancelBubble = true;
6766             }
d9344f 6767         }
58fb65 6768     });
d9344f 6769
58fb65 6770     Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
d9344f 6771
S 6772     // Dispatch DOM content loaded event for IE and Safari
58fb65 6773     Event._wait(window);
A 6774
6775     tinymce.addUnload(function() {
6776         Event.destroy();
6777     });
29da64 6778 })(tinymce);
69d05c 6779
29da64 6780 (function(tinymce) {
69d05c 6781     tinymce.dom.Element = function(id, settings) {
A 6782         var t = this, dom, el;
d9344f 6783
69d05c 6784         t.settings = settings = settings || {};
A 6785         t.id = id;
6786         t.dom = dom = settings.dom || tinymce.DOM;
d9344f 6787
69d05c 6788         // Only IE leaks DOM references, this is a lot faster
A 6789         if (!tinymce.isIE)
6790             el = dom.get(t.id);
d9344f 6791
69d05c 6792         tinymce.each(
A 6793                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
6794                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
6795                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
6796                 'isHidden,setHTML,get').split(/,/)
6797             , function(k) {
d9344f 6798                 t[k] = function() {
29da64 6799                     var a = [id], i;
d9344f 6800
29da64 6801                     for (i = 0; i < arguments.length; i++)
A 6802                         a.push(arguments[i]);
d9344f 6803
29da64 6804                     a = dom[k].apply(dom, a);
d9344f 6805                     t.update(k);
S 6806
29da64 6807                     return a;
d9344f 6808                 };
69d05c 6809         });
d9344f 6810
69d05c 6811         tinymce.extend(t, {
A 6812             on : function(n, f, s) {
6813                 return tinymce.dom.Event.add(t.id, n, f, s);
6814             },
d9344f 6815
69d05c 6816             getXY : function() {
A 6817                 return {
6818                     x : parseInt(t.getStyle('left')),
6819                     y : parseInt(t.getStyle('top'))
6820                 };
6821             },
d9344f 6822
69d05c 6823             getSize : function() {
A 6824                 var n = dom.get(t.id);
d9344f 6825
69d05c 6826                 return {
A 6827                     w : parseInt(t.getStyle('width') || n.clientWidth),
6828                     h : parseInt(t.getStyle('height') || n.clientHeight)
6829                 };
6830             },
d9344f 6831
69d05c 6832             moveTo : function(x, y) {
A 6833                 t.setStyles({left : x, top : y});
6834             },
d9344f 6835
69d05c 6836             moveBy : function(x, y) {
A 6837                 var p = t.getXY();
d9344f 6838
69d05c 6839                 t.moveTo(p.x + x, p.y + y);
A 6840             },
d9344f 6841
69d05c 6842             resizeTo : function(w, h) {
A 6843                 t.setStyles({width : w, height : h});
6844             },
d9344f 6845
69d05c 6846             resizeBy : function(w, h) {
A 6847                 var s = t.getSize();
d9344f 6848
69d05c 6849                 t.resizeTo(s.w + w, s.h + h);
A 6850             },
d9344f 6851
69d05c 6852             update : function(k) {
A 6853                 var b;
d9344f 6854
69d05c 6855                 if (tinymce.isIE6 && settings.blocker) {
A 6856                     k = k || '';
d9344f 6857
69d05c 6858                     // Ignore getters
A 6859                     if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
6860                         return;
d9344f 6861
69d05c 6862                     // Remove blocker on remove
A 6863                     if (k == 'remove') {
6864                         dom.remove(t.blocker);
6865                         return;
6866                     }
6867
6868                     if (!t.blocker) {
6869                         t.blocker = dom.uniqueId();
6870                         b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
6871                         dom.setStyle(b, 'opacity', 0);
6872                     } else
6873                         b = dom.get(t.blocker);
6874
6875                     dom.setStyles(b, {
6876                         left : t.getStyle('left', 1),
6877                         top : t.getStyle('top', 1),
6878                         width : t.getStyle('width', 1),
6879                         height : t.getStyle('height', 1),
6880                         display : t.getStyle('display', 1),
6881                         zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
6882                     });
d9344f 6883                 }
S 6884             }
69d05c 6885         });
A 6886     };
29da64 6887 })(tinymce);
69d05c 6888
29da64 6889 (function(tinymce) {
18240a 6890     function trimNl(s) {
A 6891         return s.replace(/[\n\r]+/g, '');
6892     };
6893
d9344f 6894     // Shorten names
S 6895     var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
6896
6897     tinymce.create('tinymce.dom.Selection', {
6898         Selection : function(dom, win, serializer) {
6899             var t = this;
6900
6901             t.dom = dom;
6902             t.win = win;
6903             t.serializer = serializer;
6904
29da64 6905             // Add events
A 6906             each([
6907                 'onBeforeSetContent',
a9251b 6908
29da64 6909                 'onBeforeGetContent',
a9251b 6910
29da64 6911                 'onSetContent',
a9251b 6912
29da64 6913                 'onGetContent'
A 6914             ], function(e) {
6915                 t[e] = new tinymce.util.Dispatcher(t);
6916             });
6917
6918             // No W3C Range support
6919             if (!t.win.getSelection)
6920                 t.tridentSel = new tinymce.dom.TridentSelection(t);
a9251b 6921
T 6922             if (tinymce.isIE && dom.boxModel)
6923                 this._fixIESelection();
29da64 6924
d9344f 6925             // Prevent leaks
S 6926             tinymce.addUnload(t.destroy, t);
6927         },
6928
6929         getContent : function(s) {
6930             var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
6931
6932             s = s || {};
6933             wb = wa = '';
6934             s.get = true;
6935             s.format = s.format || 'html';
29da64 6936             t.onBeforeGetContent.dispatch(t, s);
d9344f 6937
S 6938             if (s.format == 'text')
6939                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
6940
6941             if (r.cloneContents) {
6942                 n = r.cloneContents();
6943
6944                 if (n)
6945                     e.appendChild(n);
6946             } else if (is(r.item) || is(r.htmlText))
6947                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
6948             else
6949                 e.innerHTML = r.toString();
6950
6951             // Keep whitespace before and after
6952             if (/^\s/.test(e.innerHTML))
6953                 wb = ' ';
6954
6955             if (/\s+$/.test(e.innerHTML))
6956                 wa = ' ';
6957
6958             s.getInner = true;
6959
29da64 6960             s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
A 6961             t.onGetContent.dispatch(t, s);
6962
6963             return s.content;
d9344f 6964         },
S 6965
a9251b 6966         setContent : function(content, args) {
T 6967             var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
d9344f 6968
a9251b 6969             args = args || {format : 'html'};
T 6970             args.set = true;
6971             content = args.content = content;
29da64 6972
A 6973             // Dispatch before set content event
a9251b 6974             if (!args.no_events)
T 6975                 self.onBeforeSetContent.dispatch(self, args);
d9344f 6976
a9251b 6977             content = args.content;
T 6978
6979             if (rng.insertNode) {
29da64 6980                 // Make caret marker since insertNode places the caret in the beginning of text after insert
a9251b 6981                 content += '<span id="__caret">_</span>';
d9344f 6982
29da64 6983                 // Delete and insert new node
a9251b 6984                 if (rng.startContainer == doc && rng.endContainer == doc) {
69d05c 6985                     // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
a9251b 6986                     doc.body.innerHTML = content;
69d05c 6987                 } else {
a9251b 6988                     rng.deleteContents();
T 6989
6990                     if (doc.body.childNodes.length == 0) {
6991                         doc.body.innerHTML = content;
2011be 6992                     } else {
a9251b 6993                         // createContextualFragment doesn't exists in IE 9 DOMRanges
T 6994                         if (rng.createContextualFragment) {
6995                             rng.insertNode(rng.createContextualFragment(content));
6996                         } else {
6997                             // Fake createContextualFragment call in IE 9
6998                             frag = doc.createDocumentFragment();
6999                             temp = doc.createElement('div');
7000
7001                             frag.appendChild(temp);
7002                             temp.outerHTML = content;
7003
7004                             rng.insertNode(frag);
7005                         }
2011be 7006                     }
69d05c 7007                 }
29da64 7008
A 7009                 // Move to caret marker
a9251b 7010                 caretNode = self.dom.get('__caret');
T 7011
29da64 7012                 // Make sure we wrap it compleatly, Opera fails with a simple select call
a9251b 7013                 rng = doc.createRange();
T 7014                 rng.setStartBefore(caretNode);
7015                 rng.setEndBefore(caretNode);
7016                 self.setRng(rng);
29da64 7017
A 7018                 // Remove the caret position
a9251b 7019                 self.dom.remove('__caret');
T 7020                 self.setRng(rng);
d9344f 7021             } else {
a9251b 7022                 if (rng.item) {
18240a 7023                     // Delete content and get caret text selection
a9251b 7024                     doc.execCommand('Delete', false, null);
T 7025                     rng = self.getRng();
18240a 7026                 }
A 7027
a9251b 7028                 rng.pasteHTML(content);
d9344f 7029             }
29da64 7030
A 7031             // Dispatch set content event
a9251b 7032             if (!args.no_events)
T 7033                 self.onSetContent.dispatch(self, args);
d9344f 7034         },
S 7035
7036         getStart : function() {
2011be 7037             var rng = this.getRng(), startElement, parentElement, checkRng, node;
d9344f 7038
2011be 7039             if (rng.duplicate || rng.item) {
A 7040                 // Control selection, return first item
7041                 if (rng.item)
7042                     return rng.item(0);
d9344f 7043
2011be 7044                 // Get start element
A 7045                 checkRng = rng.duplicate();
7046                 checkRng.collapse(1);
7047                 startElement = checkRng.parentElement();
d9344f 7048
2011be 7049                 // Check if range parent is inside the start element, then return the inner parent element
A 7050                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7051                 parentElement = node = rng.parentElement();
7052                 while (node = node.parentNode) {
7053                     if (node == startElement) {
7054                         startElement = parentElement;
7055                         break;
7056                     }
7057                 }
7058
7059                 return startElement;
d9344f 7060             } else {
2011be 7061                 startElement = rng.startContainer;
d9344f 7062
2011be 7063                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
A 7064                     startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
d9344f 7065
2011be 7066                 if (startElement && startElement.nodeType == 3)
A 7067                     return startElement.parentNode;
69d05c 7068
2011be 7069                 return startElement;
d9344f 7070             }
S 7071         },
7072
7073         getEnd : function() {
69d05c 7074             var t = this, r = t.getRng(), e, eo;
d9344f 7075
799907 7076             if (r.duplicate || r.item) {
d9344f 7077                 if (r.item)
S 7078                     return r.item(0);
7079
7080                 r = r.duplicate();
7081                 r.collapse(0);
7082                 e = r.parentElement();
7083
18240a 7084                 if (e && e.nodeName == 'BODY')
69d05c 7085                     return e.lastChild || e;
d9344f 7086
S 7087                 return e;
7088             } else {
7089                 e = r.endContainer;
69d05c 7090                 eo = r.endOffset;
d9344f 7091
69d05c 7092                 if (e.nodeType == 1 && e.hasChildNodes())
A 7093                     e = e.childNodes[eo > 0 ? eo - 1 : eo];
d9344f 7094
69d05c 7095                 if (e && e.nodeType == 3)
A 7096                     return e.parentNode;
7097
7098                 return e;
d9344f 7099             }
S 7100         },
7101
69d05c 7102         getBookmark : function(type, normalized) {
A 7103             var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
d9344f 7104
69d05c 7105             function findIndex(name, element) {
A 7106                 var index = 0;
d9344f 7107
69d05c 7108                 each(dom.select(name), function(node, i) {
A 7109                     if (node == element)
7110                         index = i;
7111                 });
d9344f 7112
69d05c 7113                 return index;
A 7114             };
7115
7116             if (type == 2) {
7117                 function getLocation() {
7118                     var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
7119
7120                     function getPoint(rng, start) {
7121                         var container = rng[start ? 'startContainer' : 'endContainer'],
799907 7122                             offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
69d05c 7123
A 7124                         if (container.nodeType == 3) {
7125                             if (normalized) {
7126                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
7127                                     offset += node.nodeValue.length;
7128                             }
7129
7130                             point.push(offset);
7131                         } else {
7132                             childNodes = container.childNodes;
2011be 7133
A 7134                             if (offset >= childNodes.length && childNodes.length) {
69d05c 7135                                 after = 1;
2011be 7136                                 offset = Math.max(0, childNodes.length - 1);
69d05c 7137                             }
A 7138
7139                             point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
d9344f 7140                         }
S 7141
69d05c 7142                         for (; container && container != root; container = container.parentNode)
A 7143                             point.push(t.dom.nodeIndex(container, normalized));
7144
7145                         return point;
d9344f 7146                     };
S 7147
69d05c 7148                     bookmark.start = getPoint(rng, true);
A 7149
7150                     if (!t.isCollapsed())
7151                         bookmark.end = getPoint(rng);
7152
7153                     return bookmark;
7154                 };
7155
7156                 return getLocation();
7157             }
7158
7159             // Handle simple range
7160             if (type)
7161                 return {rng : t.getRng()};
7162
7163             rng = t.getRng();
7164             id = dom.uniqueId();
7165             collapsed = tinyMCE.activeEditor.selection.isCollapsed();
7166             styles = 'overflow:hidden;line-height:0px';
7167
7168             // Explorer method
7169             if (rng.duplicate || rng.item) {
d9344f 7170                 // Text selection
69d05c 7171                 if (!rng.item) {
A 7172                     rng2 = rng.duplicate();
d9344f 7173
a9251b 7174                     try {
T 7175                         // Insert start marker
7176                         rng.collapse();
7177                         rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
d9344f 7178
a9251b 7179                         // Insert end marker
T 7180                         if (!collapsed) {
7181                             rng2.collapse(false);
7182
7183                             // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
7184                             rng.moveToElementText(rng2.parentElement());
7185                             if (rng.compareEndPoints('StartToEnd', rng2) == 0)
7186                                 rng2.move('character', -1);
7187
7188                             rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
7189                         }
7190                     } catch (ex) {
7191                         // IE might throw unspecified error so lets ignore it
7192                         return null;
d9344f 7193                     }
69d05c 7194                 } else {
A 7195                     // Control selection
7196                     element = rng.item(0);
7197                     name = element.nodeName;
d9344f 7198
69d05c 7199                     return {name : name, index : findIndex(name, element)};
A 7200                 }
7201             } else {
7202                 element = t.getNode();
7203                 name = element.nodeName;
7204                 if (name == 'IMG')
7205                     return {name : name, index : findIndex(name, element)};
7206
7207                 // W3C method
7208                 rng2 = rng.cloneRange();
7209
7210                 // Insert end marker
7211                 if (!collapsed) {
7212                     rng2.collapse(false);
a9251b 7213                     rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
d9344f 7214                 }
S 7215
69d05c 7216                 rng.collapse(true);
a9251b 7217                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
d9344f 7218             }
69d05c 7219
A 7220             t.moveToBookmark({id : id, keep : 1});
7221
7222             return {id : id};
d9344f 7223         },
S 7224
69d05c 7225         moveToBookmark : function(bookmark) {
2011be 7226             var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
d9344f 7227
69d05c 7228             // Clear selection cache
A 7229             if (t.tridentSel)
58fb65 7230                 t.tridentSel.destroy();
A 7231
69d05c 7232             if (bookmark) {
A 7233                 if (bookmark.start) {
7234                     rng = dom.createRng();
7235                     root = dom.getRoot();
d9344f 7236
69d05c 7237                     function setEndPoint(start) {
2011be 7238                         var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
d9344f 7239
69d05c 7240                         if (point) {
a9251b 7241                             offset = point[0];
T 7242
69d05c 7243                             // Find container node
2011be 7244                             for (node = root, i = point.length - 1; i >= 1; i--) {
A 7245                                 children = node.childNodes;
7246
a9251b 7247                                 if (point[i] > children.length - 1)
T 7248                                     return;
7249
7250                                 node = children[point[i]];
2011be 7251                             }
a9251b 7252
T 7253                             // Move text offset to best suitable location
7254                             if (node.nodeType === 3)
7255                                 offset = Math.min(point[0], node.nodeValue.length);
7256
7257                             // Move element offset to best suitable location
7258                             if (node.nodeType === 1)
7259                                 offset = Math.min(point[0], node.childNodes.length);
d9344f 7260
69d05c 7261                             // Set offset within container node
A 7262                             if (start)
a9251b 7263                                 rng.setStart(node, offset);
69d05c 7264                             else
a9251b 7265                                 rng.setEnd(node, offset);
d9344f 7266                         }
a9251b 7267
T 7268                         return true;
69d05c 7269                     };
d9344f 7270
a9251b 7271                     if (setEndPoint(true) && setEndPoint()) {
T 7272                         t.setRng(rng);
7273                     }
69d05c 7274                 } else if (bookmark.id) {
A 7275                     function restoreEndPoint(suffix) {
7276                         var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
7277
7278                         if (marker) {
7279                             node = marker.parentNode;
7280
7281                             if (suffix == 'start') {
7282                                 if (!keep) {
7283                                     idx = dom.nodeIndex(marker);
7284                                 } else {
2011be 7285                                     node = marker.firstChild;
69d05c 7286                                     idx = 1;
A 7287                                 }
7288
2011be 7289                                 startContainer = endContainer = node;
A 7290                                 startOffset = endOffset = idx;
69d05c 7291                             } else {
A 7292                                 if (!keep) {
7293                                     idx = dom.nodeIndex(marker);
7294                                 } else {
2011be 7295                                     node = marker.firstChild;
69d05c 7296                                     idx = 1;
A 7297                                 }
7298
2011be 7299                                 endContainer = node;
A 7300                                 endOffset = idx;
69d05c 7301                             }
A 7302
7303                             if (!keep) {
7304                                 prev = marker.previousSibling;
7305                                 next = marker.nextSibling;
7306
7307                                 // Remove all marker text nodes
7308                                 each(tinymce.grep(marker.childNodes), function(node) {
7309                                     if (node.nodeType == 3)
7310                                         node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
7311                                 });
7312
7313                                 // Remove marker but keep children if for example contents where inserted into the marker
7314                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
7315                                 while (marker = dom.get(bookmark.id + '_' + suffix))
7316                                     dom.remove(marker, 1);
7317
a9251b 7318                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
T 7319                                 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
7320                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
69d05c 7321                                     idx = prev.nodeValue.length;
A 7322                                     prev.appendData(next.nodeValue);
7323                                     dom.remove(next);
7324
7325                                     if (suffix == 'start') {
2011be 7326                                         startContainer = endContainer = prev;
A 7327                                         startOffset = endOffset = idx;
7328                                     } else {
7329                                         endContainer = prev;
7330                                         endOffset = idx;
7331                                     }
69d05c 7332                                 }
A 7333                             }
7334                         }
2011be 7335                     };
A 7336
7337                     function addBogus(node) {
a9251b 7338                         // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
T 7339                         if (dom.isBlock(node) && !node.innerHTML)
7340                             node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
2011be 7341
A 7342                         return node;
69d05c 7343                     };
A 7344
7345                     // Restore start/end points
7346                     restoreEndPoint('start');
7347                     restoreEndPoint('end');
7348
a9251b 7349                     if (startContainer) {
T 7350                         rng = dom.createRng();
7351                         rng.setStart(addBogus(startContainer), startOffset);
7352                         rng.setEnd(addBogus(endContainer), endOffset);
7353                         t.setRng(rng);
7354                     }
69d05c 7355                 } else if (bookmark.name) {
A 7356                     t.select(dom.select(bookmark.name)[bookmark.index]);
7357                 } else if (bookmark.rng)
7358                     t.setRng(bookmark.rng);
d9344f 7359             }
S 7360         },
7361
69d05c 7362         select : function(node, content) {
A 7363             var t = this, dom = t.dom, rng = dom.createRng(), idx;
d9344f 7364
a9251b 7365             if (node) {
T 7366                 idx = dom.nodeIndex(node);
7367                 rng.setStart(node.parentNode, idx);
7368                 rng.setEnd(node.parentNode, idx + 1);
d9344f 7369
a9251b 7370                 // Find first/last text node or BR element
T 7371                 if (content) {
7372                     function setPoint(node, start) {
7373                         var walker = new tinymce.dom.TreeWalker(node, node);
d9344f 7374
a9251b 7375                         do {
T 7376                             // Text node
7377                             if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
7378                                 if (start)
7379                                     rng.setStart(node, 0);
7380                                 else
7381                                     rng.setEnd(node, node.nodeValue.length);
69d05c 7382
a9251b 7383                                 return;
T 7384                             }
d9344f 7385
a9251b 7386                             // BR element
T 7387                             if (node.nodeName == 'BR') {
7388                                 if (start)
7389                                     rng.setStartBefore(node);
7390                                 else
7391                                     rng.setEndBefore(node);
d9344f 7392
a9251b 7393                                 return;
T 7394                             }
7395                         } while (node = (start ? walker.next() : walker.prev()));
7396                     };
d9344f 7397
a9251b 7398                     setPoint(node, 1);
T 7399                     setPoint(node);
7400                 }
7401
7402                 t.setRng(rng);
d9344f 7403             }
69d05c 7404
A 7405             return node;
d9344f 7406         },
S 7407
7408         isCollapsed : function() {
7409             var t = this, r = t.getRng(), s = t.getSel();
7410
7411             if (!r || r.item)
7412                 return false;
7413
69d05c 7414             if (r.compareEndPoints)
A 7415                 return r.compareEndPoints('StartToEnd', r) === 0;
7416
7417             return !s || r.collapsed;
d9344f 7418         },
S 7419
a9251b 7420         collapse : function(to_start) {
T 7421             var self = this, rng = self.getRng(), node;
d9344f 7422
S 7423             // Control range on IE
a9251b 7424             if (rng.item) {
T 7425                 node = rng.item(0);
7426                 rng = self.win.document.body.createTextRange();
7427                 rng.moveToElementText(node);
d9344f 7428             }
S 7429
a9251b 7430             rng.collapse(!!to_start);
T 7431             self.setRng(rng);
d9344f 7432         },
S 7433
7434         getSel : function() {
7435             var t = this, w = this.win;
7436
7437             return w.getSelection ? w.getSelection() : w.document.selection;
7438         },
7439
29da64 7440         getRng : function(w3c) {
a9251b 7441             var t = this, s, r, elm, doc = t.win.document;
29da64 7442
A 7443             // Found tridentSel object then we need to use that one
7444             if (w3c && t.tridentSel)
7445                 return t.tridentSel.getRangeAt(0);
d9344f 7446
S 7447             try {
29da64 7448                 if (s = t.getSel())
a9251b 7449                     r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
d9344f 7450             } catch (ex) {
S 7451                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
a9251b 7452             }
T 7453
7454             // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
7455             if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
7456                 elm = doc.selection.createRange().item(0);
7457                 r = doc.createRange();
7458                 r.setStartBefore(elm);
7459                 r.setEndAfter(elm);
d9344f 7460             }
S 7461
7462             // No range found then create an empty one
7463             // This can occur when the editor is placed in a hidden container element on Gecko
7464             // Or on IE when there was an exception
7465             if (!r)
a9251b 7466                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
d9344f 7467
2011be 7468             if (t.selectedRange && t.explicitRange) {
A 7469                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
7470                     // Safari, Opera and Chrome only ever select text which causes the range to change.
7471                     // This lets us use the originally set range if the selection hasn't been changed by the user.
7472                     r = t.explicitRange;
7473                 } else {
7474                     t.selectedRange = null;
7475                     t.explicitRange = null;
7476                 }
7477             }
a9251b 7478
d9344f 7479             return r;
S 7480         },
7481
7482         setRng : function(r) {
29da64 7483             var s, t = this;
2011be 7484             
29da64 7485             if (!t.tridentSel) {
A 7486                 s = t.getSel();
d9344f 7487
S 7488                 if (s) {
2011be 7489                     t.explicitRange = r;
a9251b 7490
T 7491                     try {
7492                         s.removeAllRanges();
7493                     } catch (ex) {
7494                         // IE9 might throw errors here don't know why
7495                     }
7496
d9344f 7497                     s.addRange(r);
2011be 7498                     t.selectedRange = s.getRangeAt(0);
d9344f 7499                 }
S 7500             } else {
29da64 7501                 // Is W3C Range
A 7502                 if (r.cloneRange) {
7503                     t.tridentSel.addRange(r);
7504                     return;
7505                 }
7506
7507                 // Is IE specific range
d9344f 7508                 try {
S 7509                     r.select();
7510                 } catch (ex) {
7511                     // Needed for some odd IE bug #1843306
7512                 }
7513             }
7514         },
7515
7516         setNode : function(n) {
7517             var t = this;
7518
7519             t.setContent(t.dom.getOuterHTML(n));
7520
7521             return n;
7522         },
7523
7524         getNode : function() {
a9251b 7525             var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
T 7526
7527             // Range maybe lost after the editor is made visible again
7528             if (!rng)
7529                 return t.dom.getRoot();
d9344f 7530
799907 7531             if (rng.setStart) {
69d05c 7532                 elm = rng.commonAncestorContainer;
d9344f 7533
S 7534                 // Handle selection a image or other control like element such as anchors
69d05c 7535                 if (!rng.collapsed) {
A 7536                     if (rng.startContainer == rng.endContainer) {
a9251b 7537                         if (rng.endOffset - rng.startOffset < 2) {
69d05c 7538                             if (rng.startContainer.hasChildNodes())
A 7539                                 elm = rng.startContainer.childNodes[rng.startOffset];
d9344f 7540                         }
S 7541                     }
69d05c 7542
A 7543                     // If the anchor node is a element instead of a text node then return this element
a9251b 7544                     //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
T 7545                     //    return sel.anchorNode.childNodes[sel.anchorOffset];
7546
7547                     // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
7548                     // This happens when you double click an underlined word in FireFox.
7549                     if (start.nodeType === 3 && end.nodeType === 3) {
7550                         function skipEmptyTextNodes(n, forwards) {
7551                             var orig = n;
7552                             while (n && n.nodeType === 3 && n.length === 0) {
7553                                 n = forwards ? n.nextSibling : n.previousSibling;
7554                             }
7555                             return n || orig;
7556                         }
7557                         if (start.length === rng.startOffset) {
7558                             start = skipEmptyTextNodes(start.nextSibling, true);
7559                         } else {
7560                             start = start.parentNode;
7561                         }
7562                         if (rng.endOffset === 0) {
7563                             end = skipEmptyTextNodes(end.previousSibling, false);
7564                         } else {
7565                             end = end.parentNode;
7566                         }
7567
7568                         if (start && start === end)
7569                             return start;
7570                     }
d9344f 7571                 }
S 7572
69d05c 7573                 if (elm && elm.nodeType == 3)
A 7574                     return elm.parentNode;
7575
7576                 return elm;
d9344f 7577             }
S 7578
69d05c 7579             return rng.item ? rng.item(0) : rng.parentElement();
29da64 7580         },
A 7581
7582         getSelectedBlocks : function(st, en) {
7583             var t = this, dom = t.dom, sb, eb, n, bl = [];
7584
7585             sb = dom.getParent(st || t.getStart(), dom.isBlock);
7586             eb = dom.getParent(en || t.getEnd(), dom.isBlock);
7587
7588             if (sb)
7589                 bl.push(sb);
7590
7591             if (sb && eb && sb != eb) {
7592                 n = sb;
7593
7594                 while ((n = n.nextSibling) && n != eb) {
7595                     if (dom.isBlock(n))
7596                         bl.push(n);
7597                 }
7598             }
7599
7600             if (eb && sb != eb)
7601                 bl.push(eb);
7602
7603             return bl;
d9344f 7604         },
S 7605
7606         destroy : function(s) {
7607             var t = this;
7608
7609             t.win = null;
7610
29da64 7611             if (t.tridentSel)
A 7612                 t.tridentSel.destroy();
7613
d9344f 7614             // Manual destroy then remove unload handler
S 7615             if (!s)
7616                 tinymce.removeUnload(t.destroy);
a9251b 7617         },
69d05c 7618
a9251b 7619         // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
T 7620         _fixIESelection : function() {
7621             var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
d9344f 7622
a9251b 7623             // Make HTML element unselectable since we are going to handle selection by hand
T 7624             doc.documentElement.unselectable = true;
d9344f 7625
a9251b 7626             // Return range from point or null if it failed
T 7627             function rngFromPoint(x, y) {
7628                 var rng = body.createTextRange();
7629
7630                 try {
7631                     rng.moveToPoint(x, y);
7632                 } catch (ex) {
7633                     // IE sometimes throws and exception, so lets just ignore it
7634                     rng = null;
7635                 }
7636
7637                 return rng;
7638             };
7639
7640             // Fires while the selection is changing
7641             function selectionChange(e) {
7642                 var pointRng;
7643
7644                 // Check if the button is down or not
7645                 if (e.button) {
7646                     // Create range from mouse position
7647                     pointRng = rngFromPoint(e.x, e.y);
7648
7649                     if (pointRng) {
7650                         // Check if pointRange is before/after selection then change the endPoint
7651                         if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
7652                             pointRng.setEndPoint('StartToStart', startRng);
7653                         else
7654                             pointRng.setEndPoint('EndToEnd', startRng);
7655
7656                         pointRng.select();
7657                     }
d9344f 7658                 } else
a9251b 7659                     endSelection();
T 7660             }
7661
7662             // Removes listeners
7663             function endSelection() {
7664                 var rng = doc.selection.createRange();
7665
7666                 // If the range is collapsed then use the last start range
7667                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
7668                     startRng.select();
7669
7670                 dom.unbind(doc, 'mouseup', endSelection);
7671                 dom.unbind(doc, 'mousemove', selectionChange);
7672                 startRng = started = 0;
d9344f 7673             };
S 7674
a9251b 7675             // Detect when user selects outside BODY
T 7676             dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
7677                 if (e.target.nodeName === 'HTML') {
7678                     if (started)
7679                         endSelection();
d9344f 7680
a9251b 7681                     // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
T 7682                     htmlElm = doc.documentElement;
7683                     if (htmlElm.scrollHeight > htmlElm.clientHeight)
7684                         return;
d9344f 7685
a9251b 7686                     started = 1;
T 7687                     // Setup start position
7688                     startRng = rngFromPoint(e.x, e.y);
7689                     if (startRng) {
7690                         // Listen for selection change events
7691                         dom.bind(doc, 'mouseup', endSelection);
7692                         dom.bind(doc, 'mousemove', selectionChange);
d9344f 7693
a9251b 7694                         dom.win.focus();
T 7695                         startRng.select();
7696                     }
7697                 }
7698             });
d9344f 7699         }
58fb65 7700     });
29da64 7701 })(tinymce);
69d05c 7702
29da64 7703 (function(tinymce) {
a9251b 7704     tinymce.dom.Serializer = function(settings, dom, schema) {
T 7705         var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
d9344f 7706
a9251b 7707         // Support the old apply_source_formatting option
T 7708         if (!settings.apply_source_formatting)
7709             settings.indent = false;
d9344f 7710
a9251b 7711         settings.remove_trailing_brs = true;
d9344f 7712
a9251b 7713         // Default DOM and Schema if they are undefined
T 7714         dom = dom || tinymce.DOM;
7715         schema = schema || new tinymce.html.Schema(settings);
7716         settings.entity_encoding = settings.entity_encoding || 'named';
d9344f 7717
a9251b 7718         onPreProcess = new tinymce.util.Dispatcher(self);
d9344f 7719
a9251b 7720         onPostProcess = new tinymce.util.Dispatcher(self);
d9344f 7721
a9251b 7722         htmlParser = new tinymce.html.DomParser(settings, schema);
d9344f 7723
a9251b 7724         // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
T 7725         htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
7726             var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
d9344f 7727
a9251b 7728             while (i--) {
T 7729                 node = nodes[i];
d9344f 7730
a9251b 7731                 value = node.attributes.map[internalName];
T 7732                 if (value !== undef) {
7733                     // Set external name to internal value and remove internal
7734                     node.attr(name, value.length > 0 ? value : null);
7735                     node.attr(internalName, null);
7736                 } else {
7737                     // No internal attribute found then convert the value we have in the DOM
7738                     value = node.attributes.map[name];
d9344f 7739
a9251b 7740                     if (name === "style")
T 7741                         value = dom.serializeStyle(dom.parseStyle(value), node.name);
7742                     else if (urlConverter)
7743                         value = urlConverter.call(urlConverterScope, value, name, node.name);
7744
7745                     node.attr(name, value.length > 0 ? value : null);
7746                 }
d9344f 7747             }
a9251b 7748         });
d9344f 7749
a9251b 7750         // Remove internal classes mceItem<..>
T 7751         htmlParser.addAttributeFilter('class', function(nodes, name) {
7752             var i = nodes.length, node, value;
d9344f 7753
a9251b 7754             while (i--) {
T 7755                 node = nodes[i];
7756                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
7757                 node.attr('class', value.length > 0 ? value : null);
d9344f 7758             }
a9251b 7759         });
d9344f 7760
a9251b 7761         // Remove bookmark elements
T 7762         htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
7763             var i = nodes.length, node;
d9344f 7764
a9251b 7765             while (i--) {
T 7766                 node = nodes[i];
d9344f 7767
a9251b 7768                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
T 7769                     node.remove();
7770             }
7771         });
d9344f 7772
a9251b 7773         // Force script into CDATA sections and remove the mce- prefix also add comments around styles
T 7774         htmlParser.addNodeFilter('script,style', function(nodes, name) {
7775             var i = nodes.length, node, value;
d9344f 7776
a9251b 7777             function trim(value) {
T 7778                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
7779                         .replace(/^[\r\n]*|[\r\n]*$/g, '')
7780                         .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
7781                         .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
7782             };
d9344f 7783
a9251b 7784             while (i--) {
T 7785                 node = nodes[i];
7786                 value = node.firstChild ? node.firstChild.value : '';
d9344f 7787
a9251b 7788                 if (name === "script") {
T 7789                     // Remove mce- prefix from script elements
7790                     node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
d9344f 7791
a9251b 7792                     if (value.length > 0)
T 7793                         node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
7794                 } else {
7795                     if (value.length > 0)
7796                         node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
7797                 }
7798             }
7799         });
7800
7801         // Convert comments to cdata and handle protected comments
7802         htmlParser.addNodeFilter('#comment', function(nodes, name) {
7803             var i = nodes.length, node;
7804
7805             while (i--) {
7806                 node = nodes[i];
7807
7808                 if (node.value.indexOf('[CDATA[') === 0) {
7809                     node.name = '#cdata';
7810                     node.type = 4;
7811                     node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
7812                 } else if (node.value.indexOf('mce:protected ') === 0) {
7813                     node.name = "#text";
7814                     node.type = 3;
7815                     node.raw = true;
7816                     node.value = unescape(node.value).substr(14);
7817                 }
7818             }
7819         });
7820
7821         htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
7822             var i = nodes.length, node;
7823
7824             while (i--) {
7825                 node = nodes[i];
7826                 if (node.type === 7)
7827                     node.remove();
7828                 else if (node.type === 1) {
7829                     if (name === "input" && !("type" in node.attributes.map))
7830                         node.attr('type', 'text');
7831                 }
7832             }
7833         });
7834
7835         // Fix list elements, TODO: Replace this later
7836         if (settings.fix_list_elements) {
7837             htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
7838                 var i = nodes.length, node, parentNode;
7839
7840                 while (i--) {
7841                     node = nodes[i];
7842                     parentNode = node.parent;
7843
7844                     if (parentNode.name === 'ul' || parentNode.name === 'ol') {
7845                         if (node.prev && node.prev.name === 'li') {
7846                             node.prev.append(node);
7847                         }
7848                     }
7849                 }
7850             });
7851         }
7852
7853         // Remove internal data attributes
7854         htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
7855             var i = nodes.length;
7856
7857             while (i--) {
7858                 nodes[i].attr(name, null);
7859             }
7860         });
7861
7862         // Return public methods
7863         return {
7864             schema : schema,
7865
7866             addNodeFilter : htmlParser.addNodeFilter,
7867
7868             addAttributeFilter : htmlParser.addAttributeFilter,
7869
7870             onPreProcess : onPreProcess,
7871
7872             onPostProcess : onPostProcess,
7873
7874             serialize : function(node, args) {
7875                 var impl, doc, oldDoc, htmlSerializer, content;
7876
7877                 // Explorer won't clone contents of script and style and the
7878                 // selected index of select elements are cleared on a clone operation.
7879                 if (isIE && dom.select('script,style,select').length > 0) {
7880                     content = node.innerHTML;
7881                     node = node.cloneNode(false);
7882                     dom.setHTML(node, content);
7883                 } else
7884                     node = node.cloneNode(true);
7885
7886                 // Nodes needs to be attached to something in WebKit/Opera
7887                 // Older builds of Opera crashes if you attach the node to an document created dynamically
7888                 // and since we can't feature detect a crash we need to sniff the acutal build number
7889                 // This fix will make DOM ranges and make Sizzle happy!
7890                 impl = node.ownerDocument.implementation;
7891                 if (impl.createHTMLDocument) {
7892                     // Create an empty HTML document
7893                     doc = impl.createHTMLDocument("");
7894
7895                     // Add the element or it's children if it's a body element to the new document
7896                     each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
7897                         doc.body.appendChild(doc.importNode(node, true));
7898                     });
7899
7900                     // Grab first child or body element for serialization
7901                     if (node.nodeName != 'BODY')
7902                         node = doc.body.firstChild;
7903                     else
7904                         node = doc.body;
7905
7906                     // set the new document in DOMUtils so createElement etc works
7907                     oldDoc = dom.doc;
7908                     dom.doc = doc;
d9344f 7909                 }
S 7910
a9251b 7911                 args = args || {};
T 7912                 args.format = args.format || 'html';
d9344f 7913
a9251b 7914                 // Pre process
T 7915                 if (!args.no_events) {
7916                     args.node = node;
7917                     onPreProcess.dispatch(self, args);
7918                 }
d9344f 7919
a9251b 7920                 // Setup serializer
T 7921                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
d9344f 7922
a9251b 7923                 // Parse and serialize HTML
T 7924                 args.content = htmlSerializer.serialize(
7925                     htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
7926                 );
d9344f 7927
a9251b 7928                 // Replace all BOM characters for now until we can find a better solution
T 7929                 if (!args.cleanup)
7930                     args.content = args.content.replace(/\uFEFF/g, '');
7931
7932                 // Post process
7933                 if (!args.no_events)
7934                     onPostProcess.dispatch(self, args);
7935
7936                 // Restore the old document if it was changed
7937                 if (oldDoc)
7938                     dom.doc = oldDoc;
7939
7940                 args.node = null;
7941
7942                 return args.content;
7943             },
7944
7945             addRules : function(rules) {
7946                 schema.addValidElements(rules);
7947             },
7948
7949             setRules : function(rules) {
7950                 schema.setValidElements(rules);
d9344f 7951             }
a9251b 7952         };
d9344f 7953     };
29da64 7954 })(tinymce);
A 7955 (function(tinymce) {
69d05c 7956     tinymce.dom.ScriptLoader = function(settings) {
A 7957         var QUEUED = 0,
7958             LOADING = 1,
7959             LOADED = 2,
7960             states = {},
7961             queue = [],
7962             scriptLoadedCallbacks = {},
7963             queueLoadedCallbacks = [],
7964             loading = 0,
7965             undefined;
d9344f 7966
69d05c 7967         function loadScript(url, callback) {
A 7968             var t = this, dom = tinymce.DOM, elm, uri, loc, id;
d9344f 7969
69d05c 7970             // Execute callback when script is loaded
A 7971             function done() {
7972                 dom.remove(id);
d9344f 7973
69d05c 7974                 if (elm)
A 7975                     elm.onreadystatechange = elm.onload = elm = null;
d9344f 7976
69d05c 7977                 callback();
A 7978             };
a9251b 7979             
T 7980             function error() {
7981                 // Report the error so it's easier for people to spot loading errors
7982                 if (typeof(console) !== "undefined" && console.log)
7983                     console.log("Failed to load: " + url);
7984
7985                 // We can't mark it as done if there is a load error since
7986                 // A) We don't want to produce 404 errors on the server and
7987                 // B) the onerror event won't fire on all browsers.
7988                 // done();
7989             };
d9344f 7990
69d05c 7991             id = dom.uniqueId();
d9344f 7992
69d05c 7993             if (tinymce.isIE6) {
A 7994                 uri = new tinymce.util.URI(url);
7995                 loc = location;
d9344f 7996
69d05c 7997                 // If script is from same domain and we
A 7998                 // use IE 6 then use XHR since it's more reliable
a9251b 7999                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
d9344f 8000                     tinymce.util.XHR.send({
69d05c 8001                         url : tinymce._addVer(uri.getURI()),
A 8002                         success : function(content) {
8003                             // Create new temp script element
8004                             var script = dom.create('script', {
8005                                 type : 'text/javascript'
8006                             });
8007
8008                             // Evaluate script in global scope
8009                             script.text = content;
8010                             document.getElementsByTagName('head')[0].appendChild(script);
8011                             dom.remove(script);
8012
8013                             done();
a9251b 8014                         },
T 8015                         
8016                         error : error
d9344f 8017                     });
S 8018
69d05c 8019                     return;
A 8020                 }
d9344f 8021             }
S 8022
69d05c 8023             // Create new script element
A 8024             elm = dom.create('script', {
8025                 id : id,
8026                 type : 'text/javascript',
8027                 src : tinymce._addVer(url)
d9344f 8028             });
S 8029
a9251b 8030             // Add onload listener for non IE browsers since IE9
T 8031             // fires onload event before the script is parsed and executed
8032             if (!tinymce.isIE)
8033                 elm.onload = done;
d9344f 8034
a9251b 8035             // Add onerror event will get fired on some browsers but not all of them
T 8036             elm.onerror = error;
8037
8038             // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
8039             if (!tinymce.isOpera) {
8040                 elm.onreadystatechange = function() {
8041                     var state = elm.readyState;
8042
8043                     // Loaded state is passed on IE 6 however there
8044                     // are known issues with this method but we can't use
8045                     // XHR in a cross domain loading
8046                     if (state == 'complete' || state == 'loaded')
8047                         done();
8048                 };
8049             }
d9344f 8050
69d05c 8051             // Most browsers support this feature so we report errors
A 8052             // for those at least to help users track their missing plugins etc
8053             // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
8054             /*elm.onerror = function() {
8055                 alert('Failed to load: ' + url);
8056             };*/
d9344f 8057
69d05c 8058             // Add script to document
A 8059             (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
8060         };
d9344f 8061
69d05c 8062         this.isDone = function(url) {
A 8063             return states[url] == LOADED;
8064         };
29da64 8065
69d05c 8066         this.markDone = function(url) {
A 8067             states[url] = LOADED;
8068         };
29da64 8069
69d05c 8070         this.add = this.load = function(url, callback, scope) {
A 8071             var item, state = states[url];
29da64 8072
69d05c 8073             // Add url to load queue
A 8074             if (state == undefined) {
8075                 queue.push(url);
8076                 states[url] = QUEUED;
d9344f 8077             }
69d05c 8078
A 8079             if (callback) {
8080                 // Store away callback for later execution
8081                 if (!scriptLoadedCallbacks[url])
8082                     scriptLoadedCallbacks[url] = [];
8083
8084                 scriptLoadedCallbacks[url].push({
8085                     func : callback,
8086                     scope : scope || this
8087                 });
8088             }
8089         };
8090
8091         this.loadQueue = function(callback, scope) {
8092             this.loadScripts(queue, callback, scope);
8093         };
8094
8095         this.loadScripts = function(scripts, callback, scope) {
8096             var loadScripts;
8097
8098             function execScriptLoadedCallbacks(url) {
8099                 // Execute URL callback functions
8100                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
8101                     callback.func.call(callback.scope);
8102                 });
8103
8104                 scriptLoadedCallbacks[url] = undefined;
8105             };
8106
8107             queueLoadedCallbacks.push({
8108                 func : callback,
8109                 scope : scope || this
8110             });
8111
8112             loadScripts = function() {
8113                 var loadingScripts = tinymce.grep(scripts);
8114
8115                 // Current scripts has been handled
8116                 scripts.length = 0;
8117
8118                 // Load scripts that needs to be loaded
8119                 tinymce.each(loadingScripts, function(url) {
8120                     // Script is already loaded then execute script callbacks directly
8121                     if (states[url] == LOADED) {
8122                         execScriptLoadedCallbacks(url);
8123                         return;
8124                     }
8125
8126                     // Is script not loading then start loading it
8127                     if (states[url] != LOADING) {
8128                         states[url] = LOADING;
8129                         loading++;
8130
8131                         loadScript(url, function() {
8132                             states[url] = LOADED;
8133                             loading--;
8134
8135                             execScriptLoadedCallbacks(url);
8136
8137                             // Load more scripts if they where added by the recently loaded script
8138                             loadScripts();
8139                         });
8140                     }
8141                 });
8142
8143                 // No scripts are currently loading then execute all pending queue loaded callbacks
8144                 if (!loading) {
8145                     tinymce.each(queueLoadedCallbacks, function(callback) {
8146                         callback.func.call(callback.scope);
8147                     });
8148
8149                     queueLoadedCallbacks.length = 0;
8150                 }
8151             };
8152
8153             loadScripts();
8154         };
8155     };
d9344f 8156
S 8157     // Global script loader
8158     tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
29da64 8159 })(tinymce);
69d05c 8160
A 8161 tinymce.dom.TreeWalker = function(start_node, root_node) {
8162     var node = start_node;
8163
8164     function findSibling(node, start_name, sibling_name, shallow) {
8165         var sibling, parent;
8166
8167         if (node) {
8168             // Walk into nodes if it has a start
8169             if (!shallow && node[start_name])
8170                 return node[start_name];
8171
8172             // Return the sibling if it has one
8173             if (node != root_node) {
8174                 sibling = node[sibling_name];
8175                 if (sibling)
8176                     return sibling;
8177
8178                 // Walk up the parents to look for siblings
8179                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
8180                     sibling = parent[sibling_name];
8181                     if (sibling)
8182                         return sibling;
8183                 }
8184             }
8185         }
8186     };
8187
8188     this.current = function() {
8189         return node;
8190     };
8191
8192     this.next = function(shallow) {
8193         return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
8194     };
8195
8196     this.prev = function(shallow) {
a9251b 8197         return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
69d05c 8198     };
A 8199 };
8200
8201 (function(tinymce) {
8202     tinymce.dom.RangeUtils = function(dom) {
8203         var INVISIBLE_CHAR = '\uFEFF';
8204
8205         this.walk = function(rng, callback) {
8206             var startContainer = rng.startContainer,
8207                 startOffset = rng.startOffset,
8208                 endContainer = rng.endContainer,
8209                 endOffset = rng.endOffset,
8210                 ancestor, startPoint,
8211                 endPoint, node, parent, siblings, nodes;
8212
8213             // Handle table cell selection the table plugin enables
8214             // you to fake select table cells and perform formatting actions on them
8215             nodes = dom.select('td.mceSelected,th.mceSelected');
8216             if (nodes.length > 0) {
8217                 tinymce.each(nodes, function(node) {
8218                     callback([node]);
8219                 });
8220
8221                 return;
8222             }
8223
8224             function collectSiblings(node, name, end_node) {
8225                 var siblings = [];
8226
8227                 for (; node && node != end_node; node = node[name])
8228                     siblings.push(node);
8229
8230                 return siblings;
8231             };
8232
8233             function findEndPoint(node, root) {
8234                 do {
8235                     if (node.parentNode == root)
8236                         return node;
8237
8238                     node = node.parentNode;
8239                 } while(node);
8240             };
8241
8242             function walkBoundary(start_node, end_node, next) {
8243                 var siblingName = next ? 'nextSibling' : 'previousSibling';
8244
8245                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
8246                     parent = node.parentNode;
8247                     siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
8248
8249                     if (siblings.length) {
8250                         if (!next)
8251                             siblings.reverse();
8252
8253                         callback(siblings);
8254                     }
8255                 }
8256             };
8257
8258             // If index based start position then resolve it
8259             if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
8260                 startContainer = startContainer.childNodes[startOffset];
8261
8262             // If index based end position then resolve it
8263             if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
a9251b 8264                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
69d05c 8265
A 8266             // Find common ancestor and end points
8267             ancestor = dom.findCommonAncestor(startContainer, endContainer);
8268
8269             // Same container
8270             if (startContainer == endContainer)
8271                 return callback([startContainer]);
8272
8273             // Process left side
8274             for (node = startContainer; node; node = node.parentNode) {
8275                 if (node == endContainer)
8276                     return walkBoundary(startContainer, ancestor, true);
8277
8278                 if (node == ancestor)
8279                     break;
8280             }
8281
8282             // Process right side
8283             for (node = endContainer; node; node = node.parentNode) {
8284                 if (node == startContainer)
8285                     return walkBoundary(endContainer, ancestor);
8286
8287                 if (node == ancestor)
8288                     break;
8289             }
8290
8291             // Find start/end point
8292             startPoint = findEndPoint(startContainer, ancestor) || startContainer;
8293             endPoint = findEndPoint(endContainer, ancestor) || endContainer;
8294
8295             // Walk left leaf
8296             walkBoundary(startContainer, startPoint, true);
8297
8298             // Walk the middle from start to end point
8299             siblings = collectSiblings(
8300                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
8301                 'nextSibling',
8302                 endPoint == endContainer ? endPoint.nextSibling : endPoint
8303             );
8304
8305             if (siblings.length)
8306                 callback(siblings);
8307
8308             // Walk right leaf
8309             walkBoundary(endContainer, endPoint);
8310         };
8311
8312         /*        this.split = function(rng) {
8313             var startContainer = rng.startContainer,
8314                 startOffset = rng.startOffset,
8315                 endContainer = rng.endContainer,
8316                 endOffset = rng.endOffset;
8317
8318             function splitText(node, offset) {
8319                 if (offset == node.nodeValue.length)
8320                     node.appendData(INVISIBLE_CHAR);
8321
8322                 node = node.splitText(offset);
8323
8324                 if (node.nodeValue === INVISIBLE_CHAR)
8325                     node.nodeValue = '';
8326
8327                 return node;
8328             };
8329
8330             // Handle single text node
8331             if (startContainer == endContainer) {
8332                 if (startContainer.nodeType == 3) {
8333                     if (startOffset != 0)
8334                         startContainer = endContainer = splitText(startContainer, startOffset);
8335
8336                     if (endOffset - startOffset != startContainer.nodeValue.length)
8337                         splitText(startContainer, endOffset - startOffset);
8338                 }
8339             } else {
8340                 // Split startContainer text node if needed
8341                 if (startContainer.nodeType == 3 && startOffset != 0) {
8342                     startContainer = splitText(startContainer, startOffset);
8343                     startOffset = 0;
8344                 }
8345
8346                 // Split endContainer text node if needed
8347                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
8348                     endContainer = splitText(endContainer, endOffset).previousSibling;
8349                     endOffset = endContainer.nodeValue.length;
8350                 }
8351             }
8352
8353             return {
8354                 startContainer : startContainer,
8355                 startOffset : startOffset,
8356                 endContainer : endContainer,
8357                 endOffset : endOffset
8358             };
8359         };
8360 */
8361     };
2011be 8362
A 8363     tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
8364         if (rng1 && rng2) {
8365             // Compare native IE ranges
8366             if (rng1.item || rng1.duplicate) {
8367                 // Both are control ranges and the selected element matches
8368                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
8369                     return true;
8370
8371                 // Both are text ranges and the range matches
8372                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
8373                     return true;
8374             } else {
8375                 // Compare w3c ranges
8376                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
8377             }
8378         }
8379
8380         return false;
8381     };
69d05c 8382 })(tinymce);
A 8383
29da64 8384 (function(tinymce) {
a9251b 8385     var Event = tinymce.dom.Event, each = tinymce.each;
T 8386
8387     tinymce.create('tinymce.ui.KeyboardNavigation', {
8388         KeyboardNavigation: function(settings, dom) {
8389             var t = this, root = settings.root, items = settings.items,
8390                     enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
8391                     excludeFromTabOrder = settings.excludeFromTabOrder,
8392                     itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
8393
8394             dom = dom || tinymce.DOM;
8395
8396             itemFocussed = function(evt) {
8397                 focussedId = evt.target.id;
8398             };
8399             
8400             itemBlurred = function(evt) {
8401                 dom.setAttrib(evt.target.id, 'tabindex', '-1');
8402             };
8403             
8404             rootFocussed = function(evt) {
8405                 var item = dom.get(focussedId);
8406                 dom.setAttrib(item, 'tabindex', '0');
8407                 item.focus();
8408             };
8409             
8410             t.focus = function() {
8411                 dom.get(focussedId).focus();
8412             };
8413
8414             t.destroy = function() {
8415                 each(items, function(item) {
8416                     dom.unbind(dom.get(item.id), 'focus', itemFocussed);
8417                     dom.unbind(dom.get(item.id), 'blur', itemBlurred);
8418                 });
8419
8420                 dom.unbind(dom.get(root), 'focus', rootFocussed);
8421                 dom.unbind(dom.get(root), 'keydown', rootKeydown);
8422
8423                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
8424                 t.destroy = function() {};
8425             };
8426             
8427             t.moveFocus = function(dir, evt) {
8428                 var idx = -1, controls = t.controls, newFocus;
8429
8430                 if (!focussedId)
8431                     return;
8432
8433                 each(items, function(item, index) {
8434                     if (item.id === focussedId) {
8435                         idx = index;
8436                         return false;
8437                     }
8438                 });
8439
8440                 idx += dir;
8441                 if (idx < 0) {
8442                     idx = items.length - 1;
8443                 } else if (idx >= items.length) {
8444                     idx = 0;
8445                 }
8446                 
8447                 newFocus = items[idx];
8448                 dom.setAttrib(focussedId, 'tabindex', '-1');
8449                 dom.setAttrib(newFocus.id, 'tabindex', '0');
8450                 dom.get(newFocus.id).focus();
8451
8452                 if (settings.actOnFocus) {
8453                     settings.onAction(newFocus.id);
8454                 }
8455
8456                 if (evt)
8457                     Event.cancel(evt);
8458             };
8459             
8460             rootKeydown = function(evt) {
8461                 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
8462                 
8463                 switch (evt.keyCode) {
8464                     case DOM_VK_LEFT:
8465                         if (enableLeftRight) t.moveFocus(-1);
8466                         break;
8467     
8468                     case DOM_VK_RIGHT:
8469                         if (enableLeftRight) t.moveFocus(1);
8470                         break;
8471     
8472                     case DOM_VK_UP:
8473                         if (enableUpDown) t.moveFocus(-1);
8474                         break;
8475
8476                     case DOM_VK_DOWN:
8477                         if (enableUpDown) t.moveFocus(1);
8478                         break;
8479
8480                     case DOM_VK_ESCAPE:
8481                         if (settings.onCancel) {
8482                             settings.onCancel();
8483                             Event.cancel(evt);
8484                         }
8485                         break;
8486
8487                     case DOM_VK_ENTER:
8488                     case DOM_VK_RETURN:
8489                     case DOM_VK_SPACE:
8490                         if (settings.onAction) {
8491                             settings.onAction(focussedId);
8492                             Event.cancel(evt);
8493                         }
8494                         break;
8495                 }
8496             };
8497
8498             // Set up state and listeners for each item.
8499             each(items, function(item, idx) {
8500                 var tabindex;
8501
8502                 if (!item.id) {
8503                     item.id = dom.uniqueId('_mce_item_');
8504                 }
8505
8506                 if (excludeFromTabOrder) {
8507                     dom.bind(item.id, 'blur', itemBlurred);
8508                     tabindex = '-1';
8509                 } else {
8510                     tabindex = (idx === 0 ? '0' : '-1');
8511                 }
8512
8513                 dom.setAttrib(item.id, 'tabindex', tabindex);
8514                 dom.bind(dom.get(item.id), 'focus', itemFocussed);
8515             });
8516             
8517             // Setup initial state for root element.
8518             if (items[0]){
8519                 focussedId = items[0].id;
8520             }
8521
8522             dom.setAttrib(root, 'tabindex', '-1');
8523             
8524             // Setup listeners for root element.
8525             dom.bind(dom.get(root), 'focus', rootFocussed);
8526             dom.bind(dom.get(root), 'keydown', rootKeydown);
8527         }
8528     });
8529 })(tinymce);
8530 (function(tinymce) {
d9344f 8531     // Shorten class names
S 8532     var DOM = tinymce.DOM, is = tinymce.is;
8533
8534     tinymce.create('tinymce.ui.Control', {
a9251b 8535         Control : function(id, s, editor) {
d9344f 8536             this.id = id;
S 8537             this.settings = s = s || {};
8538             this.rendered = false;
8539             this.onRender = new tinymce.util.Dispatcher(this);
8540             this.classPrefix = '';
8541             this.scope = s.scope || this;
8542             this.disabled = 0;
8543             this.active = 0;
a9251b 8544             this.editor = editor;
T 8545         },
8546         
8547         setAriaProperty : function(property, value) {
8548             var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
8549             if (element) {
8550                 DOM.setAttrib(element, 'aria-' + property, !!value);
8551             }
8552         },
8553         
8554         focus : function() {
8555             DOM.get(this.id).focus();
d9344f 8556         },
S 8557
8558         setDisabled : function(s) {
8559             if (s != this.disabled) {
a9251b 8560                 this.setAriaProperty('disabled', s);
d9344f 8561
S 8562                 this.setState('Disabled', s);
8563                 this.setState('Enabled', !s);
8564                 this.disabled = s;
8565             }
8566         },
8567
8568         isDisabled : function() {
8569             return this.disabled;
8570         },
8571
8572         setActive : function(s) {
8573             if (s != this.active) {
8574                 this.setState('Active', s);
8575                 this.active = s;
a9251b 8576                 this.setAriaProperty('pressed', s);
d9344f 8577             }
S 8578         },
8579
8580         isActive : function() {
8581             return this.active;
8582         },
8583
8584         setState : function(c, s) {
8585             var n = DOM.get(this.id);
8586
8587             c = this.classPrefix + c;
8588
8589             if (s)
8590                 DOM.addClass(n, c);
8591             else
8592                 DOM.removeClass(n, c);
8593         },
8594
8595         isRendered : function() {
8596             return this.rendered;
8597         },
8598
8599         renderHTML : function() {
8600         },
8601
8602         renderTo : function(n) {
8603             DOM.setHTML(n, this.renderHTML());
8604         },
8605
8606         postRender : function() {
8607             var t = this, b;
8608
8609             // Set pending states
8610             if (is(t.disabled)) {
8611                 b = t.disabled;
8612                 t.disabled = -1;
8613                 t.setDisabled(b);
8614             }
8615
8616             if (is(t.active)) {
8617                 b = t.active;
8618                 t.active = -1;
8619                 t.setActive(b);
8620             }
8621         },
8622
8623         remove : function() {
8624             DOM.remove(this.id);
8625             this.destroy();
8626         },
8627
8628         destroy : function() {
8629             tinymce.dom.Event.clear(this.id);
8630         }
58fb65 8631     });
69d05c 8632 })(tinymce);
A 8633 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
a9251b 8634     Container : function(id, s, editor) {
T 8635         this.parent(id, s, editor);
58fb65 8636
d9344f 8637         this.controls = [];
58fb65 8638
d9344f 8639         this.lookup = {};
S 8640     },
8641
8642     add : function(c) {
8643         this.lookup[c.id] = c;
8644         this.controls.push(c);
8645
8646         return c;
8647     },
8648
8649     get : function(n) {
8650         return this.lookup[n];
8651     }
58fb65 8652 });
d9344f 8653
69d05c 8654
d9344f 8655 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
S 8656     Separator : function(id, s) {
8657         this.parent(id, s);
8658         this.classPrefix = 'mceSeparator';
a9251b 8659         this.setDisabled(true);
d9344f 8660     },
S 8661
8662     renderHTML : function() {
a9251b 8663         return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
d9344f 8664     }
58fb65 8665 });
69d05c 8666
29da64 8667 (function(tinymce) {
d9344f 8668     var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
S 8669
8670     tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
8671         MenuItem : function(id, s) {
8672             this.parent(id, s);
8673             this.classPrefix = 'mceMenuItem';
8674         },
8675
8676         setSelected : function(s) {
8677             this.setState('Selected', s);
a9251b 8678             this.setAriaProperty('checked', !!s);
d9344f 8679             this.selected = s;
S 8680         },
8681
8682         isSelected : function() {
8683             return this.selected;
8684         },
8685
8686         postRender : function() {
8687             var t = this;
8688             
8689             t.parent();
8690
8691             // Set pending state
8692             if (is(t.selected))
8693                 t.setSelected(t.selected);
8694         }
58fb65 8695     });
29da64 8696 })(tinymce);
69d05c 8697
29da64 8698 (function(tinymce) {
d9344f 8699     var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
S 8700
8701     tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
8702         Menu : function(id, s) {
8703             var t = this;
8704
8705             t.parent(id, s);
8706             t.items = {};
8707             t.collapsed = false;
8708             t.menuCount = 0;
8709             t.onAddItem = new tinymce.util.Dispatcher(this);
8710         },
8711
8712         expand : function(d) {
8713             var t = this;
8714
8715             if (d) {
8716                 walk(t, function(o) {
8717                     if (o.expand)
8718                         o.expand();
8719                 }, 'items', t);
8720             }
8721
8722             t.collapsed = false;
8723         },
8724
8725         collapse : function(d) {
8726             var t = this;
8727
8728             if (d) {
8729                 walk(t, function(o) {
8730                     if (o.collapse)
8731                         o.collapse();
8732                 }, 'items', t);
8733             }
8734
8735             t.collapsed = true;
8736         },
8737
8738         isCollapsed : function() {
8739             return this.collapsed;
8740         },
8741
8742         add : function(o) {
8743             if (!o.settings)
8744                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
8745
8746             this.onAddItem.dispatch(this, o);
8747
8748             return this.items[o.id] = o;
8749         },
8750
8751         addSeparator : function() {
8752             return this.add({separator : true});
8753         },
8754
8755         addMenu : function(o) {
8756             if (!o.collapse)
8757                 o = this.createMenu(o);
8758
8759             this.menuCount++;
8760
8761             return this.add(o);
8762         },
8763
8764         hasMenus : function() {
8765             return this.menuCount !== 0;
8766         },
8767
8768         remove : function(o) {
8769             delete this.items[o.id];
8770         },
8771
8772         removeAll : function() {
8773             var t = this;
8774
8775             walk(t, function(o) {
8776                 if (o.removeAll)
8777                     o.removeAll();
8778                 else
8779                     o.remove();
8780
8781                 o.destroy();
8782             }, 'items', t);
8783
8784             t.items = {};
8785         },
8786
8787         createMenu : function(o) {
8788             var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
8789
8790             m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
8791
8792             return m;
8793         }
58fb65 8794     });
69d05c 8795 })(tinymce);
A 8796 (function(tinymce) {
d9344f 8797     var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
S 8798
8799     tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
8800         DropMenu : function(id, s) {
8801             s = s || {};
8802             s.container = s.container || DOM.doc.body;
8803             s.offset_x = s.offset_x || 0;
8804             s.offset_y = s.offset_y || 0;
8805             s.vp_offset_x = s.vp_offset_x || 0;
8806             s.vp_offset_y = s.vp_offset_y || 0;
8807
8808             if (is(s.icons) && !s.icons)
8809                 s['class'] += ' mceNoIcons';
8810
8811             this.parent(id, s);
8812             this.onShowMenu = new tinymce.util.Dispatcher(this);
8813             this.onHideMenu = new tinymce.util.Dispatcher(this);
8814             this.classPrefix = 'mceMenu';
8815         },
8816
8817         createMenu : function(s) {
8818             var t = this, cs = t.settings, m;
8819
8820             s.container = s.container || cs.container;
8821             s.parent = t;
8822             s.constrain = s.constrain || cs.constrain;
8823             s['class'] = s['class'] || cs['class'];
8824             s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
8825             s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
a9251b 8826             s.keyboard_focus = cs.keyboard_focus;
d9344f 8827             m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
S 8828
8829             m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
8830
8831             return m;
a9251b 8832         },
T 8833         
8834         focus : function() {
8835             var t = this;
8836             if (t.keyboardNav) {
8837                 t.keyboardNav.focus();
8838             }
d9344f 8839         },
S 8840
8841         update : function() {
8842             var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
8843
8844             tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
8845             th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
8846
8847             if (!DOM.boxModel)
8848                 t.element.setStyles({width : tw + 2, height : th + 2});
8849             else
8850                 t.element.setStyles({width : tw, height : th});
8851
8852             if (s.max_width)
8853                 DOM.setStyle(co, 'width', tw);
8854
8855             if (s.max_height) {
8856                 DOM.setStyle(co, 'height', th);
8857
8858                 if (tb.clientHeight < s.max_height)
8859                     DOM.setStyle(co, 'overflow', 'hidden');
8860             }
8861         },
8862
8863         showMenu : function(x, y, px) {
8864             var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
8865
8866             t.collapse(1);
8867
8868             if (t.isMenuVisible)
8869                 return;
8870
8871             if (!t.rendered) {
8872                 co = DOM.add(t.settings.container, t.renderNode());
8873
8874                 each(t.items, function(o) {
8875                     o.postRender();
8876                 });
8877
8878                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8879             } else
8880                 co = DOM.get('menu_' + t.id);
8881
8882             // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
8883             if (!tinymce.isOpera)
8884                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
8885
8886             DOM.show(co);
8887             t.update();
8888
8889             x += s.offset_x || 0;
8890             y += s.offset_y || 0;
8891             vp.w -= 4;
8892             vp.h -= 4;
8893
8894             // Move inside viewport if not submenu
8895             if (s.constrain) {
8896                 w = co.clientWidth - ot;
8897                 h = co.clientHeight - ot;
8898                 mx = vp.x + vp.w;
8899                 my = vp.y + vp.h;
8900
8901                 if ((x + s.vp_offset_x + w) > mx)
8902                     x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
8903
8904                 if ((y + s.vp_offset_y + h) > my)
8905                     y = Math.max(0, (my - s.vp_offset_y) - h);
8906             }
8907
8908             DOM.setStyles(co, {left : x , top : y});
8909             t.element.update();
8910
8911             t.isMenuVisible = 1;
18240a 8912             t.mouseClickFunc = Event.add(co, 'click', function(e) {
d9344f 8913                 var m;
S 8914
8915                 e = e.target;
8916
29da64 8917                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
d9344f 8918                     m = t.items[e.id];
S 8919
8920                     if (m.isDisabled())
8921                         return;
8922
8923                     dm = t;
8924
8925                     while (dm) {
8926                         if (dm.hideMenu)
8927                             dm.hideMenu();
8928
8929                         dm = dm.settings.parent;
8930                     }
8931
8932                     if (m.settings.onclick)
8933                         m.settings.onclick(e);
8934
8935                     return Event.cancel(e); // Cancel to fix onbeforeunload problem
8936                 }
8937             });
8938
8939             if (t.hasMenus()) {
8940                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
8941                     var m, r, mi;
8942
8943                     e = e.target;
29da64 8944                     if (e && (e = DOM.getParent(e, 'tr'))) {
d9344f 8945                         m = t.items[e.id];
S 8946
8947                         if (t.lastMenu)
8948                             t.lastMenu.collapse(1);
8949
8950                         if (m.isDisabled())
8951                             return;
8952
8953                         if (e && DOM.hasClass(e, cp + 'ItemSub')) {
8954                             //p = DOM.getPos(s.container);
8955                             r = DOM.getRect(e);
8956                             m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
8957                             t.lastMenu = m;
8958                             DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
8959                         }
8960                     }
8961                 });
8962             }
a9251b 8963             
T 8964             Event.add(co, 'keydown', t._keyHandler, t);
d9344f 8965
S 8966             t.onShowMenu.dispatch(t);
8967
a9251b 8968             if (s.keyboard_focus) { 
T 8969                 t._setupKeyboardNav(); 
d9344f 8970             }
S 8971         },
8972
8973         hideMenu : function(c) {
8974             var t = this, co = DOM.get('menu_' + t.id), e;
8975
8976             if (!t.isMenuVisible)
8977                 return;
8978
a9251b 8979             if (t.keyboardNav) t.keyboardNav.destroy();
d9344f 8980             Event.remove(co, 'mouseover', t.mouseOverFunc);
18240a 8981             Event.remove(co, 'click', t.mouseClickFunc);
d9344f 8982             Event.remove(co, 'keydown', t._keyHandler);
S 8983             DOM.hide(co);
8984             t.isMenuVisible = 0;
8985
8986             if (!c)
8987                 t.collapse(1);
8988
8989             if (t.element)
8990                 t.element.hide();
8991
8992             if (e = DOM.get(t.id))
8993                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
8994
8995             t.onHideMenu.dispatch(t);
8996         },
8997
8998         add : function(o) {
8999             var t = this, co;
9000
9001             o = t.parent(o);
9002
9003             if (t.isRendered && (co = DOM.get('menu_' + t.id)))
9004                 t._add(DOM.select('tbody', co)[0], o);
9005
9006             return o;
9007         },
9008
9009         collapse : function(d) {
9010             this.parent(d);
9011             this.hideMenu(1);
9012         },
9013
9014         remove : function(o) {
9015             DOM.remove(o.id);
9016             this.destroy();
9017
9018             return this.parent(o);
9019         },
9020
9021         destroy : function() {
9022             var t = this, co = DOM.get('menu_' + t.id);
9023
a9251b 9024             if (t.keyboardNav) t.keyboardNav.destroy();
d9344f 9025             Event.remove(co, 'mouseover', t.mouseOverFunc);
a9251b 9026             Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
d9344f 9027             Event.remove(co, 'click', t.mouseClickFunc);
a9251b 9028             Event.remove(co, 'keydown', t._keyHandler);
d9344f 9029
S 9030             if (t.element)
9031                 t.element.remove();
9032
9033             DOM.remove(co);
9034         },
9035
9036         renderNode : function() {
9037             var t = this, s = t.settings, n, tb, co, w;
9038
a9251b 9039             w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
T 9040             if (t.settings.parent) {
9041                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
9042             }
9043             co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
d9344f 9044             t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
S 9045
9046             if (s.menu_line)
9047                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
9048
9049 //            n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
a9251b 9050             n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
d9344f 9051             tb = DOM.add(n, 'tbody');
S 9052
9053             each(t.items, function(o) {
9054                 t._add(tb, o);
9055             });
9056
9057             t.rendered = true;
9058
9059             return w;
9060         },
9061
9062         // Internal functions
a9251b 9063         _setupKeyboardNav : function(){
T 9064             var contextMenu, menuItems, t=this; 
9065             contextMenu = DOM.select('#menu_' + t.id)[0];
9066             menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
9067             menuItems.splice(0,0,contextMenu);
9068             t.keyboardNav = new tinymce.ui.KeyboardNavigation({
9069                 root: 'menu_' + t.id,
9070                 items: menuItems,
9071                 onCancel: function() {
9072                     t.hideMenu();
9073                 },
9074                 enableUpDown: true
9075             });
9076             contextMenu.focus();
9077         },
d9344f 9078
a9251b 9079         _keyHandler : function(evt) {
T 9080             var t = this, e;
9081             switch (evt.keyCode) {
9082                 case 37: // Left
9083                     if (t.settings.parent) {
9084                         t.hideMenu();
9085                         t.settings.parent.focus();
9086                         Event.cancel(evt);
9087                     }
9088                     break;
9089                 case 39: // Right
9090                     if (t.mouseOverFunc)
9091                         t.mouseOverFunc(evt);
9092                     break;
18240a 9093             }
d9344f 9094         },
S 9095
9096         _add : function(tb, o) {
29da64 9097             var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
d9344f 9098
S 9099             if (s.separator) {
9100                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
9101                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
9102
9103                 if (n = ro.previousSibling)
9104                     DOM.addClass(n, 'mceLast');
9105
9106                 return;
9107             }
9108
9109             n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
a9251b 9110             n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
T 9111             n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
9112
9113             if (s.parent) {
9114                 DOM.setAttrib(a, 'aria-haspopup', 'true');
9115                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
9116             }
d9344f 9117
S 9118             DOM.addClass(it, s['class']);
9119 //            n = DOM.add(n, 'span', {'class' : 'item'});
29da64 9120
A 9121             ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
9122
9123             if (s.icon_src)
9124                 DOM.add(ic, 'img', {src : s.icon_src});
9125
d9344f 9126             n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
S 9127
9128             if (o.settings.style)
9129                 DOM.setAttrib(n, 'style', o.settings.style);
9130
9131             if (tb.childNodes.length == 1)
9132                 DOM.addClass(ro, 'mceFirst');
9133
9134             if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
9135                 DOM.addClass(ro, 'mceFirst');
9136
9137             if (o.collapse)
9138                 DOM.addClass(ro, cp + 'ItemSub');
9139
9140             if (n = ro.previousSibling)
9141                 DOM.removeClass(n, 'mceLast');
9142
9143             DOM.addClass(ro, 'mceLast');
9144         }
58fb65 9145     });
69d05c 9146 })(tinymce);
A 9147 (function(tinymce) {
d9344f 9148     var DOM = tinymce.DOM;
S 9149
9150     tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
a9251b 9151         Button : function(id, s, ed) {
T 9152             this.parent(id, s, ed);
d9344f 9153             this.classPrefix = 'mceButton';
S 9154         },
9155
9156         renderHTML : function() {
18240a 9157             var cp = this.classPrefix, s = this.settings, h, l;
A 9158
9159             l = DOM.encode(s.label || '');
a9251b 9160             h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
d9344f 9161
S 9162             if (s.image)
a9251b 9163                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
d9344f 9164             else
a9251b 9165                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
d9344f 9166
a9251b 9167             h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
T 9168             h += '</a>';
d9344f 9169             return h;
S 9170         },
9171
9172         postRender : function() {
9173             var t = this, s = t.settings;
9174
9175             tinymce.dom.Event.add(t.id, 'click', function(e) {
9176                 if (!t.isDisabled())
9177                     return s.onclick.call(s.scope, e);
9178             });
9179         }
58fb65 9180     });
29da64 9181 })(tinymce);
69d05c 9182
29da64 9183 (function(tinymce) {
d9344f 9184     var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
S 9185
9186     tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
a9251b 9187         ListBox : function(id, s, ed) {
d9344f 9188             var t = this;
S 9189
a9251b 9190             t.parent(id, s, ed);
58fb65 9191
d9344f 9192             t.items = [];
58fb65 9193
d9344f 9194             t.onChange = new Dispatcher(t);
58fb65 9195
d9344f 9196             t.onPostRender = new Dispatcher(t);
58fb65 9197
d9344f 9198             t.onAdd = new Dispatcher(t);
58fb65 9199
d9344f 9200             t.onRenderMenu = new tinymce.util.Dispatcher(this);
58fb65 9201
d9344f 9202             t.classPrefix = 'mceListBox';
S 9203         },
9204
29da64 9205         select : function(va) {
A 9206             var t = this, fv, f;
9207
9208             if (va == undefined)
9209                 return t.selectByIndex(-1);
9210
9211             // Is string or number make function selector
9212             if (va && va.call)
9213                 f = va;
9214             else {
9215                 f = function(v) {
9216                     return v == va;
9217                 };
9218             }
d9344f 9219
S 9220             // Do we need to do something?
29da64 9221             if (va != t.selectedValue) {
d9344f 9222                 // Find item
29da64 9223                 each(t.items, function(o, i) {
A 9224                     if (f(o.value)) {
d9344f 9225                         fv = 1;
29da64 9226                         t.selectByIndex(i);
d9344f 9227                         return false;
S 9228                     }
9229                 });
9230
29da64 9231                 if (!fv)
A 9232                     t.selectByIndex(-1);
9233             }
9234         },
9235
9236         selectByIndex : function(idx) {
9237             var t = this, e, o;
9238
9239             if (idx != t.selectedIndex) {
9240                 e = DOM.get(t.id + '_text');
9241                 o = t.items[idx];
9242
9243                 if (o) {
9244                     t.selectedValue = o.value;
9245                     t.selectedIndex = idx;
9246                     DOM.setHTML(e, DOM.encode(o.title));
9247                     DOM.removeClass(e, 'mceTitle');
a9251b 9248                     DOM.setAttrib(t.id, 'aria-valuenow', o.title);
29da64 9249                 } else {
d9344f 9250                     DOM.setHTML(e, DOM.encode(t.settings.title));
S 9251                     DOM.addClass(e, 'mceTitle');
29da64 9252                     t.selectedValue = t.selectedIndex = null;
a9251b 9253                     DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
29da64 9254                 }
A 9255                 e = 0;
9256             }
d9344f 9257         },
S 9258
9259         add : function(n, v, o) {
9260             var t = this;
9261
9262             o = o || {};
9263             o = tinymce.extend(o, {
9264                 title : n,
9265                 value : v
9266             });
9267
9268             t.items.push(o);
9269             t.onAdd.dispatch(t, o);
9270         },
9271
9272         getLength : function() {
9273             return this.items.length;
9274         },
9275
9276         renderHTML : function() {
9277             var h = '', t = this, s = t.settings, cp = t.classPrefix;
9278
a9251b 9279             h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
T 9280             h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
9281             h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
9282             h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
9283             h += '</tr></tbody></table></span>';
d9344f 9284
S 9285             return h;
9286         },
9287
9288         showMenu : function() {
9289             var t = this, p1, p2, e = DOM.get(this.id), m;
9290
9291             if (t.isDisabled() || t.items.length == 0)
9292                 return;
9293
18240a 9294             if (t.menu && t.menu.isMenuVisible)
A 9295                 return t.hideMenu();
9296
d9344f 9297             if (!t.isMenuRendered) {
S 9298                 t.renderMenu();
9299                 t.isMenuRendered = true;
9300             }
9301
9302             p1 = DOM.getPos(this.settings.menu_container);
9303             p2 = DOM.getPos(e);
9304
9305             m = t.menu;
9306             m.settings.offset_x = p2.x;
9307             m.settings.offset_y = p2.y;
18240a 9308             m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
d9344f 9309
S 9310             // Select in menu
9311             if (t.oldID)
9312                 m.items[t.oldID].setSelected(0);
9313
9314             each(t.items, function(o) {
9315                 if (o.value === t.selectedValue) {
9316                     m.items[o.id].setSelected(1);
9317                     t.oldID = o.id;
9318                 }
9319             });
9320
9321             m.showMenu(0, e.clientHeight);
9322
9323             Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9324             DOM.addClass(t.id, t.classPrefix + 'Selected');
18240a 9325
A 9326             //DOM.get(t.id + '_text').focus();
d9344f 9327         },
S 9328
9329         hideMenu : function(e) {
9330             var t = this;
18240a 9331
69d05c 9332             if (t.menu && t.menu.isMenuVisible) {
a9251b 9333                 DOM.removeClass(t.id, t.classPrefix + 'Selected');
T 9334
69d05c 9335                 // Prevent double toogles by canceling the mouse click event to the button
A 9336                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
9337                     return;
d9344f 9338
69d05c 9339                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
A 9340                     DOM.removeClass(t.id, t.classPrefix + 'Selected');
9341                     Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
d9344f 9342                     t.menu.hideMenu();
69d05c 9343                 }
d9344f 9344             }
S 9345         },
9346
9347         renderMenu : function() {
9348             var t = this, m;
9349
9350             m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9351                 menu_line : 1,
9352                 'class' : t.classPrefix + 'Menu mceNoIcons',
9353                 max_width : 150,
9354                 max_height : 150
9355             });
9356
a9251b 9357             m.onHideMenu.add(function() {
T 9358                 t.hideMenu();
9359                 t.focus();
9360             });
d9344f 9361
S 9362             m.add({
9363                 title : t.settings.title,
18240a 9364                 'class' : 'mceMenuItemTitle',
A 9365                 onclick : function() {
9366                     if (t.settings.onselect('') !== false)
9367                         t.select(''); // Must be runned after
9368                 }
9369             });
d9344f 9370
S 9371             each(t.items, function(o) {
69d05c 9372                 // No value then treat it as a title
A 9373                 if (o.value === undefined) {
9374                     m.add({
9375                         title : o.title,
9376                         'class' : 'mceMenuItemTitle',
9377                         onclick : function() {
9378                             if (t.settings.onselect('') !== false)
9379                                 t.select(''); // Must be runned after
9380                         }
9381                     });
9382                 } else {
9383                     o.id = DOM.uniqueId();
9384                     o.onclick = function() {
9385                         if (t.settings.onselect(o.value) !== false)
9386                             t.select(o.value); // Must be runned after
9387                     };
d9344f 9388
69d05c 9389                     m.add(o);
A 9390                 }
d9344f 9391             });
S 9392
9393             t.onRenderMenu.dispatch(t, m);
9394             t.menu = m;
9395         },
9396
9397         postRender : function() {
9398             var t = this, cp = t.classPrefix;
9399
9400             Event.add(t.id, 'click', t.showMenu, t);
a9251b 9401             Event.add(t.id, 'keydown', function(evt) {
T 9402                 if (evt.keyCode == 32) { // Space
9403                     t.showMenu(evt);
9404                     Event.cancel(evt);
9405                 }
9406             });
9407             Event.add(t.id, 'focus', function() {
18240a 9408                 if (!t._focused) {
a9251b 9409                     t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
T 9410                         if (e.keyCode == 40) {
9411                             t.showMenu();
9412                             Event.cancel(e);
9413                         }
9414                     });
9415                     t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
9416                         var v;
9417                         if (e.keyCode == 13) {
18240a 9418                             // Fake select on enter
A 9419                             v = t.selectedValue;
9420                             t.selectedValue = null; // Needs to be null to fake change
a9251b 9421                             Event.cancel(e);
18240a 9422                             t.settings.onselect(v);
A 9423                         }
9424                     });
9425                 }
9426
9427                 t._focused = 1;
9428             });
a9251b 9429             Event.add(t.id, 'blur', function() {
T 9430                 Event.remove(t.id, 'keydown', t.keyDownHandler);
9431                 Event.remove(t.id, 'keypress', t.keyPressHandler);
9432                 t._focused = 0;
9433             });
d9344f 9434
S 9435             // Old IE doesn't have hover on all elements
9436             if (tinymce.isIE6 || !DOM.boxModel) {
9437                 Event.add(t.id, 'mouseover', function() {
9438                     if (!DOM.hasClass(t.id, cp + 'Disabled'))
9439                         DOM.addClass(t.id, cp + 'Hover');
9440                 });
9441
9442                 Event.add(t.id, 'mouseout', function() {
9443                     if (!DOM.hasClass(t.id, cp + 'Disabled'))
9444                         DOM.removeClass(t.id, cp + 'Hover');
9445                 });
9446             }
9447
9448             t.onPostRender.dispatch(t, DOM.get(t.id));
9449         },
9450
9451         destroy : function() {
9452             this.parent();
9453
9454             Event.clear(this.id + '_text');
58fb65 9455             Event.clear(this.id + '_open');
d9344f 9456         }
58fb65 9457     });
69d05c 9458 })(tinymce);
A 9459 (function(tinymce) {
d9344f 9460     var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
S 9461
9462     tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
9463         NativeListBox : function(id, s) {
9464             this.parent(id, s);
9465             this.classPrefix = 'mceNativeListBox';
9466         },
9467
9468         setDisabled : function(s) {
9469             DOM.get(this.id).disabled = s;
a9251b 9470             this.setAriaProperty('disabled', s);
d9344f 9471         },
S 9472
9473         isDisabled : function() {
9474             return DOM.get(this.id).disabled;
9475         },
9476
29da64 9477         select : function(va) {
A 9478             var t = this, fv, f;
d9344f 9479
29da64 9480             if (va == undefined)
A 9481                 return t.selectByIndex(-1);
d9344f 9482
29da64 9483             // Is string or number make function selector
A 9484             if (va && va.call)
9485                 f = va;
9486             else {
9487                 f = function(v) {
9488                     return v == va;
9489                 };
9490             }
9491
9492             // Do we need to do something?
9493             if (va != t.selectedValue) {
9494                 // Find item
9495                 each(t.items, function(o, i) {
9496                     if (f(o.value)) {
9497                         fv = 1;
9498                         t.selectByIndex(i);
9499                         return false;
9500                     }
9501                 });
9502
9503                 if (!fv)
9504                     t.selectByIndex(-1);
9505             }
9506         },
9507
9508         selectByIndex : function(idx) {
9509             DOM.get(this.id).selectedIndex = idx + 1;
9510             this.selectedValue = this.items[idx] ? this.items[idx].value : null;
d9344f 9511         },
S 9512
9513         add : function(n, v, a) {
9514             var o, t = this;
9515
9516             a = a || {};
9517             a.value = v;
9518
9519             if (t.isRendered())
9520                 DOM.add(DOM.get(this.id), 'option', a, n);
9521
9522             o = {
9523                 title : n,
9524                 value : v,
9525                 attribs : a
9526             };
9527
9528             t.items.push(o);
9529             t.onAdd.dispatch(t, o);
9530         },
9531
9532         getLength : function() {
799907 9533             return this.items.length;
d9344f 9534         },
S 9535
9536         renderHTML : function() {
9537             var h, t = this;
9538
9539             h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
9540
9541             each(t.items, function(it) {
9542                 h += DOM.createHTML('option', {value : it.value}, it.title);
9543             });
9544
a9251b 9545             h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
T 9546             h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
d9344f 9547             return h;
S 9548         },
9549
9550         postRender : function() {
a9251b 9551             var t = this, ch, changeListenerAdded = true;
d9344f 9552
S 9553             t.rendered = true;
9554
9555             function onChange(e) {
29da64 9556                 var v = t.items[e.target.selectedIndex - 1];
d9344f 9557
29da64 9558                 if (v && (v = v.value)) {
A 9559                     t.onChange.dispatch(t, v);
d9344f 9560
29da64 9561                     if (t.settings.onselect)
A 9562                         t.settings.onselect(v);
9563                 }
d9344f 9564             };
S 9565
9566             Event.add(t.id, 'change', onChange);
9567
9568             // Accessibility keyhandler
9569             Event.add(t.id, 'keydown', function(e) {
9570                 var bf;
9571
9572                 Event.remove(t.id, 'change', ch);
a9251b 9573                 changeListenerAdded = false;
d9344f 9574
S 9575                 bf = Event.add(t.id, 'blur', function() {
a9251b 9576                     if (changeListenerAdded) return;
T 9577                     changeListenerAdded = true;
d9344f 9578                     Event.add(t.id, 'change', onChange);
S 9579                     Event.remove(t.id, 'blur', bf);
9580                 });
9581
9582                 if (e.keyCode == 13 || e.keyCode == 32) {
9583                     onChange(e);
9584                     return Event.cancel(e);
9585                 }
9586             });
9587
9588             t.onPostRender.dispatch(t, DOM.get(t.id));
9589         }
58fb65 9590     });
69d05c 9591 })(tinymce);
A 9592 (function(tinymce) {
d9344f 9593     var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
S 9594
9595     tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
a9251b 9596         MenuButton : function(id, s, ed) {
T 9597             this.parent(id, s, ed);
58fb65 9598
d9344f 9599             this.onRenderMenu = new tinymce.util.Dispatcher(this);
58fb65 9600
d9344f 9601             s.menu_container = s.menu_container || DOM.doc.body;
S 9602         },
9603
9604         showMenu : function() {
9605             var t = this, p1, p2, e = DOM.get(t.id), m;
9606
9607             if (t.isDisabled())
9608                 return;
9609
9610             if (!t.isMenuRendered) {
9611                 t.renderMenu();
9612                 t.isMenuRendered = true;
9613             }
9614
18240a 9615             if (t.isMenuVisible)
A 9616                 return t.hideMenu();
9617
d9344f 9618             p1 = DOM.getPos(t.settings.menu_container);
S 9619             p2 = DOM.getPos(e);
9620
9621             m = t.menu;
9622             m.settings.offset_x = p2.x;
9623             m.settings.offset_y = p2.y;
9624             m.settings.vp_offset_x = p2.x;
9625             m.settings.vp_offset_y = p2.y;
9626             m.settings.keyboard_focus = t._focused;
9627             m.showMenu(0, e.clientHeight);
9628
9629             Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9630             t.setState('Selected', 1);
18240a 9631
A 9632             t.isMenuVisible = 1;
d9344f 9633         },
S 9634
9635         renderMenu : function() {
9636             var t = this, m;
9637
9638             m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9639                 menu_line : 1,
9640                 'class' : this.classPrefix + 'Menu',
9641                 icons : t.settings.icons
9642             });
9643
a9251b 9644             m.onHideMenu.add(function() {
T 9645                 t.hideMenu();
9646                 t.focus();
9647             });
d9344f 9648
S 9649             t.onRenderMenu.dispatch(t, m);
9650             t.menu = m;
9651         },
9652
9653         hideMenu : function(e) {
9654             var t = this;
9655
18240a 9656             // Prevent double toogles by canceling the mouse click event to the button
A 9657             if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
9658                 return;
9659
29da64 9660             if (!e || !DOM.getParent(e.target, '.mceMenu')) {
d9344f 9661                 t.setState('Selected', 0);
S 9662                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9663                 if (t.menu)
9664                     t.menu.hideMenu();
9665             }
18240a 9666
A 9667             t.isMenuVisible = 0;
d9344f 9668         },
S 9669
9670         postRender : function() {
9671             var t = this, s = t.settings;
9672
9673             Event.add(t.id, 'click', function() {
9674                 if (!t.isDisabled()) {
9675                     if (s.onclick)
9676                         s.onclick(t.value);
9677
9678                     t.showMenu();
9679                 }
9680             });
9681         }
58fb65 9682     });
29da64 9683 })(tinymce);
69d05c 9684
29da64 9685 (function(tinymce) {
d9344f 9686     var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
S 9687
9688     tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
a9251b 9689         SplitButton : function(id, s, ed) {
T 9690             this.parent(id, s, ed);
d9344f 9691             this.classPrefix = 'mceSplitButton';
S 9692         },
9693
9694         renderHTML : function() {
9695             var h, t = this, s = t.settings, h1;
9696
9697             h = '<tbody><tr>';
9698
9699             if (s.image)
a9251b 9700                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
d9344f 9701             else
S 9702                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
9703
a9251b 9704             h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
T 9705             h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
d9344f 9706     
a9251b 9707             h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
T 9708             h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
d9344f 9709
S 9710             h += '</tr></tbody>';
a9251b 9711             h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
T 9712             return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
d9344f 9713         },
S 9714
9715         postRender : function() {
a9251b 9716             var t = this, s = t.settings, activate;
d9344f 9717
S 9718             if (s.onclick) {
a9251b 9719                 activate = function(evt) {
T 9720                     if (!t.isDisabled()) {
d9344f 9721                         s.onclick(t.value);
a9251b 9722                         Event.cancel(evt);
T 9723                     }
9724                 };
9725                 Event.add(t.id + '_action', 'click', activate);
9726                 Event.add(t.id, ['click', 'keydown'], function(evt) {
9727                     var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
9728                     if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
9729                         activate();
9730                         Event.cancel(evt);
9731                     } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
9732                         t.showMenu();
9733                         Event.cancel(evt);
9734                     }
d9344f 9735                 });
S 9736             }
9737
a9251b 9738             Event.add(t.id + '_open', 'click', function (evt) {
T 9739                 t.showMenu();
9740                 Event.cancel(evt);
9741             });
9742             Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
9743             Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
d9344f 9744
S 9745             // Old IE doesn't have hover on all elements
9746             if (tinymce.isIE6 || !DOM.boxModel) {
9747                 Event.add(t.id, 'mouseover', function() {
9748                     if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9749                         DOM.addClass(t.id, 'mceSplitButtonHover');
9750                 });
9751
9752                 Event.add(t.id, 'mouseout', function() {
9753                     if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9754                         DOM.removeClass(t.id, 'mceSplitButtonHover');
9755                 });
9756             }
9757         },
9758
9759         destroy : function() {
9760             this.parent();
9761
9762             Event.clear(this.id + '_action');
9763             Event.clear(this.id + '_open');
a9251b 9764             Event.clear(this.id);
d9344f 9765         }
58fb65 9766     });
29da64 9767 })(tinymce);
69d05c 9768
29da64 9769 (function(tinymce) {
d9344f 9770     var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
S 9771
9772     tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
a9251b 9773         ColorSplitButton : function(id, s, ed) {
d9344f 9774             var t = this;
S 9775
a9251b 9776             t.parent(id, s, ed);
d9344f 9777
S 9778             t.settings = s = tinymce.extend({
9779                 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
9780                 grid_width : 8,
9781                 default_color : '#888888'
9782             }, t.settings);
9783
18240a 9784             t.onShowMenu = new tinymce.util.Dispatcher(t);
58fb65 9785
18240a 9786             t.onHideMenu = new tinymce.util.Dispatcher(t);
A 9787
d9344f 9788             t.value = s.default_color;
S 9789         },
9790
9791         showMenu : function() {
9792             var t = this, r, p, e, p2;
9793
9794             if (t.isDisabled())
9795                 return;
9796
9797             if (!t.isMenuRendered) {
9798                 t.renderMenu();
9799                 t.isMenuRendered = true;
9800             }
18240a 9801
A 9802             if (t.isMenuVisible)
9803                 return t.hideMenu();
d9344f 9804
S 9805             e = DOM.get(t.id);
9806             DOM.show(t.id + '_menu');
9807             DOM.addClass(e, 'mceSplitButtonSelected');
9808             p2 = DOM.getPos(e);
9809             DOM.setStyles(t.id + '_menu', {
9810                 left : p2.x,
9811                 top : p2.y + e.clientHeight,
9812                 zIndex : 200000
9813             });
9814             e = 0;
9815
9816             Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
58fb65 9817             t.onShowMenu.dispatch(t);
d9344f 9818
S 9819             if (t._focused) {
9820                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
9821                     if (e.keyCode == 27)
9822                         t.hideMenu();
9823                 });
9824
9825                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
9826             }
18240a 9827
A 9828             t.isMenuVisible = 1;
d9344f 9829         },
S 9830
9831         hideMenu : function(e) {
9832             var t = this;
18240a 9833
a9251b 9834             if (t.isMenuVisible) {
T 9835                 // Prevent double toogles by canceling the mouse click event to the button
9836                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
9837                     return;
d9344f 9838
a9251b 9839                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
T 9840                     DOM.removeClass(t.id, 'mceSplitButtonSelected');
9841                     Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9842                     Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
9843                     DOM.hide(t.id + '_menu');
9844                 }
9845
9846                 t.isMenuVisible = 0;
d9344f 9847             }
S 9848         },
9849
9850         renderMenu : function() {
a9251b 9851             var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
d9344f 9852
a9251b 9853             w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
d9344f 9854             m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
S 9855             DOM.add(m, 'span', {'class' : 'mceMenuLine'});
9856
a9251b 9857             n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
d9344f 9858             tb = DOM.add(n, 'tbody');
S 9859
9860             // Generate color grid
9861             i = 0;
9862             each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
9863                 c = c.replace(/^#/, '');
9864
9865                 if (!i--) {
9866                     tr = DOM.add(tb, 'tr');
9867                     i = s.grid_width - 1;
9868                 }
9869
9870                 n = DOM.add(tr, 'td');
9871                 n = DOM.add(n, 'a', {
a9251b 9872                     role : 'option',
d9344f 9873                     href : 'javascript:;',
S 9874                     style : {
9875                         backgroundColor : '#' + c
9876                     },
a9251b 9877                     'title': t.editor.getLang('colors.' + c, c),
T 9878                     'data-mce-color' : '#' + c
d9344f 9879                 });
a9251b 9880
T 9881                 if (t.editor.forcedHighContrastMode) {
9882                     n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
9883                     if (n.getContext && (context = n.getContext("2d"))) {
9884                         context.fillStyle = '#' + c;
9885                         context.fillRect(0, 0, 16, 16);
9886                     } else {
9887                         // No point leaving a canvas element around if it's not supported for drawing on anyway.
9888                         DOM.remove(n);
9889                     }
9890                 }
d9344f 9891             });
S 9892
9893             if (s.more_colors_func) {
9894                 n = DOM.add(tb, 'tr');
9895                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
a9251b 9896                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
d9344f 9897
S 9898                 Event.add(n, 'click', function(e) {
9899                     s.more_colors_func.call(s.more_colors_scope || this);
9900                     return Event.cancel(e); // Cancel to fix onbeforeunload problem
9901                 });
9902             }
9903
9904             DOM.addClass(m, 'mceColorSplitMenu');
a9251b 9905             
T 9906             new tinymce.ui.KeyboardNavigation({
9907                 root: t.id + '_menu',
9908                 items: DOM.select('a', t.id + '_menu'),
9909                 onCancel: function() {
9910                     t.hideMenu();
9911                     t.focus();
9912                 }
9913             });
9914
9915             // Prevent IE from scrolling and hindering click to occur #4019
9916             Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
d9344f 9917
S 9918             Event.add(t.id + '_menu', 'click', function(e) {
9919                 var c;
9920
a9251b 9921                 e = DOM.getParent(e.target, 'a', tb);
d9344f 9922
a9251b 9923                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
d9344f 9924                     t.setColor(c);
18240a 9925
A 9926                 return Event.cancel(e); // Prevent IE auto save warning
d9344f 9927             });
S 9928
9929             return w;
9930         },
9931
9932         setColor : function(c) {
a9251b 9933             this.displayColor(c);
T 9934             this.hideMenu();
9935             this.settings.onselect(c);
9936         },
9937         
9938         displayColor : function(c) {
d9344f 9939             var t = this;
S 9940
9941             DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
9942
9943             t.value = c;
9944         },
9945
9946         postRender : function() {
9947             var t = this, id = t.id;
9948
9949             t.parent();
9950             DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
29da64 9951             DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
d9344f 9952         },
S 9953
9954         destroy : function() {
9955             this.parent();
9956
9957             Event.clear(this.id + '_menu');
9958             Event.clear(this.id + '_more');
9959             DOM.remove(this.id + '_menu');
9960         }
58fb65 9961     });
29da64 9962 })(tinymce);
69d05c 9963
a9251b 9964 (function(tinymce) {
T 9965 // Shorten class names
9966 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
9967 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
9968     renderHTML : function() {
9969         var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
9970
9971         h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
9972         //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
9973         h.push("<span role='application'>");
9974         h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
9975         each(controls, function(toolbar) {
9976             h.push(toolbar.renderHTML());
9977         });
9978         h.push("</span>");
9979         h.push('</div>');
9980
9981         return h.join('');
9982     },
9983     
9984     focus : function() {
9985         this.keyNav.focus();
9986     },
9987     
9988     postRender : function() {
9989         var t = this, items = [];
9990
9991         each(t.controls, function(toolbar) {
9992             each (toolbar.controls, function(control) {
9993                 if (control.id) {
9994                     items.push(control);
9995                 }
9996             });
9997         });
9998
9999         t.keyNav = new tinymce.ui.KeyboardNavigation({
10000             root: t.id,
10001             items: items,
10002             onCancel: function() {
10003                 t.editor.focus();
10004             },
10005             excludeFromTabOrder: !t.settings.tab_focus_toolbar
10006         });
10007     },
10008     
10009     destroy : function() {
10010         var self = this;
10011
10012         self.parent();
10013         self.keyNav.destroy();
10014         Event.clear(self.id);
10015     }
10016 });
10017 })(tinymce);
10018
10019 (function(tinymce) {
10020 // Shorten class names
10021 var dom = tinymce.DOM, each = tinymce.each;
d9344f 10022 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
S 10023     renderHTML : function() {
a9251b 10024         var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
d9344f 10025
S 10026         cl = t.controls;
10027         for (i=0; i<cl.length; i++) {
10028             // Get current control, prev control, next control and if the control is a list box or not
10029             co = cl[i];
10030             pr = cl[i - 1];
10031             nx = cl[i + 1];
10032
10033             // Add toolbar start
10034             if (i === 0) {
10035                 c = 'mceToolbarStart';
10036
10037                 if (co.Button)
10038                     c += ' mceToolbarStartButton';
10039                 else if (co.SplitButton)
10040                     c += ' mceToolbarStartSplitButton';
10041                 else if (co.ListBox)
10042                     c += ' mceToolbarStartListBox';
10043
10044                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10045             }
10046
10047             // Add toolbar end before list box and after the previous button
10048             // This is to fix the o2k7 editor skins
10049             if (pr && co.ListBox) {
10050                 if (pr.Button || pr.SplitButton)
10051                     h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
10052             }
10053
10054             // Render control HTML
10055
10056             // IE 8 quick fix, needed to propertly generate a hit area for anchors
10057             if (dom.stdMode)
10058                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
10059             else
10060                 h += '<td>' + co.renderHTML() + '</td>';
10061
10062             // Add toolbar start after list box and before the next button
10063             // This is to fix the o2k7 editor skins
10064             if (nx && co.ListBox) {
10065                 if (nx.Button || nx.SplitButton)
10066                     h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
10067             }
10068         }
10069
10070         c = 'mceToolbarEnd';
10071
10072         if (co.Button)
10073             c += ' mceToolbarEndButton';
10074         else if (co.SplitButton)
10075             c += ' mceToolbarEndSplitButton';
10076         else if (co.ListBox)
10077             c += ' mceToolbarEndListBox';
10078
10079         h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10080
a9251b 10081         return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
d9344f 10082     }
58fb65 10083 });
a9251b 10084 })(tinymce);
69d05c 10085
29da64 10086 (function(tinymce) {
d9344f 10087     var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
S 10088
10089     tinymce.create('tinymce.AddOnManager', {
a9251b 10090         AddOnManager : function() {
T 10091             var self = this;
58fb65 10092
a9251b 10093             self.items = [];
T 10094             self.urls = {};
10095             self.lookup = {};
10096             self.onAdd = new Dispatcher(self);
10097         },
d9344f 10098
S 10099         get : function(n) {
10100             return this.lookup[n];
10101         },
10102
10103         requireLangPack : function(n) {
69d05c 10104             var s = tinymce.settings;
d9344f 10105
a9251b 10106             if (s && s.language && s.language_load !== false)
69d05c 10107                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
d9344f 10108         },
S 10109
10110         add : function(id, o) {
10111             this.items.push(o);
10112             this.lookup[id] = o;
10113             this.onAdd.dispatch(this, id, o);
10114
10115             return o;
10116         },
10117
10118         load : function(n, u, cb, s) {
10119             var t = this;
10120
10121             if (t.urls[n])
10122                 return;
10123
10124             if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
a9251b 10125                 u = tinymce.baseURL + '/' + u;
d9344f 10126
S 10127             t.urls[n] = u.substring(0, u.lastIndexOf('/'));
a9251b 10128
T 10129             if (!t.lookup[n])
10130                 tinymce.ScriptLoader.add(u, cb, s);
d9344f 10131         }
58fb65 10132     });
d9344f 10133
S 10134     // Create plugin and theme managers
10135     tinymce.PluginManager = new tinymce.AddOnManager();
10136     tinymce.ThemeManager = new tinymce.AddOnManager();
58fb65 10137 }(tinymce));
A 10138
10139 (function(tinymce) {
d9344f 10140     // Shorten names
69d05c 10141     var each = tinymce.each, extend = tinymce.extend,
A 10142         DOM = tinymce.DOM, Event = tinymce.dom.Event,
10143         ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10144         explode = tinymce.explode,
10145         Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
d9344f 10146
69d05c 10147     // Setup some URLs where the editor API is located and where the document is
A 10148     tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
10149     if (!/[\/\\]$/.test(tinymce.documentBaseURL))
10150         tinymce.documentBaseURL += '/';
10151
10152     tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
10153
10154     tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
10155
10156     // Add before unload listener
10157     // This was required since IE was leaking memory if you added and removed beforeunload listeners
10158     // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
10159     tinymce.onBeforeUnload = new Dispatcher(tinymce);
10160
10161     // Must be on window or IE will leak if the editor is placed in frame or iframe
10162     Event.add(window, 'beforeunload', function(e) {
10163         tinymce.onBeforeUnload.dispatch(tinymce, e);
10164     });
10165
10166     tinymce.onAddEditor = new Dispatcher(tinymce);
10167
10168     tinymce.onRemoveEditor = new Dispatcher(tinymce);
10169
10170     tinymce.EditorManager = extend(tinymce, {
10171         editors : [],
58fb65 10172
d9344f 10173         i18n : {},
69d05c 10174
d9344f 10175         activeEditor : null,
S 10176
10177         init : function(s) {
69d05c 10178             var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
d9344f 10179
S 10180             function execCallback(se, n, s) {
10181                 var f = se[n];
10182
10183                 if (!f)
10184                     return;
10185
10186                 if (tinymce.is(f, 'string')) {
10187                     s = f.replace(/\.\w+$/, '');
10188                     s = s ? tinymce.resolve(s) : 0;
10189                     f = tinymce.resolve(f);
10190                 }
10191
10192                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
10193             };
10194
10195             s = extend({
10196                 theme : "simple",
69d05c 10197                 language : "en"
d9344f 10198             }, s);
S 10199
10200             t.settings = s;
10201
10202             // Legacy call
10203             Event.add(document, 'init', function() {
10204                 var l, co;
10205
10206                 execCallback(s, 'onpageload');
10207
10208                 switch (s.mode) {
10209                     case "exact":
10210                         l = s.elements || '';
10211
10212                         if(l.length > 0) {
10213                             each(explode(l), function(v) {
29da64 10214                                 if (DOM.get(v)) {
A 10215                                     ed = new tinymce.Editor(v, s);
10216                                     el.push(ed);
10217                                     ed.render(1);
10218                                 } else {
d9344f 10219                                     each(document.forms, function(f) {
S 10220                                         each(f.elements, function(e) {
10221                                             if (e.name === v) {
69d05c 10222                                                 v = 'mce_editor_' + instanceCounter++;
d9344f 10223                                                 DOM.setAttrib(e, 'id', v);
29da64 10224
A 10225                                                 ed = new tinymce.Editor(v, s);
10226                                                 el.push(ed);
10227                                                 ed.render(1);
d9344f 10228                                             }
S 10229                                         });
10230                                     });
10231                                 }
10232                             });
10233                         }
10234                         break;
10235
10236                     case "textareas":
10237                     case "specific_textareas":
10238                         function hasClass(n, c) {
18240a 10239                             return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
d9344f 10240                         };
S 10241
10242                         each(DOM.select('textarea'), function(v) {
10243                             if (s.editor_deselector && hasClass(v, s.editor_deselector))
10244                                 return;
10245
10246                             if (!s.editor_selector || hasClass(v, s.editor_selector)) {
18240a 10247                                 // Can we use the name
A 10248                                 e = DOM.get(v.name);
10249                                 if (!v.id && !e)
10250                                     v.id = v.name;
d9344f 10251
S 10252                                 // Generate unique name if missing or already exists
10253                                 if (!v.id || t.get(v.id))
10254                                     v.id = DOM.uniqueId();
10255
29da64 10256                                 ed = new tinymce.Editor(v.id, s);
A 10257                                 el.push(ed);
10258                                 ed.render(1);
d9344f 10259                             }
S 10260                         });
10261                         break;
10262                 }
10263
10264                 // Call onInit when all editors are initialized
10265                 if (s.oninit) {
10266                     l = co = 0;
10267
69d05c 10268                     each(el, function(ed) {
d9344f 10269                         co++;
S 10270
10271                         if (!ed.initialized) {
10272                             // Wait for it
10273                             ed.onInit.add(function() {
10274                                 l++;
10275
10276                                 // All done
10277                                 if (l == co)
10278                                     execCallback(s, 'oninit');
10279                             });
10280                         } else
10281                             l++;
10282
10283                         // All done
10284                         if (l == co)
10285                             execCallback(s, 'oninit');                    
10286                     });
10287                 }
10288             });
10289         },
10290
10291         get : function(id) {
69d05c 10292             if (id === undefined)
A 10293                 return this.editors;
10294
d9344f 10295             return this.editors[id];
S 10296         },
10297
10298         getInstanceById : function(id) {
10299             return this.get(id);
10300         },
10301
69d05c 10302         add : function(editor) {
A 10303             var self = this, editors = self.editors;
d9344f 10304
69d05c 10305             // Add named and index editor instance
A 10306             editors[editor.id] = editor;
10307             editors.push(editor);
10308
10309             self._setActive(editor);
10310             self.onAddEditor.dispatch(self, editor);
10311
10312
10313             return editor;
d9344f 10314         },
S 10315
69d05c 10316         remove : function(editor) {
A 10317             var t = this, i, editors = t.editors;
d9344f 10318
S 10319             // Not in the collection
69d05c 10320             if (!editors[editor.id])
d9344f 10321                 return null;
S 10322
69d05c 10323             delete editors[editor.id];
d9344f 10324
69d05c 10325             for (i = 0; i < editors.length; i++) {
A 10326                 if (editors[i] == editor) {
10327                     editors.splice(i, 1);
10328                     break;
10329                 }
d9344f 10330             }
S 10331
69d05c 10332             // Select another editor since the active one was removed
A 10333             if (t.activeEditor == editor)
10334                 t._setActive(editors[0]);
d9344f 10335
69d05c 10336             editor.destroy();
A 10337             t.onRemoveEditor.dispatch(t, editor);
10338
10339             return editor;
d9344f 10340         },
S 10341
10342         execCommand : function(c, u, v) {
10343             var t = this, ed = t.get(v), w;
10344
10345             // Manager commands
10346             switch (c) {
10347                 case "mceFocus":
10348                     ed.focus();
10349                     return true;
10350
10351                 case "mceAddEditor":
10352                 case "mceAddControl":
18240a 10353                     if (!t.get(v))
A 10354                         new tinymce.Editor(v, t.settings).render();
10355
d9344f 10356                     return true;
S 10357
10358                 case "mceAddFrameControl":
10359                     w = v.window;
10360
10361                     // Add tinyMCE global instance and tinymce namespace to specified window
10362                     w.tinyMCE = tinyMCE;
10363                     w.tinymce = tinymce;
10364
10365                     tinymce.DOM.doc = w.document;
10366                     tinymce.DOM.win = w;
10367
10368                     ed = new tinymce.Editor(v.element_id, v);
10369                     ed.render();
10370
10371                     // Fix IE memory leaks
10372                     if (tinymce.isIE) {
10373                         function clr() {
10374                             ed.destroy();
10375                             w.detachEvent('onunload', clr);
10376                             w = w.tinyMCE = w.tinymce = null; // IE leak
10377                         };
10378
10379                         w.attachEvent('onunload', clr);
10380                     }
10381
10382                     v.page_window = null;
10383
10384                     return true;
10385
10386                 case "mceRemoveEditor":
10387                 case "mceRemoveControl":
29da64 10388                     if (ed)
A 10389                         ed.remove();
10390
d9344f 10391                     return true;
S 10392
10393                 case 'mceToggleEditor':
10394                     if (!ed) {
10395                         t.execCommand('mceAddControl', 0, v);
10396                         return true;
10397                     }
10398
10399                     if (ed.isHidden())
10400                         ed.show();
10401                     else
10402                         ed.hide();
10403
10404                     return true;
10405             }
10406
10407             // Run command on active editor
10408             if (t.activeEditor)
10409                 return t.activeEditor.execCommand(c, u, v);
10410
10411             return false;
10412         },
10413
10414         execInstanceCommand : function(id, c, u, v) {
10415             var ed = this.get(id);
10416
10417             if (ed)
10418                 return ed.execCommand(c, u, v);
10419
10420             return false;
10421         },
10422
10423         triggerSave : function() {
10424             each(this.editors, function(e) {
10425                 e.save();
10426             });
10427         },
10428
10429         addI18n : function(p, o) {
10430             var lo, i18n = this.i18n;
10431
10432             if (!tinymce.is(p, 'string')) {
10433                 each(p, function(o, lc) {
10434                     each(o, function(o, g) {
10435                         each(o, function(o, k) {
10436                             if (g === 'common')
10437                                 i18n[lc + '.' + k] = o;
10438                             else
10439                                 i18n[lc + '.' + g + '.' + k] = o;
10440                         });
10441                     });
10442                 });
10443             } else {
10444                 each(o, function(o, k) {
10445                     i18n[p + '.' + k] = o;
10446                 });
10447             }
10448         },
10449
10450         // Private methods
10451
69d05c 10452         _setActive : function(editor) {
A 10453             this.selectedInstance = this.activeEditor = editor;
d9344f 10454         }
58fb65 10455     });
29da64 10456 })(tinymce);
d9344f 10457
29da64 10458 (function(tinymce) {
69d05c 10459     // Shorten these names
A 10460     var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
10461         Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10462         isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10463         ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10464         inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
d9344f 10465
S 10466     tinymce.create('tinymce.Editor', {
10467         Editor : function(id, s) {
10468             var t = this;
10469
10470             t.id = t.editorId = id;
58fb65 10471
d9344f 10472             t.execCommands = {};
S 10473             t.queryStateCommands = {};
10474             t.queryValueCommands = {};
58fb65 10475
A 10476             t.isNotDirty = false;
10477
d9344f 10478             t.plugins = {};
S 10479
10480             // Add events to the editor
10481             each([
10482                 'onPreInit',
58fb65 10483
d9344f 10484                 'onBeforeRenderUI',
58fb65 10485
d9344f 10486                 'onPostRender',
58fb65 10487
d9344f 10488                 'onInit',
58fb65 10489
d9344f 10490                 'onRemove',
58fb65 10491
d9344f 10492                 'onActivate',
58fb65 10493
d9344f 10494                 'onDeactivate',
58fb65 10495
d9344f 10496                 'onClick',
58fb65 10497
d9344f 10498                 'onEvent',
58fb65 10499
d9344f 10500                 'onMouseUp',
58fb65 10501
d9344f 10502                 'onMouseDown',
58fb65 10503
d9344f 10504                 'onDblClick',
58fb65 10505
d9344f 10506                 'onKeyDown',
58fb65 10507
d9344f 10508                 'onKeyUp',
58fb65 10509
d9344f 10510                 'onKeyPress',
58fb65 10511
d9344f 10512                 'onContextMenu',
58fb65 10513
d9344f 10514                 'onSubmit',
58fb65 10515
d9344f 10516                 'onReset',
58fb65 10517
d9344f 10518                 'onPaste',
58fb65 10519
d9344f 10520                 'onPreProcess',
58fb65 10521
d9344f 10522                 'onPostProcess',
58fb65 10523
d9344f 10524                 'onBeforeSetContent',
58fb65 10525
d9344f 10526                 'onBeforeGetContent',
58fb65 10527
d9344f 10528                 'onSetContent',
58fb65 10529
d9344f 10530                 'onGetContent',
58fb65 10531
d9344f 10532                 'onLoadContent',
58fb65 10533
d9344f 10534                 'onSaveContent',
58fb65 10535
d9344f 10536                 'onNodeChange',
58fb65 10537
d9344f 10538                 'onChange',
58fb65 10539
d9344f 10540                 'onBeforeExecCommand',
58fb65 10541
d9344f 10542                 'onExecCommand',
58fb65 10543
d9344f 10544                 'onUndo',
58fb65 10545
d9344f 10546                 'onRedo',
58fb65 10547
d9344f 10548                 'onVisualAid',
58fb65 10549
d9344f 10550                 'onSetProgressState'
S 10551             ], function(e) {
10552                 t[e] = new Dispatcher(t);
10553             });
10554
10555             t.settings = s = extend({
10556                 id : id,
10557                 language : 'en',
10558                 docs_language : 'en',
10559                 theme : 'simple',
10560                 skin : 'default',
10561                 delta_width : 0,
10562                 delta_height : 0,
10563                 popup_css : '',
10564                 plugins : '',
10565                 document_base_url : tinymce.documentBaseURL,
10566                 add_form_submit_trigger : 1,
10567                 submit_patch : 1,
10568                 add_unload_trigger : 1,
10569                 convert_urls : 1,
10570                 relative_urls : 1,
10571                 remove_script_host : 1,
10572                 table_inline_editing : 0,
10573                 object_resizing : 1,
10574                 cleanup : 1,
10575                 accessibility_focus : 1,
10576                 custom_shortcuts : 1,
10577                 custom_undo_redo_keyboard_shortcuts : 1,
10578                 custom_undo_redo_restore_selection : 1,
10579                 custom_undo_redo : 1,
69d05c 10580                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
d9344f 10581                 visual_table_class : 'mceItemTable',
S 10582                 visual : 1,
10583                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
10584                 apply_source_formatting : 1,
10585                 directionality : 'ltr',
10586                 forced_root_block : 'p',
10587                 hidden_input : 1,
10588                 padd_empty_editor : 1,
10589                 render_ui : 1,
10590                 init_theme : 1,
10591                 force_p_newlines : 1,
29da64 10592                 indentation : '30px',
A 10593                 keep_styles : 1,
10594                 fix_table_elements : 1,
69d05c 10595                 inline_styles : 1,
a9251b 10596                 convert_fonts_to_spans : true,
T 10597                 indent : 'simple',
10598                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10599                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10600                 validate : true,
10601                 entity_encoding : 'named',
10602                 url_converter : t.convertURL,
10603                 url_converter_scope : t,
10604                 ie7_compat : true
d9344f 10605             }, s);
S 10606
10607             t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
10608                 base_uri : tinyMCE.baseURI
10609             });
58fb65 10610
69d05c 10611             t.baseURI = tinymce.baseURI;
a9251b 10612
T 10613             t.contentCSS = [];
d9344f 10614
S 10615             // Call setup
10616             t.execCallback('setup', t);
10617         },
10618
10619         render : function(nst) {
10620             var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
10621
10622             // Page is not loaded yet, wait for it
10623             if (!Event.domLoaded) {
10624                 Event.add(document, 'init', function() {
10625                     t.render();
10626                 });
10627                 return;
10628             }
10629
69d05c 10630             tinyMCE.settings = s;
d9344f 10631
S 10632             // Element not found, then skip initialization
10633             if (!t.getElement())
10634                 return;
10635
2011be 10636             // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
A 10637             // browser says it has contentEditable support but there is no visible caret
10638             // We will remove this check ones Apple implements full contentEditable support
10639             if (tinymce.isIDevice)
10640                 return;
10641
d9344f 10642             // Add hidden input for non input elements inside form elements
S 10643             if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
10644                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
10645
29da64 10646             if (tinymce.WindowManager)
A 10647                 t.windowManager = new tinymce.WindowManager(t);
d9344f 10648
S 10649             if (s.encoding == 'xml') {
10650                 t.onGetContent.add(function(ed, o) {
18240a 10651                     if (o.save)
d9344f 10652                         o.content = DOM.encode(o.content);
S 10653                 });
10654             }
10655
10656             if (s.add_form_submit_trigger) {
10657                 t.onSubmit.addToTop(function() {
10658                     if (t.initialized) {
10659                         t.save();
10660                         t.isNotDirty = 1;
10661                     }
10662                 });
10663             }
10664
29da64 10665             if (s.add_unload_trigger) {
d9344f 10666                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
18240a 10667                     if (t.initialized && !t.destroyed && !t.isHidden())
d9344f 10668                         t.save({format : 'raw', no_events : true});
S 10669                 });
10670             }
10671
10672             tinymce.addUnload(t.destroy, t);
10673
10674             if (s.submit_patch) {
10675                 t.onBeforeRenderUI.add(function() {
10676                     var n = t.getElement().form;
10677
10678                     if (!n)
10679                         return;
10680
10681                     // Already patched
10682                     if (n._mceOldSubmit)
10683                         return;
10684
10685                     // Check page uses id="submit" or name="submit" for it's submit button
10686                     if (!n.submit.nodeType && !n.submit.length) {
10687                         t.formElement = n;
10688                         n._mceOldSubmit = n.submit;
10689                         n.submit = function() {
10690                             // Save all instances
69d05c 10691                             tinymce.triggerSave();
d9344f 10692                             t.isNotDirty = 1;
S 10693
29da64 10694                             return t.formElement._mceOldSubmit(t.formElement);
d9344f 10695                         };
S 10696                     }
10697
10698                     n = null;
10699                 });
10700             }
10701
10702             // Load scripts
10703             function loadScripts() {
a9251b 10704                 if (s.language && s.language_load !== false)
d9344f 10705                     sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
S 10706
29da64 10707                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
d9344f 10708                     ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
S 10709
10710                 each(explode(s.plugins), function(p) {
10711                     if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
69d05c 10712                         // Skip safari plugin, since it is removed as of 3.3b1
A 10713                         if (p == 'safari')
d9344f 10714                             return;
S 10715
10716                         PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
10717                     }
10718                 });
10719
10720                 // Init when que is loaded
10721                 sl.loadQueue(function() {
10722                     if (!t.removed)
10723                         t.init();
10724                 });
10725             };
10726
58fb65 10727             loadScripts();
d9344f 10728         },
S 10729
10730         init : function() {
a9251b 10731             var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i;
d9344f 10732
69d05c 10733             tinymce.add(t);
a9251b 10734
T 10735             s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
d9344f 10736
29da64 10737             if (s.theme) {
A 10738                 s.theme = s.theme.replace(/-/, '');
10739                 o = ThemeManager.get(s.theme);
10740                 t.theme = new o();
d9344f 10741
29da64 10742                 if (t.theme.init && s.init_theme)
A 10743                     t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
10744             }
d9344f 10745
S 10746             // Create all plugins
10747             each(explode(s.plugins.replace(/\-/g, '')), function(p) {
10748                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
10749
10750                 if (c) {
10751                     po = new c(t, u);
10752
10753                     t.plugins[p] = po;
10754
10755                     if (po.init)
10756                         po.init(t, u);
10757                 }
10758             });
10759
10760             // Setup popup CSS path(s)
29da64 10761             if (s.popup_css !== false) {
A 10762                 if (s.popup_css)
10763                     s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
10764                 else
10765                     s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
10766             }
d9344f 10767
S 10768             if (s.popup_css_add)
10769                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
10770
10771             t.controlManager = new tinymce.ControlManager(t);
58fb65 10772
d9344f 10773             if (s.custom_undo_redo) {
69d05c 10774                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
a9251b 10775                     if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
T 10776                         t.undoManager.beforeChange();
69d05c 10777                 });
A 10778
d9344f 10779                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
S 10780                     if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10781                         t.undoManager.add();
10782                 });
10783             }
10784
10785             t.onExecCommand.add(function(ed, c) {
10786                 // Don't refresh the select lists until caret move
10787                 if (!/^(FontName|FontSize)$/.test(c))
10788                     t.nodeChanged();
10789             });
10790
10791             // Remove ghost selections on images and tables in Gecko
10792             if (isGecko) {
10793                 function repaint(a, o) {
10794                     if (!o || !o.initial)
10795                         t.execCommand('mceRepaint');
10796                 };
10797
10798                 t.onUndo.add(repaint);
10799                 t.onRedo.add(repaint);
10800                 t.onSetContent.add(repaint);
10801             }
10802
10803             // Enables users to override the control factory
10804             t.onBeforeRenderUI.dispatch(t, t.controlManager);
10805
10806             // Measure box
10807             if (s.render_ui) {
18240a 10808                 w = s.width || e.style.width || e.offsetWidth;
A 10809                 h = s.height || e.style.height || e.offsetHeight;
d9344f 10810                 t.orgDisplay = e.style.display;
S 10811                 re = /^[0-9\.]+(|px)$/i;
10812
10813                 if (re.test('' + w))
10814                     w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
10815
10816                 if (re.test('' + h))
10817                     h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
10818
10819                 // Render UI
10820                 o = t.theme.renderUI({
10821                     targetNode : e,
10822                     width : w,
10823                     height : h,
10824                     deltaWidth : s.delta_width,
10825                     deltaHeight : s.delta_height
10826                 });
10827
10828                 t.editorContainer = o.editorContainer;
10829             }
10830
29da64 10831
58fb65 10832             // User specified a document.domain value
A 10833             if (document.domain && location.hostname != document.domain)
10834                 tinymce.relaxedDomain = document.domain;
10835
d9344f 10836             // Resize editor
S 10837             DOM.setStyles(o.sizeContainer || o.editorContainer, {
10838                 width : w,
10839                 height : h
10840             });
10841
a9251b 10842             // Load specified content CSS last
T 10843             if (s.content_css) {
10844                 tinymce.each(explode(s.content_css), function(u) {
10845                     t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
10846                 });
10847             }
10848
29da64 10849             h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
d9344f 10850             if (h < 100)
S 10851                 h = 100;
10852
58fb65 10853             t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
A 10854
10855             // We only need to override paths if we have to
10856             // IE has a bug where it remove site absolute urls to relative ones if this is specified
10857             if (s.document_base_url != tinymce.documentBaseURL)
10858                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
10859
a9251b 10860             // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
T 10861             if (s.ie7_compat)
10862                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
10863             else
10864                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
d9344f 10865
a9251b 10866             t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
T 10867
10868             // Firefox 2 doesn't load stylesheets correctly this way
10869             if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
10870                 for (i = 0; i < t.contentCSS.length; i++)
10871                     t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
10872
10873                 t.contentCSS = [];
10874             }
d9344f 10875
S 10876             bi = s.body_id || 'tinymce';
10877             if (bi.indexOf('=') != -1) {
10878                 bi = t.getParam('body_id', '', 'hash');
10879                 bi = bi[t.id] || bi;
10880             }
10881
10882             bc = s.body_class || '';
10883             if (bc.indexOf('=') != -1) {
10884                 bc = t.getParam('body_class', '', 'hash');
10885                 bc = bc[t.id] || '';
10886             }
10887
10888             t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
10889
10890             // Domain relaxing enabled, then set document domain
a9251b 10891             if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
d9344f 10892                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
a9251b 10893                 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';                
d9344f 10894             }
S 10895
10896             // Create iframe
a9251b 10897             // TODO: ACC add the appropriate description on this.
T 10898             n = DOM.add(o.iframeContainer, 'iframe', { 
d9344f 10899                 id : t.id + "_ifr",
S 10900                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
a9251b 10901                 frameBorder : '0', 
T 10902                 title : s.aria_label,
d9344f 10903                 style : {
S 10904                     width : '100%',
10905                     height : h
10906                 }
10907             });
10908
10909             t.contentAreaContainer = o.iframeContainer;
10910             DOM.get(o.editorContainer).style.display = t.orgDisplay;
10911             DOM.get(t.id).style.display = 'none';
a9251b 10912             DOM.setAttrib(t.id, 'aria-hidden', true);
d9344f 10913
a9251b 10914             if (!tinymce.relaxedDomain || !u)
29da64 10915                 t.setupIframe();
d9344f 10916
29da64 10917             e = n = o = null; // Cleanup
d9344f 10918         },
S 10919
10920         setupIframe : function() {
10921             var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
10922
10923             // Setup iframe body
10924             if (!isIE || !tinymce.relaxedDomain) {
10925                 d.open();
10926                 d.write(t.iframeHTML);
10927                 d.close();
a9251b 10928
T 10929                 if (tinymce.relaxedDomain)
10930                     d.domain = tinymce.relaxedDomain;
d9344f 10931             }
S 10932
10933             // Design mode needs to be added here Ctrl+A will fail otherwise
10934             if (!isIE) {
10935                 try {
29da64 10936                     if (!s.readonly)
A 10937                         d.designMode = 'On';
d9344f 10938                 } catch (ex) {
S 10939                     // Will fail on Gecko if the editor is placed in an hidden container element
10940                     // The design mode will be set ones the editor is focused
10941                 }
10942             }
10943
10944             // IE needs to use contentEditable or it will display non secure items for HTTPS
10945             if (isIE) {
10946                 // It will not steal focus if we hide it while setting contentEditable
10947                 b = t.getBody();
10948                 DOM.hide(b);
29da64 10949
A 10950                 if (!s.readonly)
10951                     b.contentEditable = true;
10952
d9344f 10953                 DOM.show(b);
S 10954             }
10955
a9251b 10956             t.schema = new tinymce.html.Schema(s);
T 10957
58fb65 10958             t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
d9344f 10959                 keep_values : true,
S 10960                 url_converter : t.convertURL,
10961                 url_converter_scope : t,
10962                 hex_colors : s.force_hex_style_colors,
10963                 class_filter : s.class_filter,
10964                 update_styles : 1,
69d05c 10965                 fix_ie_paragraphs : 1,
a9251b 10966                 schema : t.schema
d9344f 10967             });
69d05c 10968
a9251b 10969             t.parser = new tinymce.html.DomParser(s, t.schema);
d9344f 10970
a9251b 10971             // Force anchor names closed
T 10972             t.parser.addAttributeFilter('name', function(nodes, name) {
10973                 var i = nodes.length, sibling, prevSibling, parent, node;
10974
10975                 while (i--) {
10976                     node = nodes[i];
10977                     if (node.name === 'a' && node.firstChild) {
10978                         parent = node.parent;
10979
10980                         // Move children after current node
10981                         sibling = node.lastChild;
10982                         do {
10983                             prevSibling = sibling.prev;
10984                             parent.insert(sibling, node);
10985                             sibling = prevSibling;
10986                         } while (sibling);
10987                     }
10988                 }
10989             });
10990
10991             // Convert src and href into data-mce-src, data-mce-href and data-mce-style
10992             t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
10993                 var i = nodes.length, node, dom = t.dom, value;
10994
10995                 while (i--) {
10996                     node = nodes[i];
10997                     value = node.attr(name);
10998
10999                     if (name === "style")
11000                         node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name));
11001                     else
11002                         node.attr('data-mce-' + name, t.convertURL(value, name, node.name));
11003                 }
11004             });
11005
11006             // Keep scripts from executing
11007             t.parser.addNodeFilter('script', function(nodes, name) {
11008                 var i = nodes.length;
11009
11010                 while (i--)
11011                     nodes[i].attr('type', 'mce-text/javascript');
11012             });
11013
11014             t.parser.addNodeFilter('#cdata', function(nodes, name) {
11015                 var i = nodes.length, node;
11016
11017                 while (i--) {
11018                     node = nodes[i];
11019                     node.type = 8;
11020                     node.name = '#comment';
11021                     node.value = '[CDATA[' + node.value + ']]';
11022                 }
11023             });
11024
11025             t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
11026                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
11027
11028                 while (i--) {
11029                     node = nodes[i];
11030
11031                     if (node.isEmpty(nonEmptyElements))
11032                         node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
11033                 }
11034             });
11035
11036             t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
d9344f 11037
S 11038             t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
58fb65 11039
69d05c 11040             t.formatter = new tinymce.Formatter(this);
A 11041
11042             // Register default formats
11043             t.formatter.register({
11044                 alignleft : [
11045                     {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
a9251b 11046                     {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
69d05c 11047                 ],
A 11048
11049                 aligncenter : [
11050                     {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
a9251b 11051                     {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
T 11052                     {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
69d05c 11053                 ],
A 11054
11055                 alignright : [
11056                     {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
a9251b 11057                     {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
69d05c 11058                 ],
A 11059
11060                 alignfull : [
11061                     {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
11062                 ],
11063
11064                 bold : [
a9251b 11065                     {inline : 'strong', remove : 'all'},
69d05c 11066                     {inline : 'span', styles : {fontWeight : 'bold'}},
a9251b 11067                     {inline : 'b', remove : 'all'}
69d05c 11068                 ],
A 11069
11070                 italic : [
a9251b 11071                     {inline : 'em', remove : 'all'},
69d05c 11072                     {inline : 'span', styles : {fontStyle : 'italic'}},
a9251b 11073                     {inline : 'i', remove : 'all'}
69d05c 11074                 ],
A 11075
11076                 underline : [
11077                     {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
a9251b 11078                     {inline : 'u', remove : 'all'}
69d05c 11079                 ],
A 11080
11081                 strikethrough : [
11082                     {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
a9251b 11083                     {inline : 'strike', remove : 'all'}
69d05c 11084                 ],
A 11085
a9251b 11086                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
T 11087                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
69d05c 11088                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
A 11089                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
2011be 11090                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
799907 11091                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
a9251b 11092                 subscript : {inline : 'sub'},
T 11093                 superscript : {inline : 'sup'},
69d05c 11094
A 11095                 removeformat : [
11096                     {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
11097                     {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
11098                     {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
11099                 ]
11100             });
11101
11102             // Register default block formats
11103             each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
799907 11104                 t.formatter.register(name, {block : name, remove : 'all'});
69d05c 11105             });
A 11106
11107             // Register user defined formats
11108             t.formatter.register(t.settings.formats);
11109
11110             t.undoManager = new tinymce.UndoManager(t);
11111
11112             // Pass through
11113             t.undoManager.onAdd.add(function(um, l) {
a9251b 11114                 if (um.hasUndo())
69d05c 11115                     return t.onChange.dispatch(t, l, um);
A 11116             });
11117
11118             t.undoManager.onUndo.add(function(um, l) {
11119                 return t.onUndo.dispatch(t, l, um);
11120             });
11121
11122             t.undoManager.onRedo.add(function(um, l) {
11123                 return t.onRedo.dispatch(t, l, um);
11124             });
11125
d9344f 11126             t.forceBlocks = new tinymce.ForceBlocks(t, {
S 11127                 forced_root_block : s.forced_root_block
11128             });
69d05c 11129
d9344f 11130             t.editorCommands = new tinymce.EditorCommands(t);
S 11131
11132             // Pass through
11133             t.serializer.onPreProcess.add(function(se, o) {
11134                 return t.onPreProcess.dispatch(t, o, se);
11135             });
11136
11137             t.serializer.onPostProcess.add(function(se, o) {
11138                 return t.onPostProcess.dispatch(t, o, se);
11139             });
11140
11141             t.onPreInit.dispatch(t);
11142
11143             if (!s.gecko_spellcheck)
11144                 t.getBody().spellcheck = 0;
11145
29da64 11146             if (!s.readonly)
A 11147                 t._addEvents();
d9344f 11148
S 11149             t.controlManager.onPostRender.dispatch(t, t.controlManager);
11150             t.onPostRender.dispatch(t);
11151
11152             if (s.directionality)
11153                 t.getBody().dir = s.directionality;
11154
11155             if (s.nowrap)
11156                 t.getBody().style.whiteSpace = "nowrap";
11157
11158             if (s.handle_node_change_callback) {
11159                 t.onNodeChange.add(function(ed, cm, n) {
11160                     t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
11161                 });
11162             }
11163
11164             if (s.save_callback) {
11165                 t.onSaveContent.add(function(ed, o) {
11166                     var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
11167
11168                     if (h)
11169                         o.content = h;
11170                 });
11171             }
11172
11173             if (s.onchange_callback) {
11174                 t.onChange.add(function(ed, l) {
11175                     t.execCallback('onchange_callback', t, l);
11176                 });
11177             }
11178
a9251b 11179             if (s.protect) {
T 11180                 t.onBeforeSetContent.add(function(ed, o) {
11181                     if (s.protect) {
11182                         each(s.protect, function(pattern) {
11183                             o.content = o.content.replace(pattern, function(str) {
11184                                 return '<!--mce:protected ' + escape(str) + '-->';
11185                             });
11186                         });
11187                     }
11188                 });
11189             }
11190
d9344f 11191             if (s.convert_newlines_to_brs) {
S 11192                 t.onBeforeSetContent.add(function(ed, o) {
11193                     if (o.initial)
11194                         o.content = o.content.replace(/\r?\n/g, '<br />');
11195                 });
11196             }
11197
11198             if (s.preformatted) {
11199                 t.onPostProcess.add(function(ed, o) {
11200                     o.content = o.content.replace(/^\s*<pre.*?>/, '');
11201                     o.content = o.content.replace(/<\/pre>\s*$/, '');
11202
11203                     if (o.set)
11204                         o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
11205                 });
11206             }
11207
11208             if (s.verify_css_classes) {
11209                 t.serializer.attribValueFilter = function(n, v) {
11210                     var s, cl;
11211
11212                     if (n == 'class') {
11213                         // Build regexp for classes
11214                         if (!t.classesRE) {
11215                             cl = t.dom.getClasses();
11216
11217                             if (cl.length > 0) {
11218                                 s = '';
11219
11220                                 each (cl, function(o) {
11221                                     s += (s ? '|' : '') + o['class'];
11222                                 });
11223
11224                                 t.classesRE = new RegExp('(' + s + ')', 'gi');
11225                             }
11226                         }
11227
11228                         return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
11229                     }
11230
11231                     return v;
11232                 };
11233             }
11234
11235             if (s.cleanup_callback) {
11236                 t.onBeforeSetContent.add(function(ed, o) {
11237                     o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11238                 });
11239
11240                 t.onPreProcess.add(function(ed, o) {
11241                     if (o.set)
11242                         t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
11243
11244                     if (o.get)
11245                         t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
11246                 });
11247
11248                 t.onPostProcess.add(function(ed, o) {
11249                     if (o.set)
11250                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11251
11252                     if (o.get)                        
11253                         o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
11254                 });
11255             }
11256
11257             if (s.save_callback) {
11258                 t.onGetContent.add(function(ed, o) {
11259                     if (o.save)
11260                         o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
11261                 });
11262             }
11263
11264             if (s.handle_event_callback) {
11265                 t.onEvent.add(function(ed, e, o) {
11266                     if (t.execCallback('handle_event_callback', e, ed, o) === false)
11267                         Event.cancel(e);
11268                 });
11269             }
11270
29da64 11271             // Add visual aids when new contents is added
d9344f 11272             t.onSetContent.add(function() {
29da64 11273                 t.addVisual(t.getBody());
d9344f 11274             });
S 11275
11276             // Remove empty contents
11277             if (s.padd_empty_editor) {
11278                 t.onPostProcess.add(function(ed, o) {
29da64 11279                     o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
d9344f 11280                 });
S 11281             }
11282
11283             if (isGecko) {
29da64 11284                 // Fix gecko link bug, when a link is placed at the end of block elements there is
A 11285                 // no way to move the caret behind the link. This fix adds a bogus br element after the link
11286                 function fixLinks(ed, o) {
11287                     each(ed.dom.select('a'), function(n) {
11288                         var pn = n.parentNode;
11289
11290                         if (ed.dom.isBlock(pn) && pn.lastChild === n)
a9251b 11291                             ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
29da64 11292                     });
A 11293                 };
11294
11295                 t.onExecCommand.add(function(ed, cmd) {
11296                     if (cmd === 'CreateLink')
11297                         fixLinks(ed);
11298                 });
11299
11300                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
11301
11302                 if (!s.readonly) {
11303                     try {
11304                         // Design mode must be set here once again to fix a bug where
11305                         // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
11306                         d.designMode = 'Off';
11307                         d.designMode = 'On';
11308                     } catch (ex) {
11309                         // Will fail on Gecko if the editor is placed in an hidden container element
11310                         // The design mode will be set ones the editor is focused
11311                     }
d9344f 11312                 }
S 11313             }
11314
11315             // A small timeout was needed since firefox will remove. Bug: #1838304
11316             setTimeout(function () {
11317                 if (t.removed)
11318                     return;
11319
a9251b 11320                 t.load({initial : true, format : 'html'});
d9344f 11321                 t.startContent = t.getContent({format : 'raw'});
a9251b 11322                 t.undoManager.add();
d9344f 11323                 t.initialized = true;
S 11324
11325                 t.onInit.dispatch(t);
11326                 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
11327                 t.execCallback('init_instance_callback', t);
11328                 t.focus(true);
11329                 t.nodeChanged({initial : 1});
11330
11331                 // Load specified content CSS last
a9251b 11332                 each(t.contentCSS, function(u) {
T 11333                     t.dom.loadCSS(u);
11334                 });
d9344f 11335
S 11336                 // Handle auto focus
11337                 if (s.auto_focus) {
11338                     setTimeout(function () {
69d05c 11339                         var ed = tinymce.get(s.auto_focus);
d9344f 11340
S 11341                         ed.selection.select(ed.getBody(), 1);
11342                         ed.selection.collapse(1);
11343                         ed.getWin().focus();
11344                     }, 100);
11345                 }
11346             }, 1);
11347     
11348             e = null;
11349         },
11350
29da64 11351
d9344f 11352         focus : function(sf) {
2011be 11353             var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
d9344f 11354
S 11355             if (!sf) {
2011be 11356                 // Get selected control element
A 11357                 ieRng = t.selection.getRng();
11358                 if (ieRng.item) {
11359                     controlElm = ieRng.item(0);
11360                 }
11361
69d05c 11362                 // Is not content editable
A 11363                 if (!ce)
18240a 11364                     t.getWin().focus();
2011be 11365
A 11366                 // Restore selected control element
11367                 // This is needed when for example an image is selected within a
11368                 // layer a call to focus will then remove the control selection
11369                 if (controlElm && controlElm.ownerDocument == doc) {
11370                     ieRng = doc.body.createControlRange();
11371                     ieRng.addElement(controlElm);
11372                     ieRng.select();
11373                 }
d9344f 11374
29da64 11375             }
d9344f 11376
69d05c 11377             if (tinymce.activeEditor != t) {
A 11378                 if ((oed = tinymce.activeEditor) != null)
d9344f 11379                     oed.onDeactivate.dispatch(oed, t);
S 11380
11381                 t.onActivate.dispatch(t, oed);
11382             }
11383
69d05c 11384             tinymce._setActive(t);
d9344f 11385         },
S 11386
11387         execCallback : function(n) {
11388             var t = this, f = t.settings[n], s;
11389
11390             if (!f)
11391                 return;
11392
11393             // Look through lookup
11394             if (t.callbackLookup && (s = t.callbackLookup[n])) {
11395                 f = s.func;
11396                 s = s.scope;
11397             }
11398
11399             if (is(f, 'string')) {
11400                 s = f.replace(/\.\w+$/, '');
11401                 s = s ? tinymce.resolve(s) : 0;
11402                 f = tinymce.resolve(f);
11403                 t.callbackLookup = t.callbackLookup || {};
11404                 t.callbackLookup[n] = {func : f, scope : s};
11405             }
11406
11407             return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
11408         },
11409
11410         translate : function(s) {
69d05c 11411             var c = this.settings.language || 'en', i18n = tinymce.i18n;
d9344f 11412
S 11413             if (!s)
11414                 return '';
11415
11416             return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
11417                 return i18n[c + '.' + b] || '{#' + b + '}';
11418             });
11419         },
11420
11421         getLang : function(n, dv) {
69d05c 11422             return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
d9344f 11423         },
S 11424
11425         getParam : function(n, dv, ty) {
11426             var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
11427
11428             if (ty === 'hash') {
11429                 o = {};
11430
11431                 if (is(v, 'string')) {
11432                     each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
11433                         v = v.split('=');
11434
11435                         if (v.length > 1)
11436                             o[tr(v[0])] = tr(v[1]);
11437                         else
11438                             o[tr(v[0])] = tr(v);
11439                     });
11440                 } else
11441                     o = v;
11442
11443                 return o;
11444             }
11445
11446             return v;
11447         },
11448
11449         nodeChanged : function(o) {
a9251b 11450             var t = this, s = t.selection, n = s.getStart() || t.getBody();
d9344f 11451
S 11452             // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
11453             if (t.initialized) {
69d05c 11454                 o = o || {};
A 11455                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
11456
11457                 // Get parents and add them to object
11458                 o.parents = [];
11459                 t.dom.getParent(n, function(node) {
11460                     if (node.nodeName == 'BODY')
11461                         return true;
11462
11463                     o.parents.push(node);
11464                 });
11465
d9344f 11466                 t.onNodeChange.dispatch(
S 11467                     t,
11468                     o ? o.controlManager || t.controlManager : t.controlManager,
69d05c 11469                     n,
d9344f 11470                     s.isCollapsed(),
S 11471                     o
11472                 );
11473             }
11474         },
11475
11476         addButton : function(n, s) {
11477             var t = this;
11478
11479             t.buttons = t.buttons || {};
11480             t.buttons[n] = s;
11481         },
11482
a9251b 11483         addCommand : function(name, callback, scope) {
T 11484             this.execCommands[name] = {func : callback, scope : scope || this};
d9344f 11485         },
S 11486
a9251b 11487         addQueryStateHandler : function(name, callback, scope) {
T 11488             this.queryStateCommands[name] = {func : callback, scope : scope || this};
d9344f 11489         },
S 11490
a9251b 11491         addQueryValueHandler : function(name, callback, scope) {
T 11492             this.queryValueCommands[name] = {func : callback, scope : scope || this};
d9344f 11493         },
S 11494
11495         addShortcut : function(pa, desc, cmd_func, sc) {
11496             var t = this, c;
11497
11498             if (!t.settings.custom_shortcuts)
11499                 return false;
11500
11501             t.shortcuts = t.shortcuts || {};
11502
11503             if (is(cmd_func, 'string')) {
11504                 c = cmd_func;
11505
11506                 cmd_func = function() {
11507                     t.execCommand(c, false, null);
11508                 };
11509             }
11510
11511             if (is(cmd_func, 'object')) {
11512                 c = cmd_func;
11513
11514                 cmd_func = function() {
11515                     t.execCommand(c[0], c[1], c[2]);
11516                 };
11517             }
11518
11519             each(explode(pa), function(pa) {
11520                 var o = {
11521                     func : cmd_func,
11522                     scope : sc || this,
11523                     desc : desc,
11524                     alt : false,
11525                     ctrl : false,
11526                     shift : false
11527                 };
11528
11529                 each(explode(pa, '+'), function(v) {
11530                     switch (v) {
11531                         case 'alt':
11532                         case 'ctrl':
11533                         case 'shift':
11534                             o[v] = true;
11535                             break;
11536
11537                         default:
11538                             o.charCode = v.charCodeAt(0);
11539                             o.keyCode = v.toUpperCase().charCodeAt(0);
11540                     }
11541                 });
11542
11543                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
11544             });
11545
11546             return true;
11547         },
11548
11549         execCommand : function(cmd, ui, val, a) {
18240a 11550             var t = this, s = 0, o, st;
d9344f 11551
S 11552             if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
11553                 t.focus();
11554
11555             o = {};
11556             t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
11557             if (o.terminate)
11558                 return false;
11559
18240a 11560             // Command callback
d9344f 11561             if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
S 11562                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11563                 return true;
11564             }
11565
11566             // Registred commands
11567             if (o = t.execCommands[cmd]) {
18240a 11568                 st = o.func.call(o.scope, ui, val);
A 11569
11570                 // Fall through on true
11571                 if (st !== true) {
11572                     t.onExecCommand.dispatch(t, cmd, ui, val, a);
11573                     return st;
11574                 }
d9344f 11575             }
S 11576
11577             // Plugin commands
11578             each(t.plugins, function(p) {
11579                 if (p.execCommand && p.execCommand(cmd, ui, val)) {
11580                     t.onExecCommand.dispatch(t, cmd, ui, val, a);
11581                     s = 1;
11582                     return false;
11583                 }
11584             });
11585
11586             if (s)
11587                 return true;
11588
11589             // Theme commands
29da64 11590             if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
d9344f 11591                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
S 11592                 return true;
11593             }
11594
11595             // Editor commands
11596             if (t.editorCommands.execCommand(cmd, ui, val)) {
11597                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
11598                 return true;
11599             }
11600
11601             // Browser commands
11602             t.getDoc().execCommand(cmd, ui, val);
11603             t.onExecCommand.dispatch(t, cmd, ui, val, a);
11604         },
11605
69d05c 11606         queryCommandState : function(cmd) {
18240a 11607             var t = this, o, s;
d9344f 11608
S 11609             // Is hidden then return undefined
11610             if (t._isHidden())
11611                 return;
11612
11613             // Registred commands
69d05c 11614             if (o = t.queryStateCommands[cmd]) {
18240a 11615                 s = o.func.call(o.scope);
A 11616
11617                 // Fall though on true
11618                 if (s !== true)
11619                     return s;
11620             }
d9344f 11621
S 11622             // Registred commands
69d05c 11623             o = t.editorCommands.queryCommandState(cmd);
d9344f 11624             if (o !== -1)
S 11625                 return o;
11626
11627             // Browser commands
11628             try {
69d05c 11629                 return this.getDoc().queryCommandState(cmd);
d9344f 11630             } catch (ex) {
S 11631                 // Fails sometimes see bug: 1896577
11632             }
11633         },
11634
11635         queryCommandValue : function(c) {
18240a 11636             var t = this, o, s;
d9344f 11637
S 11638             // Is hidden then return undefined
11639             if (t._isHidden())
11640                 return;
11641
11642             // Registred commands
18240a 11643             if (o = t.queryValueCommands[c]) {
A 11644                 s = o.func.call(o.scope);
11645
11646                 // Fall though on true
11647                 if (s !== true)
11648                     return s;
11649             }
d9344f 11650
S 11651             // Registred commands
11652             o = t.editorCommands.queryCommandValue(c);
11653             if (is(o))
11654                 return o;
11655
11656             // Browser commands
11657             try {
11658                 return this.getDoc().queryCommandValue(c);
11659             } catch (ex) {
11660                 // Fails sometimes see bug: 1896577
11661             }
11662         },
11663
11664         show : function() {
11665             var t = this;
11666
11667             DOM.show(t.getContainer());
11668             DOM.hide(t.id);
11669             t.load();
11670         },
11671
11672         hide : function() {
11673             var t = this, d = t.getDoc();
11674
11675             // Fixed bug where IE has a blinking cursor left from the editor
11676             if (isIE && d)
11677                 d.execCommand('SelectAll');
11678
11679             // We must save before we hide so Safari doesn't crash
11680             t.save();
11681             DOM.hide(t.getContainer());
11682             DOM.setStyle(t.id, 'display', t.orgDisplay);
11683         },
11684
11685         isHidden : function() {
11686             return !DOM.isHidden(this.id);
11687         },
11688
11689         setProgressState : function(b, ti, o) {
11690             this.onSetProgressState.dispatch(this, b, ti, o);
11691
11692             return b;
11693         },
11694
11695         load : function(o) {
11696             var t = this, e = t.getElement(), h;
11697
29da64 11698             if (e) {
A 11699                 o = o || {};
11700                 o.load = true;
d9344f 11701
29da64 11702                 // Double encode existing entities in the value
A 11703                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
11704                 o.element = e;
d9344f 11705
29da64 11706                 if (!o.no_events)
A 11707                     t.onLoadContent.dispatch(t, o);
d9344f 11708
29da64 11709                 o.element = e = null;
d9344f 11710
29da64 11711                 return h;
A 11712             }
d9344f 11713         },
S 11714
11715         save : function(o) {
11716             var t = this, e = t.getElement(), h, f;
11717
29da64 11718             if (!e || !t.initialized)
d9344f 11719                 return;
S 11720
11721             o = o || {};
11722             o.save = true;
11723
18240a 11724             // Add undo level will trigger onchange event
A 11725             if (!o.no_events) {
a9251b 11726                 t.undoManager.typing = false;
18240a 11727                 t.undoManager.add();
A 11728             }
11729
d9344f 11730             o.element = e;
S 11731             h = o.content = t.getContent(o);
11732
11733             if (!o.no_events)
11734                 t.onSaveContent.dispatch(t, o);
11735
11736             h = o.content;
11737
11738             if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
11739                 e.innerHTML = h;
11740
11741                 // Update hidden form element
11742                 if (f = DOM.getParent(t.id, 'form')) {
11743                     each(f.elements, function(e) {
11744                         if (e.name == t.id) {
11745                             e.value = h;
11746                             return false;
11747                         }
11748                     });
11749                 }
11750             } else
11751                 e.value = h;
11752
11753             o.element = e = null;
11754
11755             return h;
11756         },
11757
a9251b 11758         setContent : function(content, args) {
T 11759             var self = this, rootNode, body = self.getBody();
d9344f 11760
a9251b 11761             // Setup args object
T 11762             args = args || {};
11763             args.format = args.format || 'html';
11764             args.set = true;
11765             args.content = content;
d9344f 11766
a9251b 11767             // Do preprocessing
T 11768             if (!args.no_events)
11769                 self.onBeforeSetContent.dispatch(self, args);
11770
11771             content = args.content;
d9344f 11772
S 11773             // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
11774             // It will also be impossible to place the caret in the editor unless there is a BR element present
a9251b 11775             if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
T 11776                 body.innerHTML = '<br data-mce-bogus="1" />';
11777                 return;
d9344f 11778             }
S 11779
a9251b 11780             // Parse and serialize the html
T 11781             if (args.format !== 'raw') {
11782                 content = new tinymce.html.Serializer({}, self.schema).serialize(
11783                     self.parser.parse(content)
11784                 );
d9344f 11785             }
S 11786
a9251b 11787             // Set the new cleaned contents to the editor
T 11788             args.content = tinymce.trim(content);
11789             self.dom.setHTML(body, args.content);
d9344f 11790
a9251b 11791             // Do post processing
T 11792             if (!args.no_events)
11793                 self.onSetContent.dispatch(self, args);
11794
11795             return args.content;
d9344f 11796         },
S 11797
a9251b 11798         getContent : function(args) {
T 11799             var self = this, content;
d9344f 11800
a9251b 11801             // Setup args object
T 11802             args = args || {};
11803             args.format = args.format || 'html';
11804             args.get = true;
d9344f 11805
a9251b 11806             // Do preprocessing
T 11807             if (!args.no_events)
11808                 self.onBeforeGetContent.dispatch(self, args);
d9344f 11809
a9251b 11810             // Get raw contents or by default the cleaned contents
T 11811             if (args.format == 'raw')
11812                 content = self.getBody().innerHTML;
11813             else
11814                 content = self.serializer.serialize(self.getBody(), args);
d9344f 11815
a9251b 11816             args.content = tinymce.trim(content);
18240a 11817
a9251b 11818             // Do post processing
T 11819             if (!args.no_events)
11820                 self.onGetContent.dispatch(self, args);
d9344f 11821
a9251b 11822             return args.content;
d9344f 11823         },
S 11824
11825         isDirty : function() {
a9251b 11826             var self = this;
d9344f 11827
a9251b 11828             return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
d9344f 11829         },
S 11830
11831         getContainer : function() {
11832             var t = this;
11833
11834             if (!t.container)
11835                 t.container = DOM.get(t.editorContainer || t.id + '_parent');
11836
11837             return t.container;
11838         },
11839
11840         getContentAreaContainer : function() {
11841             return this.contentAreaContainer;
11842         },
11843
11844         getElement : function() {
11845             return DOM.get(this.settings.content_element || this.id);
11846         },
11847
11848         getWin : function() {
11849             var t = this, e;
11850
11851             if (!t.contentWindow) {
11852                 e = DOM.get(t.id + "_ifr");
11853
11854                 if (e)
11855                     t.contentWindow = e.contentWindow;
11856             }
11857
11858             return t.contentWindow;
11859         },
11860
11861         getDoc : function() {
11862             var t = this, w;
11863
11864             if (!t.contentDocument) {
11865                 w = t.getWin();
11866
11867                 if (w)
11868                     t.contentDocument = w.document;
11869             }
11870
11871             return t.contentDocument;
11872         },
11873
11874         getBody : function() {
11875             return this.bodyElement || this.getDoc().body;
11876         },
11877
11878         convertURL : function(u, n, e) {
11879             var t = this, s = t.settings;
11880
11881             // Use callback instead
11882             if (s.urlconverter_callback)
11883                 return t.execCallback('urlconverter_callback', u, e, true, n);
11884
11885             // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
11886             if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
11887                 return u;
11888
11889             // Convert to relative
11890             if (s.relative_urls)
11891                 return t.documentBaseURI.toRelative(u);
11892
11893             // Convert to absolute
11894             u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
11895
11896             return u;
11897         },
11898
11899         addVisual : function(e) {
11900             var t = this, s = t.settings;
11901
11902             e = e || t.getBody();
11903
11904             if (!is(t.hasVisual))
11905                 t.hasVisual = s.visual;
11906
11907             each(t.dom.select('table,a', e), function(e) {
11908                 var v;
11909
11910                 switch (e.nodeName) {
11911                     case 'TABLE':
11912                         v = t.dom.getAttrib(e, 'border');
11913
11914                         if (!v || v == '0') {
11915                             if (t.hasVisual)
11916                                 t.dom.addClass(e, s.visual_table_class);
11917                             else
11918                                 t.dom.removeClass(e, s.visual_table_class);
11919                         }
11920
11921                         return;
11922
11923                     case 'A':
11924                         v = t.dom.getAttrib(e, 'name');
11925
11926                         if (v) {
11927                             if (t.hasVisual)
11928                                 t.dom.addClass(e, 'mceItemAnchor');
11929                             else
11930                                 t.dom.removeClass(e, 'mceItemAnchor');
11931                         }
11932
11933                         return;
11934                 }
11935             });
11936
11937             t.onVisualAid.dispatch(t, e, t.hasVisual);
11938         },
11939
11940         remove : function() {
11941             var t = this, e = t.getContainer();
11942
11943             t.removed = 1; // Cancels post remove event execution
11944             t.hide();
11945
11946             t.execCallback('remove_instance_callback', t);
11947             t.onRemove.dispatch(t);
11948
11949             // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
11950             t.onExecCommand.listeners = [];
11951
69d05c 11952             tinymce.remove(t);
d9344f 11953             DOM.remove(e);
S 11954         },
11955
11956         destroy : function(s) {
11957             var t = this;
11958
11959             // One time is enough
11960             if (t.destroyed)
11961                 return;
11962
11963             if (!s) {
11964                 tinymce.removeUnload(t.destroy);
11965                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
11966
11967                 // Manual destroy
29da64 11968                 if (t.theme && t.theme.destroy)
d9344f 11969                     t.theme.destroy();
S 11970
11971                 // Destroy controls, selection and dom
11972                 t.controlManager.destroy();
11973                 t.selection.destroy();
11974                 t.dom.destroy();
11975
11976                 // Remove all events
18240a 11977
A 11978                 // Don't clear the window or document if content editable
11979                 // is enabled since other instances might still be present
11980                 if (!t.settings.content_editable) {
11981                     Event.clear(t.getWin());
11982                     Event.clear(t.getDoc());
11983                 }
11984
d9344f 11985                 Event.clear(t.getBody());
S 11986                 Event.clear(t.formElement);
11987             }
11988
11989             if (t.formElement) {
11990                 t.formElement.submit = t.formElement._mceOldSubmit;
11991                 t.formElement._mceOldSubmit = null;
11992             }
11993
11994             t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
11995
11996             if (t.selection)
11997                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
11998
11999             t.destroyed = 1;
12000         },
12001
12002         // Internal functions
12003
12004         _addEvents : function() {
12005             // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
a9251b 12006             var t = this, i, s = t.settings, dom = t.dom, lo = {
d9344f 12007                 mouseup : 'onMouseUp',
S 12008                 mousedown : 'onMouseDown',
12009                 click : 'onClick',
12010                 keyup : 'onKeyUp',
12011                 keydown : 'onKeyDown',
12012                 keypress : 'onKeyPress',
12013                 submit : 'onSubmit',
12014                 reset : 'onReset',
12015                 contextmenu : 'onContextMenu',
12016                 dblclick : 'onDblClick',
12017                 paste : 'onPaste' // Doesn't work in all browsers yet
12018             };
12019
12020             function eventHandler(e, o) {
12021                 var ty = e.type;
12022
12023                 // Don't fire events when it's removed
12024                 if (t.removed)
12025                     return;
12026
12027                 // Generic event handler
12028                 if (t.onEvent.dispatch(t, e, o) !== false) {
12029                     // Specific event handler
12030                     t[lo[e.fakeType || e.type]].dispatch(t, e, o);
12031                 }
12032             };
12033
12034             // Add DOM events
12035             each(lo, function(v, k) {
12036                 switch (k) {
12037                     case 'contextmenu':
a9251b 12038                         dom.bind(t.getDoc(), k, eventHandler);
d9344f 12039                         break;
S 12040
12041                     case 'paste':
a9251b 12042                         dom.bind(t.getBody(), k, function(e) {
29da64 12043                             eventHandler(e);
d9344f 12044                         });
S 12045                         break;
12046
12047                     case 'submit':
12048                     case 'reset':
a9251b 12049                         dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
d9344f 12050                         break;
S 12051
12052                     default:
a9251b 12053                         dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
d9344f 12054                 }
S 12055             });
12056
a9251b 12057             dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
d9344f 12058                 t.focus(true);
S 12059             });
12060
29da64 12061
d9344f 12062             // Fixes bug where a specified document_base_uri could result in broken images
S 12063             // This will also fix drag drop of images in Gecko
12064             if (tinymce.isGecko) {
a9251b 12065                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
d9344f 12066                     var v;
S 12067
12068                     e = e.target;
12069
a9251b 12070                     if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
d9344f 12071                         e.src = t.documentBaseURI.toAbsolute(v);
S 12072                 });
12073             }
12074
12075             // Set various midas options in Gecko
12076             if (isGecko) {
12077                 function setOpts() {
12078                     var t = this, d = t.getDoc(), s = t.settings;
12079
29da64 12080                     if (isGecko && !s.readonly) {
d9344f 12081                         if (t._isHidden()) {
S 12082                             try {
12083                                 if (!s.content_editable)
12084                                     d.designMode = 'On';
12085                             } catch (ex) {
12086                                 // Fails if it's hidden
12087                             }
12088                         }
12089
12090                         try {
12091                             // Try new Gecko method
12092                             d.execCommand("styleWithCSS", 0, false);
12093                         } catch (ex) {
12094                             // Use old method
12095                             if (!t._isHidden())
18240a 12096                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
d9344f 12097                         }
S 12098
12099                         if (!s.table_inline_editing)
12100                             try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
12101
12102                         if (!s.object_resizing)
12103                             try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
12104                     }
12105                 };
12106
12107                 t.onBeforeExecCommand.add(setOpts);
12108                 t.onMouseDown.add(setOpts);
12109             }
12110
69d05c 12111             // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
A 12112             // WebKit can't even do simple things like selecting an image
12113             // This also fixes so it's possible to select mceItemAnchors
12114             if (tinymce.isWebKit) {
12115                 t.onClick.add(function(ed, e) {
12116                     e = e.target;
12117
12118                     // Needs tobe the setBaseAndExtend or it will fail to select floated images
a9251b 12119                     if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
69d05c 12120                         t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
a9251b 12121                         t.nodeChanged();
T 12122                     }
69d05c 12123                 });
A 12124             }
12125
d9344f 12126             // Add node change handlers
S 12127             t.onMouseUp.add(t.nodeChanged);
2011be 12128             //t.onClick.add(t.nodeChanged);
d9344f 12129             t.onKeyUp.add(function(ed, e) {
29da64 12130                 var c = e.keyCode;
A 12131
12132                 if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
d9344f 12133                     t.nodeChanged();
S 12134             });
12135
12136             // Add reset handler
12137             t.onReset.add(function() {
12138                 t.setContent(t.startContent, {format : 'raw'});
12139             });
12140
12141             // Add shortcuts
12142             if (s.custom_shortcuts) {
12143                 if (s.custom_undo_redo_keyboard_shortcuts) {
12144                     t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
12145                     t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
12146                 }
12147
12148                 // Add default shortcuts for gecko
2011be 12149                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
A 12150                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
12151                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
d9344f 12152
S 12153                 // BlockFormat shortcuts keys
12154                 for (i=1; i<=6; i++)
69d05c 12155                     t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
d9344f 12156
S 12157                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
12158                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
12159                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
12160
12161                 function find(e) {
12162                     var v = null;
12163
12164                     if (!e.altKey && !e.ctrlKey && !e.metaKey)
12165                         return v;
12166
12167                     each(t.shortcuts, function(o) {
29da64 12168                         if (tinymce.isMac && o.ctrl != e.metaKey)
A 12169                             return;
12170                         else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
d9344f 12171                             return;
S 12172
12173                         if (o.alt != e.altKey)
12174                             return;
12175
12176                         if (o.shift != e.shiftKey)
12177                             return;
12178
12179                         if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
12180                             v = o;
12181                             return false;
12182                         }
12183                     });
12184
12185                     return v;
12186                 };
12187
12188                 t.onKeyUp.add(function(ed, e) {
12189                     var o = find(e);
12190
12191                     if (o)
12192                         return Event.cancel(e);
12193                 });
12194
12195                 t.onKeyPress.add(function(ed, e) {
12196                     var o = find(e);
12197
12198                     if (o)
12199                         return Event.cancel(e);
12200                 });
12201
12202                 t.onKeyDown.add(function(ed, e) {
12203                     var o = find(e);
12204
12205                     if (o) {
12206                         o.func.call(o.scope);
12207                         return Event.cancel(e);
12208                     }
12209                 });
12210             }
12211
12212             if (tinymce.isIE) {
12213                 // Fix so resize will only update the width and height attributes not the styles of an image
12214                 // It will also block mceItemNoResize items
a9251b 12215                 dom.bind(t.getDoc(), 'controlselect', function(e) {
d9344f 12216                     var re = t.resizeInfo, cb;
S 12217
12218                     e = e.target;
12219
12220                     // Don't do this action for non image elements
12221                     if (e.nodeName !== 'IMG')
12222                         return;
12223
12224                     if (re)
a9251b 12225                         dom.unbind(re.node, re.ev, re.cb);
d9344f 12226
a9251b 12227                     if (!dom.hasClass(e, 'mceItemNoResize')) {
d9344f 12228                         ev = 'resizeend';
a9251b 12229                         cb = dom.bind(e, ev, function(e) {
d9344f 12230                             var v;
S 12231
12232                             e = e.target;
12233
a9251b 12234                             if (v = dom.getStyle(e, 'width')) {
T 12235                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
12236                                 dom.setStyle(e, 'width', '');
d9344f 12237                             }
S 12238
a9251b 12239                             if (v = dom.getStyle(e, 'height')) {
T 12240                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
12241                                 dom.setStyle(e, 'height', '');
d9344f 12242                             }
S 12243                         });
12244                     } else {
12245                         ev = 'resizestart';
a9251b 12246                         cb = dom.bind(e, 'resizestart', Event.cancel, Event);
d9344f 12247                     }
S 12248
12249                     re = t.resizeInfo = {
12250                         node : e,
12251                         ev : ev,
12252                         cb : cb
12253                     };
12254                 });
12255
12256                 t.onKeyDown.add(function(ed, e) {
a9251b 12257                     var sel;
T 12258
d9344f 12259                     switch (e.keyCode) {
S 12260                         case 8:
a9251b 12261                             sel = t.getDoc().selection;
T 12262
d9344f 12263                             // Fix IE control + backspace browser bug
a9251b 12264                             if (sel.createRange && sel.createRange().item) {
T 12265                                 ed.dom.remove(sel.createRange().item(0));
d9344f 12266                                 return Event.cancel(e);
S 12267                             }
12268                     }
12269                 });
12270             }
12271
12272             if (tinymce.isOpera) {
12273                 t.onClick.add(function(ed, e) {
12274                     Event.prevent(e);
12275                 });
12276             }
12277
12278             // Add custom undo/redo handlers
12279             if (s.custom_undo_redo) {
12280                 function addUndo() {
a9251b 12281                     t.undoManager.typing = false;
d9344f 12282                     t.undoManager.add();
S 12283                 };
12284
a9251b 12285                 dom.bind(t.getDoc(), 'focusout', function(e) {
69d05c 12286                     if (!t.removed && t.undoManager.typing)
A 12287                         addUndo();
12288                 });
d9344f 12289
a9251b 12290                 // Add undo level when contents is drag/dropped within the editor
T 12291                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
12292                     addUndo();
12293                 });
12294
d9344f 12295                 t.onKeyUp.add(function(ed, e) {
a9251b 12296                     var rng, parent, bookmark;
T 12297
12298                     // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node.
12299                     if (isIE && e.keyCode == 8) {
12300                         rng = t.selection.getRng();
12301                         if (rng.parentElement) {
12302                             parent = rng.parentElement();
12303                             bookmark = t.selection.getBookmark();
12304                             parent.innerHTML = parent.innerHTML;
12305                             t.selection.moveToBookmark(bookmark);
12306                         }
12307                     }
12308
69d05c 12309                     if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
A 12310                         addUndo();
d9344f 12311                 });
S 12312
12313                 t.onKeyDown.add(function(ed, e) {
a9251b 12314                     var rng, parent, bookmark, keyCode = e.keyCode;
2011be 12315
A 12316                     // IE has a really odd bug where the DOM might include an node that doesn't have
12317                     // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
12318                     // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
12319                     // after you delete contents from it. See: #3008923
a9251b 12320                     if (isIE && keyCode == 46) {
2011be 12321                         rng = t.selection.getRng();
A 12322
12323                         if (rng.parentElement) {
12324                             parent = rng.parentElement();
12325
a9251b 12326                             if (!t.undoManager.typing) {
T 12327                                 t.undoManager.beforeChange();
12328                                 t.undoManager.typing = true;
12329                                 t.undoManager.add();
12330                             }
2011be 12331
A 12332                             // Select next word when ctrl key is used in combo with delete
12333                             if (e.ctrlKey) {
12334                                 rng.moveEnd('word', 1);
12335                                 rng.select();
12336                             }
12337
12338                             // Delete contents
12339                             t.selection.getSel().clear();
12340
12341                             // Check if we are within the same parent
12342                             if (rng.parentElement() == parent) {
a9251b 12343                                 bookmark = t.selection.getBookmark();
T 12344
2011be 12345                                 try {
A 12346                                     // Update the HTML and hopefully it will remove the artifacts
12347                                     parent.innerHTML = parent.innerHTML;
12348                                 } catch (ex) {
12349                                     // And since it's IE it can sometimes produce an unknown runtime error
12350                                 }
12351
12352                                 // Restore the caret position
a9251b 12353                                 t.selection.moveToBookmark(bookmark);
2011be 12354                             }
A 12355
12356                             // Block the default delete behavior since it might be broken
12357                             e.preventDefault();
12358                             return;
12359                         }
12360                     }
12361
a9251b 12362                     // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
T 12363                     if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
12364                         // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
12365                         // Todo: Remove this once we normalize enter behavior on IE
12366                         if (tinymce.isIE && keyCode == 13)
12367                             t.undoManager.beforeChange();
12368
69d05c 12369                         if (t.undoManager.typing)
A 12370                             addUndo();
d9344f 12371
S 12372                         return;
12373                     }
12374
a9251b 12375                     // If key isn't shift,ctrl,alt,capslock,metakey
T 12376                     if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
12377                         t.undoManager.beforeChange();
d9344f 12378                         t.undoManager.add();
a9251b 12379                         t.undoManager.typing = true;
d9344f 12380                     }
S 12381                 });
12382
69d05c 12383                 t.onMouseDown.add(function() {
A 12384                     if (t.undoManager.typing)
12385                         addUndo();
a9251b 12386                 });
T 12387             }
12388             
12389             // Bug fix for FireFox keeping styles from end of selection instead of start.
12390             if (tinymce.isGecko) {
12391                 function getAttributeApplyFunction() {
12392                     var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
12393
12394                     return function() {
12395                         var target = t.selection.getStart();
12396                         t.dom.removeAllAttribs(target);
12397                         each(template, function(attr) {
12398                             target.setAttributeNode(attr.cloneNode(true));
12399                         });
12400                     };
12401                 }
12402
12403                 function isSelectionAcrossElements() {
12404                     var s = t.selection;
12405
12406                     return !s.isCollapsed() && s.getStart() != s.getEnd();
12407                 }
12408
12409                 t.onKeyPress.add(function(ed, e) {
12410                     var applyAttributes;
12411
12412                     if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
12413                         applyAttributes = getAttributeApplyFunction();
12414                         t.getDoc().execCommand('delete', false, null);
12415                         applyAttributes();
12416
12417                         return Event.cancel(e);
12418                     }
12419                 });
12420
12421                 t.dom.bind(t.getDoc(), 'cut', function(e) {
12422                     var applyAttributes;
12423
12424                     if (isSelectionAcrossElements()) {
12425                         applyAttributes = getAttributeApplyFunction();
12426                         t.onKeyUp.addToTop(Event.cancel, Event);
12427
12428                         setTimeout(function() {
12429                             applyAttributes();
12430                             t.onKeyUp.remove(Event.cancel, Event);
12431                         }, 0);
12432                     }
d9344f 12433                 });
S 12434             }
12435         },
12436
12437         _isHidden : function() {
12438             var s;
12439
12440             if (!isGecko)
12441                 return 0;
12442
12443             // Weird, wheres that cursor selection?
12444             s = this.selection.getSel();
12445             return (!s || !s.rangeCount || s.rangeCount == 0);
12446         }
58fb65 12447     });
29da64 12448 })(tinymce);
69d05c 12449
29da64 12450 (function(tinymce) {
69d05c 12451     // Added for compression purposes
A 12452     var each = tinymce.each, undefined, TRUE = true, FALSE = false;
d9344f 12453
69d05c 12454     tinymce.EditorCommands = function(editor) {
A 12455         var dom = editor.dom,
12456             selection = editor.selection,
12457             commands = {state: {}, exec : {}, value : {}},
12458             settings = editor.settings,
12459             bookmark;
d9344f 12460
69d05c 12461         function execCommand(command, ui, value) {
A 12462             var func;
d9344f 12463
69d05c 12464             command = command.toLowerCase();
A 12465             if (func = commands.exec[command]) {
12466                 func(command, ui, value);
12467                 return TRUE;
d9344f 12468             }
S 12469
69d05c 12470             return FALSE;
A 12471         };
d9344f 12472
69d05c 12473         function queryCommandState(command) {
A 12474             var func;
d9344f 12475
69d05c 12476             command = command.toLowerCase();
A 12477             if (func = commands.state[command])
12478                 return func(command);
d9344f 12479
S 12480             return -1;
69d05c 12481         };
d9344f 12482
69d05c 12483         function queryCommandValue(command) {
A 12484             var func;
d9344f 12485
69d05c 12486             command = command.toLowerCase();
A 12487             if (func = commands.value[command])
12488                 return func(command);
d9344f 12489
69d05c 12490             return FALSE;
A 12491         };
d9344f 12492
69d05c 12493         function addCommands(command_list, type) {
A 12494             type = type || 'exec';
29da64 12495
69d05c 12496             each(command_list, function(callback, command) {
A 12497                 each(command.toLowerCase().split(','), function(command) {
12498                     commands[type][command] = callback;
12499                 });
12500             });
12501         };
d9344f 12502
69d05c 12503         // Expose public methods
A 12504         tinymce.extend(this, {
12505             execCommand : execCommand,
12506             queryCommandState : queryCommandState,
12507             queryCommandValue : queryCommandValue,
12508             addCommands : addCommands
12509         });
d9344f 12510
69d05c 12511         // Private methods
d9344f 12512
69d05c 12513         function execNativeCommand(command, ui, value) {
A 12514             if (ui === undefined)
12515                 ui = FALSE;
d9344f 12516
69d05c 12517             if (value === undefined)
A 12518                 value = null;
29da64 12519
69d05c 12520             return editor.getDoc().execCommand(command, ui, value);
A 12521         };
d9344f 12522
69d05c 12523         function isFormatMatch(name) {
A 12524             return editor.formatter.match(name);
12525         };
d9344f 12526
69d05c 12527         function toggleFormat(name, value) {
A 12528             editor.formatter.toggle(name, value ? {value : value} : undefined);
12529         };
d9344f 12530
69d05c 12531         function storeSelection(type) {
A 12532             bookmark = selection.getBookmark(type);
12533         };
d9344f 12534
69d05c 12535         function restoreSelection() {
A 12536             selection.moveToBookmark(bookmark);
12537         };
d9344f 12538
69d05c 12539         // Add execCommand overrides
A 12540         addCommands({
12541             // Ignore these, added for compatibility
12542             'mceResetDesignMode,mceBeginUndoLevel' : function() {},
d9344f 12543
69d05c 12544             // Add undo manager logic
A 12545             'mceEndUndoLevel,mceAddUndoLevel' : function() {
12546                 editor.undoManager.add();
12547             },
d9344f 12548
69d05c 12549             'Cut,Copy,Paste' : function(command) {
A 12550                 var doc = editor.getDoc(), failed;
d9344f 12551
69d05c 12552                 // Try executing the native command
A 12553                 try {
12554                     execNativeCommand(command);
12555                 } catch (ex) {
12556                     // Command failed
12557                     failed = TRUE;
d9344f 12558                 }
S 12559
69d05c 12560                 // Present alert message about clipboard access not being available
2011be 12561                 if (failed || !doc.queryCommandSupported(command)) {
69d05c 12562                     if (tinymce.isGecko) {
A 12563                         editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
12564                             if (state)
12565                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
12566                         });
12567                     } else
12568                         editor.windowManager.alert(editor.getLang('clipboard_no_support'));
12569                 }
12570             },
d9344f 12571
69d05c 12572             // Override unlink command
A 12573             unlink : function(command) {
12574                 if (selection.isCollapsed())
12575                     selection.select(selection.getNode());
d9344f 12576
69d05c 12577                 execNativeCommand(command);
A 12578                 selection.collapse(FALSE);
12579             },
d9344f 12580
69d05c 12581             // Override justify commands to use the text formatter engine
A 12582             'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12583                 var align = command.substring(7);
12584
12585                 // Remove all other alignments first
12586                 each('left,center,right,full'.split(','), function(name) {
12587                     if (align != name)
12588                         editor.formatter.remove('align' + name);
12589                 });
12590
12591                 toggleFormat('align' + align);
a9251b 12592                 execCommand('mceRepaint');
69d05c 12593             },
A 12594
12595             // Override list commands to fix WebKit bug
12596             'InsertUnorderedList,InsertOrderedList' : function(command) {
12597                 var listElm, listParent;
12598
12599                 execNativeCommand(command);
12600
12601                 // WebKit produces lists within block elements so we need to split them
12602                 // we will replace the native list creation logic to custom logic later on
12603                 // TODO: Remove this when the list creation logic is removed
12604                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
12605                 if (listElm) {
12606                     listParent = listElm.parentNode;
12607
12608                     // If list is within a text block then split that block
12609                     if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
12610                         storeSelection();
12611                         dom.split(listParent, listElm);
12612                         restoreSelection();
d9344f 12613                     }
69d05c 12614                 }
A 12615             },
d9344f 12616
69d05c 12617             // Override commands to use the text formatter engine
a9251b 12618             'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
69d05c 12619                 toggleFormat(command);
A 12620             },
12621
12622             // Override commands to use the text formatter engine
12623             'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
12624                 toggleFormat(command, value);
12625             },
12626
12627             FontSize : function(command, ui, value) {
12628                 var fontClasses, fontSizes;
12629
12630                 // Convert font size 1-7 to styles
12631                 if (value >= 1 && value <= 7) {
12632                     fontSizes = tinymce.explode(settings.font_size_style_values);
12633                     fontClasses = tinymce.explode(settings.font_size_classes);
12634
12635                     if (fontClasses)
12636                         value = fontClasses[value - 1] || value;
12637                     else
12638                         value = fontSizes[value - 1] || value;
12639                 }
12640
12641                 toggleFormat(command, value);
12642             },
12643
12644             RemoveFormat : function(command) {
12645                 editor.formatter.remove(command);
12646             },
12647
12648             mceBlockQuote : function(command) {
12649                 toggleFormat('blockquote');
12650             },
12651
12652             FormatBlock : function(command, ui, value) {
a9251b 12653                 return toggleFormat(value || 'p');
69d05c 12654             },
A 12655
12656             mceCleanup : function() {
2011be 12657                 var bookmark = selection.getBookmark();
A 12658
69d05c 12659                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
2011be 12660
A 12661                 selection.moveToBookmark(bookmark);
69d05c 12662             },
A 12663
12664             mceRemoveNode : function(command, ui, value) {
12665                 var node = value || selection.getNode();
12666
12667                 // Make sure that the body node isn't removed
2011be 12668                 if (node != editor.getBody()) {
69d05c 12669                     storeSelection();
A 12670                     editor.dom.remove(node, TRUE);
12671                     restoreSelection();
12672                 }
12673             },
12674
12675             mceSelectNodeDepth : function(command, ui, value) {
12676                 var counter = 0;
12677
12678                 dom.getParent(selection.getNode(), function(node) {
12679                     if (node.nodeType == 1 && counter++ == value) {
12680                         selection.select(node);
12681                         return FALSE;
12682                     }
12683                 }, editor.getBody());
12684             },
12685
12686             mceSelectNode : function(command, ui, value) {
12687                 selection.select(value);
12688             },
12689
12690             mceInsertContent : function(command, ui, value) {
a9251b 12691                 var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
T 12692
12693                 function findSuitableCaretNode(node, root_node, next) {
12694                     var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node);
12695
12696                     while ((node = walker.current())) {
12697                         if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
12698                             return node;
12699
12700                         if (next)
12701                             walker.next();
12702                         else
12703                             walker.prev();
12704                     }
12705                 };
12706
12707                 args = {content: value, format: 'html'};
12708                 selection.onBeforeSetContent.dispatch(selection, args);
12709                 value = args.content;
12710
12711                 // Add caret at end of contents if it's missing
12712                 if (value.indexOf('{$caret}') == -1)
12713                     value += '{$caret}';
12714
12715                 // Set the content at selection to a span and replace it's contents with the value
12716                 selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
12717                 dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
12718
12719                 caretNode = dom.select('#__mce')[0];
12720                 rootNode = dom.getRoot();
12721
12722                 // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
12723                 if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
12724                     node = findSuitableCaretNode(caretNode, rootNode);
12725                     if (node) {
12726                         if (node.nodeName == 'BR')
12727                             node.parentNode.insertBefore(caretNode, node);
12728                         else
12729                             dom.insertAfter(caretNode, node);
12730                     }
12731                 }
12732
12733                 // Find caret root parent and clean it up using the serializer to avoid nesting
12734                 while (caretNode) {
12735                     if (caretNode === rootNode) {
12736                         // Clean up the parent element by parsing and serializing it
12737                         // This will remove invalid elements/attributes and fix nesting issues
12738                         dom.setOuterHTML(parent, 
12739                             new tinymce.html.Serializer({}, editor.schema).serialize(
12740                                 editor.parser.parse(dom.getOuterHTML(parent))
12741                             )
12742                         );
12743
12744                         break;
12745                     }
12746
12747                     parent = caretNode;
12748                     caretNode = caretNode.parentNode;
12749                 }
12750
12751                 // Find caret after cleanup and move selection to that location
12752                 caretNode = dom.select('#__mce')[0];
12753                 if (caretNode) {
12754                     node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true);
12755                     dom.remove(caretNode);
12756
12757                     if (node) {
12758                         rng = dom.createRng();
12759
12760                         if (node.nodeType == 3) {
12761                             rng.setStart(node, node.length);
12762                             rng.setEnd(node, node.length);
12763                         } else {
12764                             if (node.nodeName == 'BR') {
12765                                 rng.setStartBefore(node);
12766                                 rng.setEndBefore(node);
12767                             } else {
12768                                 rng.setStartAfter(node);
12769                                 rng.setEndAfter(node);
12770                             }
12771                         }
12772
12773                         selection.setRng(rng);
12774
12775                         // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
12776                         if (!tinymce.isIE) {
12777                             node = dom.create('span', null, '\u00a0');
12778                             rng.insertNode(node);
12779                             nodeRect = dom.getRect(node);
12780                             viewPortRect = dom.getViewPort(editor.getWin());
12781
12782                             // Check if node is out side the viewport if it is then scroll to it
12783                             if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
12784                                 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
12785                                 editor.getBody().scrollLeft = nodeRect.x;
12786                                 editor.getBody().scrollTop = nodeRect.y;
12787                             }
12788
12789                             dom.remove(node);
12790                         }
12791
12792                         // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
12793                         // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
12794                         selection.collapse(true);
12795                     }
12796                 }
12797
12798                 selection.onSetContent.dispatch(selection, args);
12799                 editor.addVisual();
69d05c 12800             },
A 12801
12802             mceInsertRawHTML : function(command, ui, value) {
12803                 selection.setContent('tiny_mce_marker');
a9251b 12804                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
69d05c 12805             },
A 12806
12807             mceSetContent : function(command, ui, value) {
12808                 editor.setContent(value);
12809             },
12810
12811             'Indent,Outdent' : function(command) {
12812                 var intentValue, indentUnit, value;
12813
12814                 // Setup indent level
12815                 intentValue = settings.indentation;
12816                 indentUnit = /[a-z%]+$/i.exec(intentValue);
12817                 intentValue = parseInt(intentValue);
12818
12819                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
12820                     each(selection.getSelectedBlocks(), function(element) {
12821                         if (command == 'outdent') {
12822                             value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
12823                             dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
12824                         } else
12825                             dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
12826                     });
12827                 } else
12828                     execNativeCommand(command);
12829             },
12830
12831             mceRepaint : function() {
12832                 var bookmark;
12833
12834                 if (tinymce.isGecko) {
12835                     try {
12836                         storeSelection(TRUE);
12837
12838                         if (selection.getSel())
12839                             selection.getSel().selectAllChildren(editor.getBody());
12840
12841                         selection.collapse(TRUE);
12842                         restoreSelection();
12843                     } catch (ex) {
12844                         // Ignore
12845                     }
12846                 }
12847             },
12848
12849             mceToggleFormat : function(command, ui, value) {
12850                 editor.formatter.toggle(value);
12851             },
12852
12853             InsertHorizontalRule : function() {
a9251b 12854                 editor.execCommand('mceInsertContent', false, '<hr />');
69d05c 12855             },
A 12856
12857             mceToggleVisualAid : function() {
12858                 editor.hasVisual = !editor.hasVisual;
12859                 editor.addVisual();
12860             },
12861
12862             mceReplaceContent : function(command, ui, value) {
a9251b 12863                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
69d05c 12864             },
A 12865
12866             mceInsertLink : function(command, ui, value) {
a9251b 12867                 var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
69d05c 12868
A 12869                 if (tinymce.is(value, 'string'))
12870                     value = {href : value};
12871
a9251b 12872                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
T 12873                 value.href = value.href.replace(' ', '%20');
12874
69d05c 12875                 if (!link) {
a9251b 12876                     // WebKit can't create links on float images for some odd reason so just remove it and restore it later
T 12877                     if (tinymce.isWebKit) {
12878                         img = dom.getParent(selection.getNode(), 'img');
12879
12880                         if (img) {
12881                             floatVal = img.style.cssFloat;
12882                             img.style.cssFloat = null;
12883                         }
12884                     }
12885
69d05c 12886                     execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
a9251b 12887
T 12888                     // Restore float value
12889                     if (floatVal)
12890                         img.style.cssFloat = floatVal;
12891
12892                     each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
69d05c 12893                         dom.setAttribs(link, value);
A 12894                     });
d9344f 12895                 } else {
69d05c 12896                     if (value.href)
A 12897                         dom.setAttribs(link, value);
12898                     else
2011be 12899                         editor.dom.remove(link, TRUE);
d9344f 12900                 }
2011be 12901             },
A 12902             
12903             selectAll : function() {
a9251b 12904                 var root = dom.getRoot(), rng = dom.createRng();
T 12905
2011be 12906                 rng.setStart(root, 0);
A 12907                 rng.setEnd(root, root.childNodes.length);
a9251b 12908
2011be 12909                 editor.selection.setRng(rng);
d9344f 12910             }
69d05c 12911         });
d9344f 12912
69d05c 12913         // Add queryCommandState overrides
A 12914         addCommands({
12915             // Override justify commands
12916             'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12917                 return isFormatMatch('align' + command.substring(7));
12918             },
d9344f 12919
a9251b 12920             'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
69d05c 12921                 return isFormatMatch(command);
A 12922             },
d9344f 12923
69d05c 12924             mceBlockQuote : function() {
A 12925                 return isFormatMatch('blockquote');
12926             },
d9344f 12927
69d05c 12928             Outdent : function() {
A 12929                 var node;
d9344f 12930
69d05c 12931                 if (settings.inline_styles) {
A 12932                     if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12933                         return TRUE;
12934
12935                     if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12936                         return TRUE;
d9344f 12937                 }
S 12938
69d05c 12939                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
A 12940             },
d9344f 12941
69d05c 12942             'InsertUnorderedList,InsertOrderedList' : function(command) {
A 12943                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
d9344f 12944             }
69d05c 12945         }, 'state');
d9344f 12946
69d05c 12947         // Add queryCommandValue overrides
A 12948         addCommands({
12949             'FontSize,FontName' : function(command) {
12950                 var value = 0, parent;
d9344f 12951
69d05c 12952                 if (parent = dom.getParent(selection.getNode(), 'span')) {
A 12953                     if (command == 'fontsize')
12954                         value = parent.style.fontSize;
12955                     else
12956                         value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
d9344f 12957                 }
S 12958
69d05c 12959                 return value;
d9344f 12960             }
69d05c 12961         }, 'value');
d9344f 12962
69d05c 12963         // Add undo manager logic
A 12964         if (settings.custom_undo_redo) {
12965             addCommands({
12966                 Undo : function() {
12967                     editor.undoManager.undo();
12968                 },
d9344f 12969
69d05c 12970                 Redo : function() {
A 12971                     editor.undoManager.redo();
d9344f 12972                 }
S 12973             });
29da64 12974         }
69d05c 12975     };
29da64 12976 })(tinymce);
a9251b 12977
29da64 12978 (function(tinymce) {
69d05c 12979     var Dispatcher = tinymce.util.Dispatcher;
d9344f 12980
69d05c 12981     tinymce.UndoManager = function(editor) {
A 12982         var self, index = 0, data = [];
12983
12984         function getContent() {
12985             return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
12986         };
12987
12988         return self = {
a9251b 12989             typing : false,
69d05c 12990
A 12991             onAdd : new Dispatcher(self),
a9251b 12992
69d05c 12993             onUndo : new Dispatcher(self),
a9251b 12994
69d05c 12995             onRedo : new Dispatcher(self),
a9251b 12996
T 12997             beforeChange : function() {
12998                 // Set before bookmark on previous level
12999                 if (data[index])
13000                     data[index].beforeBookmark = editor.selection.getBookmark(2, true);
13001             },
69d05c 13002
A 13003             add : function(level) {
13004                 var i, settings = editor.settings, lastLevel;
13005
13006                 level = level || {};
13007                 level.content = getContent();
13008
13009                 // Add undo level if needed
13010                 lastLevel = data[index];
a9251b 13011                 if (lastLevel && lastLevel.content == level.content)
T 13012                     return null;
69d05c 13013
A 13014                 // Time to compress
13015                 if (settings.custom_undo_redo_levels) {
13016                     if (data.length > settings.custom_undo_redo_levels) {
13017                         for (i = 0; i < data.length - 1; i++)
13018                             data[i] = data[i + 1];
13019
13020                         data.length--;
13021                         index = data.length;
13022                     }
13023                 }
13024
13025                 // Get a non intrusive normalized bookmark
13026                 level.bookmark = editor.selection.getBookmark(2, true);
13027
13028                 // Crop array if needed
a9251b 13029                 if (index < data.length - 1)
T 13030                     data.length = index + 1;
69d05c 13031
A 13032                 data.push(level);
13033                 index = data.length - 1;
13034
13035                 self.onAdd.dispatch(self, level);
13036                 editor.isNotDirty = 0;
13037
13038                 return level;
13039             },
13040
13041             undo : function() {
13042                 var level, i;
13043
13044                 if (self.typing) {
13045                     self.add();
a9251b 13046                     self.typing = false;
69d05c 13047                 }
A 13048
13049                 if (index > 0) {
13050                     level = data[--index];
13051
13052                     editor.setContent(level.content, {format : 'raw'});
a9251b 13053                     editor.selection.moveToBookmark(level.beforeBookmark);
69d05c 13054
A 13055                     self.onUndo.dispatch(self, level);
13056                 }
13057
13058                 return level;
13059             },
13060
13061             redo : function() {
13062                 var level;
13063
13064                 if (index < data.length - 1) {
13065                     level = data[++index];
13066
13067                     editor.setContent(level.content, {format : 'raw'});
13068                     editor.selection.moveToBookmark(level.bookmark);
13069
13070                     self.onRedo.dispatch(self, level);
13071                 }
13072
13073                 return level;
13074             },
13075
13076             clear : function() {
13077                 data = [];
a9251b 13078                 index = 0;
T 13079                 self.typing = false;
69d05c 13080             },
A 13081
13082             hasUndo : function() {
a9251b 13083                 return index > 0 || this.typing;
69d05c 13084             },
A 13085
13086             hasRedo : function() {
a9251b 13087                 return index < data.length - 1 && !this.typing;
69d05c 13088             }
A 13089         };
13090     };
13091 })(tinymce);
13092
13093 (function(tinymce) {
13094     // Shorten names
13095     var Event = tinymce.dom.Event,
13096         isIE = tinymce.isIE,
13097         isGecko = tinymce.isGecko,
13098         isOpera = tinymce.isOpera,
13099         each = tinymce.each,
13100         extend = tinymce.extend,
13101         TRUE = true,
13102         FALSE = false;
29da64 13103
2011be 13104     function cloneFormats(node) {
A 13105         var clone, temp, inner;
13106
13107         do {
13108             if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
13109                 if (clone) {
13110                     temp = node.cloneNode(false);
13111                     temp.appendChild(clone);
13112                     clone = temp;
13113                 } else {
13114                     clone = inner = node.cloneNode(false);
13115                 }
13116
13117                 clone.removeAttribute('id');
13118             }
13119         } while (node = node.parentNode);
13120
13121         if (clone)
13122             return {wrapper : clone, inner : inner};
13123     };
13124
58fb65 13125     // Checks if the selection/caret is at the end of the specified block element
A 13126     function isAtEnd(rng, par) {
13127         var rng2 = par.ownerDocument.createRange();
13128
13129         rng2.setStart(rng.endContainer, rng.endOffset);
13130         rng2.setEndAfter(par);
13131
13132         // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
13133         return rng2.cloneContents().textContent.length == 0;
13134     };
13135
69d05c 13136     function splitList(selection, dom, li) {
A 13137         var listBlock, block;
13138
a9251b 13139         if (dom.isEmpty(li)) {
69d05c 13140             listBlock = dom.getParent(li, 'ul,ol');
A 13141
13142             if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
13143                 dom.split(listBlock, li);
a9251b 13144                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
69d05c 13145                 dom.replace(block, li);
A 13146                 selection.select(block, 1);
13147             }
13148
13149             return FALSE;
13150         }
13151
13152         return TRUE;
29da64 13153     };
d9344f 13154
S 13155     tinymce.create('tinymce.ForceBlocks', {
13156         ForceBlocks : function(ed) {
13157             var t = this, s = ed.settings, elm;
13158
13159             t.editor = ed;
13160             t.dom = ed.dom;
13161             elm = (s.forced_root_block || 'p').toLowerCase();
13162             s.element = elm.toUpperCase();
13163
13164             ed.onPreInit.add(t.setup, t);
13165
13166             if (s.forced_root_block) {
13167                 ed.onInit.add(t.forceRoots, t);
13168                 ed.onSetContent.add(t.forceRoots, t);
13169                 ed.onBeforeGetContent.add(t.forceRoots, t);
a9251b 13170                 ed.onExecCommand.add(function(ed, cmd) {
T 13171                     if (cmd == 'mceInsertContent') {
13172                         t.forceRoots();
13173                         ed.nodeChanged();
13174                     }
13175                 });
d9344f 13176             }
S 13177         },
13178
13179         setup : function() {
69d05c 13180             var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
d9344f 13181
S 13182             // Force root blocks when typing and when getting output
13183             if (s.forced_root_block) {
69d05c 13184                 ed.onBeforeExecCommand.add(t.forceRoots, t);
d9344f 13185                 ed.onKeyUp.add(t.forceRoots, t);
S 13186                 ed.onPreProcess.add(t.forceRoots, t);
13187             }
13188
13189             if (s.force_br_newlines) {
13190                 // Force IE to produce BRs on enter
13191                 if (isIE) {
13192                     ed.onKeyPress.add(function(ed, e) {
69d05c 13193                         var n;
d9344f 13194
69d05c 13195                         if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
A 13196                             selection.setContent('<br id="__" /> ', {format : 'raw'});
13197                             n = dom.get('__');
d9344f 13198                             n.removeAttribute('id');
69d05c 13199                             selection.select(n);
A 13200                             selection.collapse();
d9344f 13201                             return Event.cancel(e);
S 13202                         }
13203                     });
13204                 }
13205             }
13206
2011be 13207             if (s.force_p_newlines) {
A 13208                 if (!isIE) {
13209                     ed.onKeyPress.add(function(ed, e) {
13210                         if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
13211                             Event.cancel(e);
13212                     });
13213                 } else {
13214                     // Ungly hack to for IE to preserve the formatting when you press
13215                     // enter at the end of a block element with formatted contents
13216                     // This logic overrides the browsers default logic with
13217                     // custom logic that enables us to control the output
13218                     tinymce.addUnload(function() {
13219                         t._previousFormats = 0; // Fix IE leak
13220                     });
13221
13222                     ed.onKeyPress.add(function(ed, e) {
13223                         t._previousFormats = 0;
13224
13225                         // Clone the current formats, this will later be applied to the new block contents
13226                         if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
13227                             t._previousFormats = cloneFormats(ed.selection.getStart());
13228                     });
13229
13230                     ed.onKeyUp.add(function(ed, e) {
13231                         // Let IE break the element and the wrap the new caret location in the previous formats
13232                         if (e.keyCode == 13 && !e.shiftKey) {
13233                             var parent = ed.selection.getStart(), fmt = t._previousFormats;
13234
13235                             // Parent is an empty block
a9251b 13236                             if (!parent.hasChildNodes() && fmt) {
2011be 13237                                 parent = dom.getParent(parent, dom.isBlock);
A 13238
a9251b 13239                                 if (parent && parent.nodeName != 'LI') {
2011be 13240                                     parent.innerHTML = '';
a9251b 13241
2011be 13242                                     if (t._previousFormats) {
A 13243                                         parent.appendChild(fmt.wrapper);
13244                                         fmt.inner.innerHTML = '\uFEFF';
13245                                     } else
13246                                         parent.innerHTML = '\uFEFF';
13247
13248                                     selection.select(parent, 1);
a9251b 13249                                     selection.collapse(true);
2011be 13250                                     ed.getDoc().execCommand('Delete', false, null);
a9251b 13251                                     t._previousFormats = 0;
2011be 13252                                 }
A 13253                             }
13254                         }
13255                     });
13256                 }
d9344f 13257
S 13258                 if (isGecko) {
13259                     ed.onKeyDown.add(function(ed, e) {
13260                         if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
13261                             t.backspaceDelete(e, e.keyCode == 8);
13262                     });
13263                 }
13264             }
13265
69d05c 13266             // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
A 13267             if (tinymce.isWebKit) {
13268                 function insertBr(ed) {
13269                     var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
d9344f 13270
69d05c 13271                     // Insert BR element
A 13272                     rng.insertNode(br = dom.create('br'));
13273
13274                     // Place caret after BR
13275                     rng.setStartAfter(br);
13276                     rng.setEndAfter(br);
13277                     selection.setRng(rng);
13278
13279                     // Could not place caret after BR then insert an nbsp entity and move the caret
13280                     if (selection.getSel().focusNode == br.previousSibling) {
13281                         selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
13282                         selection.collapse(TRUE);
13283                     }
13284
13285                     // Create a temporary DIV after the BR and get the position as it
13286                     // seems like getPos() returns 0 for text nodes and BR elements.
13287                     dom.insertAfter(div, br);
13288                     divYPos = dom.getPos(div).y;
13289                     dom.remove(div);
13290
13291                     // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
13292                     if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
13293                         ed.getWin().scrollTo(0, divYPos);
13294                 };
13295
13296                 ed.onKeyPress.add(function(ed, e) {
2011be 13297                     if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
69d05c 13298                         insertBr(ed);
A 13299                         Event.cancel(e);
13300                     }
d9344f 13301                 });
69d05c 13302             }
58fb65 13303
29da64 13304             // IE specific fixes
A 13305             if (isIE) {
13306                 // Replaces IE:s auto generated paragraphs with the specified element name
13307                 if (s.element != 'P') {
13308                     ed.onKeyPress.add(function(ed, e) {
69d05c 13309                         t.lastElm = selection.getNode().nodeName;
29da64 13310                     });
d9344f 13311
29da64 13312                     ed.onKeyUp.add(function(ed, e) {
69d05c 13313                         var bl, n = selection.getNode(), b = ed.getBody();
d9344f 13314
29da64 13315                         if (b.childNodes.length === 1 && n.nodeName == 'P') {
69d05c 13316                             n = dom.rename(n, s.element);
A 13317                             selection.select(n);
13318                             selection.collapse();
d9344f 13319                             ed.nodeChanged();
29da64 13320                         } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
69d05c 13321                             bl = dom.getParent(n, 'p');
29da64 13322
A 13323                             if (bl) {
69d05c 13324                                 dom.rename(bl, s.element);
29da64 13325                                 ed.nodeChanged();
A 13326                             }
d9344f 13327                         }
29da64 13328                     });
A 13329                 }
d9344f 13330             }
S 13331         },
13332
13333         find : function(n, t, s) {
69d05c 13334             var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
d9344f 13335
S 13336             while (n = w.nextNode()) {
13337                 c++;
13338
13339                 // Index by node
13340                 if (t == 0 && n == s)
13341                     return c;
13342
13343                 // Node by index
13344                 if (t == 1 && c == s)
13345                     return n;
13346             }
13347
13348             return -1;
13349         },
13350
13351         forceRoots : function(ed, e) {
13352             var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
29da64 13353             var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
d9344f 13354
S 13355             // Fix for bug #1863847
29da64 13356             //if (e && e.keyCode == 13)
69d05c 13357             //    return TRUE;
d9344f 13358
S 13359             // Wrap non blocks into blocks
13360             for (i = nl.length - 1; i >= 0; i--) {
13361                 nx = nl[i];
69d05c 13362
A 13363                 // Ignore internal elements
a9251b 13364                 if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
69d05c 13365                     bl = null;
A 13366                     continue;
13367                 }
d9344f 13368
S 13369                 // Is text or non block element
58fb65 13370                 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
d9344f 13371                     if (!bl) {
S 13372                         // Create new block but ignore whitespace
13373                         if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
13374                             // Store selection
13375                             if (si == -2 && r) {
a9251b 13376                                 if (!isIE || r.setStart) {
29da64 13377                                     // If selection is element then mark it
A 13378                                     if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
13379                                         // Save the id of the selected element
13380                                         eid = n.getAttribute("id");
13381                                         n.setAttribute("id", "__mce");
13382                                     } else {
13383                                         // If element is inside body, might not be the case in contentEdiable mode
13384                                         if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
13385                                             so = r.startOffset;
13386                                             eo = r.endOffset;
13387                                             si = t.find(b, 0, r.startContainer);
13388                                             ei = t.find(b, 0, r.endContainer);
13389                                         }
18240a 13390                                     }
d9344f 13391                                 } else {
2011be 13392                                     // Force control range into text range
A 13393                                     if (r.item) {
13394                                         tr = d.body.createTextRange();
13395                                         tr.moveToElementText(r.item(0));
13396                                         r = tr;
13397                                     }
13398
d9344f 13399                                     tr = d.body.createTextRange();
S 13400                                     tr.moveToElementText(b);
13401                                     tr.collapse(1);
13402                                     bp = tr.move('character', c) * -1;
13403
13404                                     tr = r.duplicate();
13405                                     tr.collapse(1);
13406                                     sp = tr.move('character', c) * -1;
13407
13408                                     tr = r.duplicate();
13409                                     tr.collapse(0);
13410                                     le = (tr.move('character', c) * -1) - sp;
13411
13412                                     si = sp - bp;
13413                                     ei = le;
13414                                 }
13415                             }
13416
58fb65 13417                             // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
A 13418                             // See: http://support.microsoft.com/kb/829907
d9344f 13419                             bl = ed.dom.create(ed.settings.forced_root_block);
S 13420                             nx.parentNode.replaceChild(bl, nx);
58fb65 13421                             bl.appendChild(nx);
d9344f 13422                         }
S 13423                     } else {
13424                         if (bl.hasChildNodes())
13425                             bl.insertBefore(nx, bl.firstChild);
13426                         else
13427                             bl.appendChild(nx);
13428                     }
13429                 } else
13430                     bl = null; // Time to create new block
13431             }
13432
13433             // Restore selection
13434             if (si != -2) {
a9251b 13435                 if (!isIE || r.setStart) {
18240a 13436                     bl = b.getElementsByTagName(ed.settings.element)[0];
d9344f 13437                     r = d.createRange();
S 13438
13439                     // Select last location or generated block
13440                     if (si != -1)
13441                         r.setStart(t.find(b, 1, si), so);
13442                     else
13443                         r.setStart(bl, 0);
13444
13445                     // Select last location or generated block
13446                     if (ei != -1)
13447                         r.setEnd(t.find(b, 1, ei), eo);
13448                     else
13449                         r.setEnd(bl, 0);
13450
13451                     if (s) {
13452                         s.removeAllRanges();
13453                         s.addRange(r);
13454                     }
13455                 } else {
13456                     try {
13457                         r = s.createRange();
13458                         r.moveToElementText(b);
13459                         r.collapse(1);
13460                         r.moveStart('character', si);
13461                         r.moveEnd('character', ei);
13462                         r.select();
13463                     } catch (ex) {
13464                         // Ignore
13465                     }
13466                 }
a9251b 13467             } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
29da64 13468                 // Restore the id of the selected element
A 13469                 if (eid)
13470                     n.setAttribute('id', eid);
13471                 else
13472                     n.removeAttribute('id');
13473
13474                 // Move caret before selected element
13475                 r = d.createRange();
13476                 r.setStartBefore(n);
13477                 r.setEndBefore(n);
13478                 se.setRng(r);
d9344f 13479             }
S 13480         },
13481
13482         getParentBlock : function(n) {
13483             var d = this.dom;
13484
13485             return d.getParent(n, d.isBlock);
13486         },
13487
13488         insertPara : function(e) {
18240a 13489             var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
29da64 13490             var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
a9251b 13491
T 13492             ed.undoManager.beforeChange();
d9344f 13493
S 13494             // If root blocks are forced then use Operas default behavior since it's really good
13495 // Removed due to bug: #1853816
13496 //            if (se.forced_root_block && isOpera)
69d05c 13497 //                return TRUE;
d9344f 13498
S 13499             // Setup before range
13500             rb = d.createRange();
13501
13502             // If is before the first block element and in body, then move it into first block element
13503             rb.setStart(s.anchorNode, s.anchorOffset);
69d05c 13504             rb.collapse(TRUE);
d9344f 13505
S 13506             // Setup after range
13507             ra = d.createRange();
13508
13509             // If is before the first block element and in body, then move it into first block element
13510             ra.setStart(s.focusNode, s.focusOffset);
69d05c 13511             ra.collapse(TRUE);
d9344f 13512
S 13513             // Setup start/end points
13514             dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
13515             sn = dir ? s.anchorNode : s.focusNode;
13516             so = dir ? s.anchorOffset : s.focusOffset;
13517             en = dir ? s.focusNode : s.anchorNode;
13518             eo = dir ? s.focusOffset : s.anchorOffset;
18240a 13519
A 13520             // If selection is in empty table cell
13521             if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
58fb65 13522                 if (sn.firstChild.nodeName == 'BR')
A 13523                     dom.remove(sn.firstChild); // Remove BR
18240a 13524
A 13525                 // Create two new block elements
58fb65 13526                 if (sn.childNodes.length == 0) {
A 13527                     ed.dom.add(sn, se.element, null, '<br />');
13528                     aft = ed.dom.add(sn, se.element, null, '<br />');
13529                 } else {
13530                     n = sn.innerHTML;
13531                     sn.innerHTML = '';
13532                     ed.dom.add(sn, se.element, null, n);
13533                     aft = ed.dom.add(sn, se.element, null, '<br />');
13534                 }
18240a 13535
A 13536                 // Move caret into the last one
13537                 r = d.createRange();
13538                 r.selectNodeContents(aft);
13539                 r.collapse(1);
13540                 ed.selection.setRng(r);
13541
69d05c 13542                 return FALSE;
18240a 13543             }
d9344f 13544
S 13545             // If the caret is in an invalid location in FF we need to move it into the first block
13546             if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
13547                 sn = en = sn.firstChild;
13548                 so = eo = 0;
13549                 rb = d.createRange();
13550                 rb.setStart(sn, 0);
13551                 ra = d.createRange();
13552                 ra.setStart(en, 0);
13553             }
13554
13555             // Never use body as start or end node
13556             sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13557             sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
13558             en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13559             en = en.nodeName == "BODY" ? en.firstChild : en;
13560
13561             // Get start and end blocks
13562             sb = t.getParentBlock(sn);
13563             eb = t.getParentBlock(en);
13564             bn = sb ? sb.nodeName : se.element; // Get block name to create
13565
13566             // Return inside list use default browser behavior
69d05c 13567             if (n = t.dom.getParent(sb, 'li,pre')) {
A 13568                 if (n.nodeName == 'LI')
13569                     return splitList(ed.selection, t.dom, n);
13570
13571                 return TRUE;
13572             }
d9344f 13573
S 13574             // If caption or absolute layers then always generate new blocks within
29da64 13575             if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
d9344f 13576                 bn = se.element;
S 13577                 sb = null;
13578             }
13579
13580             // If caption or absolute layers then always generate new blocks within
29da64 13581             if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
d9344f 13582                 bn = se.element;
S 13583                 eb = null;
13584             }
13585
13586             // Use P instead
29da64 13587             if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
d9344f 13588                 bn = se.element;
S 13589                 sb = eb = null;
13590             }
13591
13592             // Setup new before and after blocks
13593             bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
13594             aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
13595
13596             // Remove id from after clone
13597             aft.removeAttribute('id');
13598
13599             // Is header and cursor is at the end, then force paragraph under
58fb65 13600             if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
d9344f 13601                 aft = ed.dom.create(se.element);
S 13602
13603             // Find start chop node
13604             n = sc = sn;
13605             do {
13606                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13607                     break;
13608
13609                 sc = n;
13610             } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
13611
13612             // Find end chop node
13613             n = ec = en;
13614             do {
13615                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13616                     break;
13617
13618                 ec = n;
13619             } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
13620
13621             // Place first chop part into before block element
13622             if (sc.nodeName == bn)
13623                 rb.setStart(sc, 0);
13624             else
13625                 rb.setStartBefore(sc);
13626
13627             rb.setEnd(sn, so);
13628             bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13629
13630             // Place secnd chop part within new block element
13631             try {
13632                 ra.setEndAfter(ec);
13633             } catch(ex) {
13634                 //console.debug(s.focusNode, s.focusOffset);
13635             }
13636
13637             ra.setStart(en, eo);
13638             aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13639
13640             // Create range around everything
13641             r = d.createRange();
13642             if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
13643                 r.setStartBefore(sc.parentNode);
13644             } else {
13645                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
13646                     r.setStartBefore(rb.startContainer);
13647                 else
13648                     r.setStart(rb.startContainer, rb.startOffset);
13649             }
13650
13651             if (!ec.nextSibling && ec.parentNode.nodeName == bn)
13652                 r.setEndAfter(ec.parentNode);
13653             else
13654                 r.setEnd(ra.endContainer, ra.endOffset);
13655
13656             // Delete and replace it with new block elements
13657             r.deleteContents();
13658
13659             if (isOpera)
13660                 ed.getWin().scrollTo(0, vp.y);
13661
13662             // Never wrap blocks in blocks
13663             if (bef.firstChild && bef.firstChild.nodeName == bn)
13664                 bef.innerHTML = bef.firstChild.innerHTML;
13665
13666             if (aft.firstChild && aft.firstChild.nodeName == bn)
13667                 aft.innerHTML = aft.firstChild.innerHTML;
13668
13669             // Padd empty blocks
a9251b 13670             if (dom.isEmpty(bef))
d9344f 13671                 bef.innerHTML = '<br />';
S 13672
29da64 13673             function appendStyles(e, en) {
A 13674                 var nl = [], nn, n, i;
13675
13676                 e.innerHTML = '';
13677
13678                 // Make clones of style elements
13679                 if (se.keep_styles) {
13680                     n = en;
13681                     do {
13682                         // We only want style specific elements
13683                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
69d05c 13684                             nn = n.cloneNode(FALSE);
29da64 13685                             dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
A 13686                             nl.push(nn);
13687                         }
13688                     } while (n = n.parentNode);
13689                 }
13690
13691                 // Append style elements to aft
13692                 if (nl.length > 0) {
13693                     for (i = nl.length - 1, nn = e; i >= 0; i--)
13694                         nn = nn.appendChild(nl[i]);
13695
13696                     // Padd most inner style element
a9251b 13697                     nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
29da64 13698                     return nl[0]; // Move caret to most inner element
A 13699                 } else
a9251b 13700                     e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
29da64 13701             };
A 13702
13703             // Fill empty afterblook with current style
a9251b 13704             if (dom.isEmpty(aft))
29da64 13705                 car = appendStyles(aft, en);
d9344f 13706
18240a 13707             // Opera needs this one backwards for older versions
A 13708             if (isOpera && parseFloat(opera.version()) < 9.5) {
d9344f 13709                 r.insertNode(bef);
S 13710                 r.insertNode(aft);
13711             } else {
13712                 r.insertNode(aft);
13713                 r.insertNode(bef);
13714             }
13715
13716             // Normalize
13717             aft.normalize();
13718             bef.normalize();
13719
18240a 13720             function first(n) {
69d05c 13721                 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
18240a 13722             };
A 13723
d9344f 13724             // Move cursor and scroll into view
S 13725             r = d.createRange();
29da64 13726             r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
d9344f 13727             r.collapse(1);
S 13728             s.removeAllRanges();
13729             s.addRange(r);
13730
13731             // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
13732             y = ed.dom.getPos(aft).y;
a9251b 13733             //ch = aft.clientHeight;
d9344f 13734
S 13735             // Is element within viewport
a9251b 13736             if (y < vp.y || y + 25 > vp.y + vp.h) {
29da64 13737                 ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
a9251b 13738
T 13739                 /*console.debug(
13740                     'Element: y=' + y + ', h=' + ch + ', ' +
13741                     'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
13742                 );*/
d9344f 13743             }
a9251b 13744
T 13745             ed.undoManager.add();
d9344f 13746
69d05c 13747             return FALSE;
d9344f 13748         },
S 13749
13750         backspaceDelete : function(e, bs) {
2011be 13751             var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
A 13752
13753             // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
13754             if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
13755                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
13756
13757                 // Walk the dom backwards until we find a text node
13758                 for (n = sc.lastChild; n; n = walker.prev()) {
13759                     if (n.nodeType == 3) {
13760                         r.setStart(n, n.nodeValue.length);
13761                         r.collapse(true);
13762                         se.setRng(r);
13763                         return;
13764                     }
13765                 }
13766             }
d9344f 13767
S 13768             // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
13769             // This workaround removes the element by hand and moves the caret to the previous element
13770             if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
13771                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
13772                     // Find previous block element
13773                     n = sc;
13774                     while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
13775
13776                     if (n) {
13777                         if (sc != b.firstChild) {
13778                             // Find last text node
69d05c 13779                             w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
d9344f 13780                             while (tn = w.nextNode())
S 13781                                 n = tn;
13782
13783                             // Place caret at the end of last text node
13784                             r = ed.getDoc().createRange();
13785                             r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
13786                             r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
13787                             se.setRng(r);
13788
13789                             // Remove the target container
13790                             ed.dom.remove(sc);
13791                         }
13792
13793                         return Event.cancel(e);
13794                     }
13795                 }
13796             }
13797         }
13798     });
29da64 13799 })(tinymce);
69d05c 13800
29da64 13801 (function(tinymce) {
d9344f 13802     // Shorten names
S 13803     var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
13804
13805     tinymce.create('tinymce.ControlManager', {
13806         ControlManager : function(ed, s) {
13807             var t = this, i;
13808
13809             s = s || {};
13810             t.editor = ed;
13811             t.controls = {};
13812             t.onAdd = new tinymce.util.Dispatcher(t);
13813             t.onPostRender = new tinymce.util.Dispatcher(t);
13814             t.prefix = s.prefix || ed.id + '_';
13815             t._cls = {};
13816
13817             t.onPostRender.add(function() {
13818                 each(t.controls, function(c) {
13819                     c.postRender();
13820                 });
13821             });
13822         },
13823
13824         get : function(id) {
13825             return this.controls[this.prefix + id] || this.controls[id];
13826         },
13827
13828         setActive : function(id, s) {
13829             var c = null;
13830
13831             if (c = this.get(id))
13832                 c.setActive(s);
13833
13834             return c;
13835         },
13836
13837         setDisabled : function(id, s) {
13838             var c = null;
13839
13840             if (c = this.get(id))
13841                 c.setDisabled(s);
13842
13843             return c;
13844         },
13845
13846         add : function(c) {
13847             var t = this;
13848
13849             if (c) {
13850                 t.controls[c.id] = c;
13851                 t.onAdd.dispatch(c, t);
13852             }
13853
13854             return c;
13855         },
13856
13857         createControl : function(n) {
13858             var c, t = this, ed = t.editor;
13859
13860             each(ed.plugins, function(p) {
13861                 if (p.createControl) {
13862                     c = p.createControl(n, t);
13863
13864                     if (c)
13865                         return false;
13866                 }
13867             });
13868
13869             switch (n) {
13870                 case "|":
13871                 case "separator":
13872                     return t.createSeparator();
13873             }
13874
13875             if (!c && ed.buttons && (c = ed.buttons[n]))
13876                 return t.createButton(n, c);
13877
13878             return t.add(c);
13879         },
13880
13881         createDropMenu : function(id, s, cc) {
13882             var t = this, ed = t.editor, c, bm, v, cls;
13883
13884             s = extend({
13885                 'class' : 'mceDropDown',
13886                 constrain : ed.settings.constrain_menus
13887             }, s);
13888
13889             s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
13890             if (v = ed.getParam('skin_variant'))
13891                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
13892
13893             id = t.prefix + id;
13894             cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
13895             c = t.controls[id] = new cls(id, s);
13896             c.onAddItem.add(function(c, o) {
13897                 var s = o.settings;
13898
13899                 s.title = ed.getLang(s.title, s.title);
13900
13901                 if (!s.onclick) {
13902                     s.onclick = function(v) {
69d05c 13903                         if (s.cmd)
A 13904                             ed.execCommand(s.cmd, s.ui || false, s.value);
d9344f 13905                     };
S 13906                 }
13907             });
13908
13909             ed.onRemove.add(function() {
13910                 c.destroy();
13911             });
13912
13913             // Fix for bug #1897785, #1898007
13914             if (tinymce.isIE) {
13915                 c.onShowMenu.add(function() {
29da64 13916                     // IE 8 needs focus in order to store away a range with the current collapsed caret location
A 13917                     ed.focus();
13918
18240a 13919                     bm = ed.selection.getBookmark(1);
d9344f 13920                 });
S 13921
13922                 c.onHideMenu.add(function() {
29da64 13923                     if (bm) {
d9344f 13924                         ed.selection.moveToBookmark(bm);
29da64 13925                         bm = 0;
A 13926                     }
d9344f 13927                 });
S 13928             }
13929
13930             return t.add(c);
13931         },
13932
13933         createListBox : function(id, s, cc) {
13934             var t = this, ed = t.editor, cmd, c, cls;
13935
13936             if (t.get(id))
13937                 return null;
13938
13939             s.title = ed.translate(s.title);
13940             s.scope = s.scope || ed;
13941
13942             if (!s.onselect) {
13943                 s.onselect = function(v) {
13944                     ed.execCommand(s.cmd, s.ui || false, v || s.value);
13945                 };
13946             }
13947
13948             s = extend({
13949                 title : s.title,
13950                 'class' : 'mce_' + id,
13951                 scope : s.scope,
13952                 control_manager : t
13953             }, s);
13954
13955             id = t.prefix + id;
13956
13957             if (ed.settings.use_native_selects)
13958                 c = new tinymce.ui.NativeListBox(id, s);
13959             else {
13960                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
a9251b 13961                 c = new cls(id, s, ed);
d9344f 13962             }
S 13963
13964             t.controls[id] = c;
13965
13966             // Fix focus problem in Safari
13967             if (tinymce.isWebKit) {
13968                 c.onPostRender.add(function(c, n) {
13969                     // Store bookmark on mousedown
13970                     Event.add(n, 'mousedown', function() {
58fb65 13971                         ed.bookmark = ed.selection.getBookmark(1);
d9344f 13972                     });
S 13973
13974                     // Restore on focus, since it might be lost
13975                     Event.add(n, 'focus', function() {
13976                         ed.selection.moveToBookmark(ed.bookmark);
13977                         ed.bookmark = null;
13978                     });
13979                 });
13980             }
13981
13982             if (c.hideMenu)
13983                 ed.onMouseDown.add(c.hideMenu, c);
13984
13985             return t.add(c);
13986         },
13987
13988         createButton : function(id, s, cc) {
13989             var t = this, ed = t.editor, o, c, cls;
13990
13991             if (t.get(id))
13992                 return null;
13993
13994             s.title = ed.translate(s.title);
18240a 13995             s.label = ed.translate(s.label);
d9344f 13996             s.scope = s.scope || ed;
S 13997
13998             if (!s.onclick && !s.menu_button) {
13999                 s.onclick = function() {
14000                     ed.execCommand(s.cmd, s.ui || false, s.value);
14001                 };
14002             }
14003
14004             s = extend({
14005                 title : s.title,
14006                 'class' : 'mce_' + id,
14007                 unavailable_prefix : ed.getLang('unavailable', ''),
14008                 scope : s.scope,
14009                 control_manager : t
14010             }, s);
14011
14012             id = t.prefix + id;
14013
14014             if (s.menu_button) {
14015                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
a9251b 14016                 c = new cls(id, s, ed);
d9344f 14017                 ed.onMouseDown.add(c.hideMenu, c);
S 14018             } else {
14019                 cls = t._cls.button || tinymce.ui.Button;
14020                 c = new cls(id, s);
14021             }
14022
14023             return t.add(c);
14024         },
14025
29da64 14026         createMenuButton : function(id, s, cc) {
d9344f 14027             s = s || {};
S 14028             s.menu_button = 1;
14029
29da64 14030             return this.createButton(id, s, cc);
d9344f 14031         },
S 14032
14033         createSplitButton : function(id, s, cc) {
14034             var t = this, ed = t.editor, cmd, c, cls;
14035
14036             if (t.get(id))
14037                 return null;
14038
14039             s.title = ed.translate(s.title);
14040             s.scope = s.scope || ed;
14041
14042             if (!s.onclick) {
14043                 s.onclick = function(v) {
14044                     ed.execCommand(s.cmd, s.ui || false, v || s.value);
14045                 };
14046             }
14047
14048             if (!s.onselect) {
14049                 s.onselect = function(v) {
14050                     ed.execCommand(s.cmd, s.ui || false, v || s.value);
14051                 };
14052             }
14053
14054             s = extend({
14055                 title : s.title,
14056                 'class' : 'mce_' + id,
14057                 scope : s.scope,
14058                 control_manager : t
14059             }, s);
14060
14061             id = t.prefix + id;
14062             cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
a9251b 14063             c = t.add(new cls(id, s, ed));
d9344f 14064             ed.onMouseDown.add(c.hideMenu, c);
S 14065
14066             return c;
14067         },
14068
14069         createColorSplitButton : function(id, s, cc) {
18240a 14070             var t = this, ed = t.editor, cmd, c, cls, bm;
d9344f 14071
S 14072             if (t.get(id))
14073                 return null;
14074
14075             s.title = ed.translate(s.title);
14076             s.scope = s.scope || ed;
14077
14078             if (!s.onclick) {
14079                 s.onclick = function(v) {
29da64 14080                     if (tinymce.isIE)
A 14081                         bm = ed.selection.getBookmark(1);
58fb65 14082
d9344f 14083                     ed.execCommand(s.cmd, s.ui || false, v || s.value);
S 14084                 };
14085             }
14086
14087             if (!s.onselect) {
14088                 s.onselect = function(v) {
14089                     ed.execCommand(s.cmd, s.ui || false, v || s.value);
14090                 };
14091             }
14092
14093             s = extend({
14094                 title : s.title,
14095                 'class' : 'mce_' + id,
14096                 'menu_class' : ed.getParam('skin') + 'Skin',
14097                 scope : s.scope,
14098                 more_colors_title : ed.getLang('more_colors')
14099             }, s);
14100
14101             id = t.prefix + id;
14102             cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
a9251b 14103             c = new cls(id, s, ed);
d9344f 14104             ed.onMouseDown.add(c.hideMenu, c);
S 14105
14106             // Remove the menu element when the editor is removed
14107             ed.onRemove.add(function() {
14108                 c.destroy();
14109             });
18240a 14110
A 14111             // Fix for bug #1897785, #1898007
14112             if (tinymce.isIE) {
58fb65 14113                 c.onShowMenu.add(function() {
A 14114                     // IE 8 needs focus in order to store away a range with the current collapsed caret location
14115                     ed.focus();
14116                     bm = ed.selection.getBookmark(1);
14117                 });
14118
18240a 14119                 c.onHideMenu.add(function() {
A 14120                     if (bm) {
14121                         ed.selection.moveToBookmark(bm);
14122                         bm = 0;
14123                     }
14124                 });
14125             }
d9344f 14126
S 14127             return t.add(c);
14128         },
14129
14130         createToolbar : function(id, s, cc) {
14131             var c, t = this, cls;
14132
14133             id = t.prefix + id;
14134             cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
a9251b 14135             c = new cls(id, s, t.editor);
d9344f 14136
S 14137             if (t.get(id))
14138                 return null;
14139
a9251b 14140             return t.add(c);
T 14141         },
14142         
14143         createToolbarGroup : function(id, s, cc) {
14144             var c, t = this, cls;
14145             id = t.prefix + id;
14146             cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14147             c = new cls(id, s, t.editor);
14148             
14149             if (t.get(id))
14150                 return null;
14151             
d9344f 14152             return t.add(c);
S 14153         },
14154
14155         createSeparator : function(cc) {
14156             var cls = cc || this._cls.separator || tinymce.ui.Separator;
14157
14158             return new cls();
14159         },
14160
14161         setControlType : function(n, c) {
14162             return this._cls[n.toLowerCase()] = c;
14163         },
58fb65 14164     
d9344f 14165         destroy : function() {
S 14166             each(this.controls, function(c) {
14167                 c.destroy();
14168             });
14169
14170             this.controls = null;
14171         }
58fb65 14172     });
29da64 14173 })(tinymce);
69d05c 14174
29da64 14175 (function(tinymce) {
d9344f 14176     var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
S 14177
14178     tinymce.create('tinymce.WindowManager', {
14179         WindowManager : function(ed) {
14180             var t = this;
14181
14182             t.editor = ed;
14183             t.onOpen = new Dispatcher(t);
14184             t.onClose = new Dispatcher(t);
14185             t.params = {};
14186             t.features = {};
14187         },
14188
14189         open : function(s, p) {
14190             var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14191
14192             // Default some options
14193             s = s || {};
14194             p = p || {};
14195             sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14196             sh = isOpera ? vp.h : screen.height;
14197             s.name = s.name || 'mc_' + new Date().getTime();
14198             s.width = parseInt(s.width || 320);
14199             s.height = parseInt(s.height || 240);
14200             s.resizable = true;
14201             s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14202             s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14203             p.inline = false;
14204             p.mce_width = s.width;
14205             p.mce_height = s.height;
14206             p.mce_auto_focus = s.auto_focus;
14207
14208             if (mo) {
14209                 if (isIE) {
14210                     s.center = true;
14211                     s.help = false;
14212                     s.dialogWidth = s.width + 'px';
14213                     s.dialogHeight = s.height + 'px';
14214                     s.scroll = s.scrollbars || false;
18240a 14215                 }
d9344f 14216             }
S 14217
14218             // Build features string
14219             each(s, function(v, k) {
14220                 if (tinymce.is(v, 'boolean'))
14221                     v = v ? 'yes' : 'no';
14222
14223                 if (!/^(name|url)$/.test(k)) {
14224                     if (isIE && mo)
14225                         f += (f ? ';' : '') + k + ':' + v;
14226                     else
14227                         f += (f ? ',' : '') + k + '=' + v;
14228                 }
14229             });
14230
14231             t.features = s;
14232             t.params = p;
14233             t.onOpen.dispatch(t, s, p);
14234
14235             u = s.url || s.file;
18240a 14236             u = tinymce._addVer(u);
A 14237
d9344f 14238             try {
S 14239                 if (isIE && mo) {
14240                     w = 1;
18240a 14241                     window.showModalDialog(u, window, f);
d9344f 14242                 } else
S 14243                     w = window.open(u, s.name, f);
14244             } catch (ex) {
14245                 // Ignore
14246             }
14247
14248             if (!w)
14249                 alert(t.editor.getLang('popup_blocked'));
14250         },
14251
14252         close : function(w) {
14253             w.close();
14254             this.onClose.dispatch(this);
14255         },
14256
14257         createInstance : function(cl, a, b, c, d, e) {
14258             var f = tinymce.resolve(cl);
14259
14260             return new f(a, b, c, d, e);
14261         },
14262
29da64 14263         confirm : function(t, cb, s, w) {
A 14264             w = w || window;
14265
14266             cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
d9344f 14267         },
S 14268
29da64 14269         alert : function(tx, cb, s, w) {
d9344f 14270             var t = this;
29da64 14271
A 14272             w = w || window;
14273             w.alert(t._decode(t.editor.getLang(tx, tx)));
d9344f 14274
S 14275             if (cb)
14276                 cb.call(s || t);
14277         },
14278
69d05c 14279         resizeBy : function(dw, dh, win) {
A 14280             win.resizeBy(dw, dh);
14281         },
14282
d9344f 14283         // Internal functions
S 14284
14285         _decode : function(s) {
14286             return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14287         }
58fb65 14288     });
69d05c 14289 }(tinymce));
A 14290 (function(tinymce) {
14291     tinymce.Formatter = function(ed) {
14292         var formats = {},
14293             each = tinymce.each,
14294             dom = ed.dom,
14295             selection = ed.selection,
14296             TreeWalker = tinymce.dom.TreeWalker,
14297             rangeUtils = new tinymce.dom.RangeUtils(dom),
a9251b 14298             isValid = ed.schema.isValidChild,
69d05c 14299             isBlock = dom.isBlock,
A 14300             forcedRootBlock = ed.settings.forced_root_block,
14301             nodeIndex = dom.nodeIndex,
14302             INVISIBLE_CHAR = '\uFEFF',
14303             MCE_ATTR_RE = /^(src|href|style)$/,
14304             FALSE = false,
14305             TRUE = true,
14306             undefined,
14307             pendingFormats = {apply : [], remove : []};
29da64 14308
799907 14309         function isArray(obj) {
A 14310             return obj instanceof Array;
14311         };
14312
69d05c 14313         function getParents(node, selector) {
A 14314             return dom.getParents(node, selector, dom.getRoot());
29da64 14315         };
A 14316
69d05c 14317         function isCaretNode(node) {
A 14318             return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
14319         };
29da64 14320
69d05c 14321         // Public functions
29da64 14322
69d05c 14323         function get(name) {
A 14324             return name ? formats[name] : formats;
14325         };
29da64 14326
69d05c 14327         function register(name, format) {
A 14328             if (name) {
14329                 if (typeof(name) !== 'string') {
14330                     each(name, function(format, name) {
14331                         register(name, format);
14332                     });
29da64 14333                 } else {
69d05c 14334                     // Force format into array and add it to internal collection
A 14335                     format = format.length ? format : [format];
29da64 14336
69d05c 14337                     each(format, function(format) {
A 14338                         // Set deep to false by default on selector formats this to avoid removing
14339                         // alignment on images inside paragraphs when alignment is changed on paragraphs
14340                         if (format.deep === undefined)
14341                             format.deep = !format.selector;
14342
14343                         // Default to true
14344                         if (format.split === undefined)
14345                             format.split = !format.selector || format.inline;
14346
14347                         // Default to true
14348                         if (format.remove === undefined && format.selector && !format.inline)
14349                             format.remove = 'none';
14350
14351                         // Mark format as a mixed format inline + block level
14352                         if (format.selector && format.inline) {
14353                             format.mixed = true;
14354                             format.block_expand = true;
14355                         }
14356
14357                         // Split classes if needed
14358                         if (typeof(format.classes) === 'string')
14359                             format.classes = format.classes.split(/\s+/);
14360                     });
14361
14362                     formats[name] = format;
14363                 }
14364             }
14365         };
14366
a9251b 14367         var getTextDecoration = function(node) {
T 14368             var decoration;
14369
14370             ed.dom.getParent(node, function(n) {
14371                 decoration = ed.dom.getStyle(n, 'text-decoration');
14372                 return decoration && decoration !== 'none';
14373             });
14374
14375             return decoration;
14376         };
14377
14378         var processUnderlineAndColor = function(node) {
14379             var textDecoration;
14380             if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14381                 textDecoration = getTextDecoration(node.parentNode);
14382                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
14383                     ed.dom.setStyle(node, 'text-decoration', textDecoration);
14384                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14385                     ed.dom.setStyle(node, 'text-decoration', null);
14386                 }
14387             }
14388         };
14389
69d05c 14390         function apply(name, vars, node) {
a9251b 14391             var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
69d05c 14392
A 14393             function moveStart(rng) {
14394                 var container = rng.startContainer,
14395                     offset = rng.startOffset,
14396                     walker, node;
14397
14398                 // Move startContainer/startOffset in to a suitable node
14399                 if (container.nodeType == 1 || container.nodeValue === "") {
2011be 14400                     container = container.nodeType == 1 ? container.childNodes[offset] : container;
A 14401
14402                     // Might fail if the offset is behind the last element in it's container
14403                     if (container) {
14404                         walker = new TreeWalker(container, container.parentNode);
14405                         for (node = walker.current(); node; node = walker.next()) {
14406                             if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14407                                 rng.setStart(node, 0);
14408                                 break;
14409                             }
69d05c 14410                         }
29da64 14411                     }
A 14412                 }
14413
69d05c 14414                 return rng;
A 14415             };
29da64 14416
69d05c 14417             function setElementFormat(elm, fmt) {
A 14418                 fmt = fmt || format;
29da64 14419
69d05c 14420                 if (elm) {
A 14421                     each(fmt.styles, function(value, name) {
14422                         dom.setStyle(elm, name, replaceVars(value, vars));
14423                     });
29da64 14424
69d05c 14425                     each(fmt.attributes, function(value, name) {
A 14426                         dom.setAttrib(elm, name, replaceVars(value, vars));
14427                     });
29da64 14428
69d05c 14429                     each(fmt.classes, function(value) {
A 14430                         value = replaceVars(value, vars);
14431
14432                         if (!dom.hasClass(elm, value))
14433                             dom.addClass(elm, value);
14434                     });
14435                 }
14436             };
14437
14438             function applyRngStyle(rng) {
14439                 var newWrappers = [], wrapName, wrapElm;
14440
14441                 // Setup wrapper element
14442                 wrapName = format.inline || format.block;
14443                 wrapElm = dom.create(wrapName);
14444                 setElementFormat(wrapElm);
14445
14446                 rangeUtils.walk(rng, function(nodes) {
14447                     var currentWrapElm;
14448
14449                     function process(node) {
14450                         var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
14451
14452                         // Stop wrapping on br elements
14453                         if (isEq(nodeName, 'br')) {
14454                             currentWrapElm = 0;
14455
14456                             // Remove any br elements when we wrap things
14457                             if (format.block)
14458                                 dom.remove(node);
14459
14460                             return;
14461                         }
14462
14463                         // If node is wrapper type
14464                         if (format.wrapper && matchNode(node, name, vars)) {
14465                             currentWrapElm = 0;
14466                             return;
14467                         }
14468
14469                         // Can we rename the block
14470                         if (format.block && !format.wrapper && isTextBlock(nodeName)) {
14471                             node = dom.rename(node, wrapName);
14472                             setElementFormat(node);
14473                             newWrappers.push(node);
14474                             currentWrapElm = 0;
14475                             return;
14476                         }
14477
14478                         // Handle selector patterns
14479                         if (format.selector) {
14480                             // Look for matching formats
14481                             each(formatList, function(format) {
a9251b 14482                                 // Check collapsed state if it exists
T 14483                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
14484                                     return;
14485                                 }
14486
69d05c 14487                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
A 14488                                     setElementFormat(node, format);
14489                                     found = true;
14490                                 }
14491                             });
14492
2011be 14493                             // Continue processing if a selector match wasn't found and a inline element is defined
69d05c 14494                             if (!format.inline || found) {
A 14495                                 currentWrapElm = 0;
14496                                 return;
14497                             }
14498                         }
14499
14500                         // Is it valid to wrap this item
a9251b 14501                         if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
T 14502                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
69d05c 14503                             // Start wrapping
A 14504                             if (!currentWrapElm) {
14505                                 // Wrap the node
14506                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14507                                 node.parentNode.insertBefore(currentWrapElm, node);
14508                                 newWrappers.push(currentWrapElm);
14509                             }
14510
14511                             currentWrapElm.appendChild(node);
14512                         } else {
14513                             // Start a new wrapper for possible children
14514                             currentWrapElm = 0;
14515
14516                             each(tinymce.grep(node.childNodes), process);
14517
14518                             // End the last wrapper
14519                             currentWrapElm = 0;
14520                         }
14521                     };
14522
14523                     // Process siblings from range
14524                     each(nodes, process);
14525                 });
a9251b 14526
T 14527                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
14528                 if (format.wrap_links === false) {
14529                     each(newWrappers, function(node) {
14530                         function process(node) {
14531                             var i, currentWrapElm, children;
14532
14533                             if (node.nodeName === 'A') {
14534                                 currentWrapElm = wrapElm.cloneNode(FALSE);
14535                                 newWrappers.push(currentWrapElm);
14536
14537                                 children = tinymce.grep(node.childNodes);
14538                                 for (i = 0; i < children.length; i++)
14539                                     currentWrapElm.appendChild(children[i]);
14540
14541                                 node.appendChild(currentWrapElm);
14542                             }
14543
14544                             each(tinymce.grep(node.childNodes), process);
14545                         };
14546
14547                         process(node);
14548                     });
14549                 }
69d05c 14550
A 14551                 // Cleanup
14552                 each(newWrappers, function(node) {
14553                     var childCount;
14554
14555                     function getChildCount(node) {
14556                         var count = 0;
14557
14558                         each(node.childNodes, function(node) {
14559                             if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
14560                                 count++;
14561                         });
14562
14563                         return count;
14564                     };
14565
14566                     function mergeStyles(node) {
14567                         var child, clone;
14568
14569                         each(node.childNodes, function(node) {
14570                             if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
14571                                 child = node;
14572                                 return FALSE; // break loop
14573                             }
14574                         });
14575
14576                         // If child was found and of the same type as the current node
14577                         if (child && matchName(child, format)) {
14578                             clone = child.cloneNode(FALSE);
14579                             setElementFormat(clone);
14580
14581                             dom.replace(clone, node, TRUE);
14582                             dom.remove(child, 1);
14583                         }
14584
14585                         return clone || node;
14586                     };
14587
14588                     childCount = getChildCount(node);
14589
a9251b 14590                     // Remove empty nodes but only if there is multiple wrappers and they are not block
T 14591                     // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
14592                     if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
69d05c 14593                         dom.remove(node, 1);
A 14594                         return;
14595                     }
14596
14597                     if (format.inline || format.wrapper) {
14598                         // Merges the current node with it's children of similar type to reduce the number of elements
14599                         if (!format.exact && childCount === 1)
14600                             node = mergeStyles(node);
14601
14602                         // Remove/merge children
14603                         each(formatList, function(format) {
14604                             // Merge all children of similar type will move styles from child to parent
14605                             // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
14606                             // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
14607                             each(dom.select(format.inline, node), function(child) {
a9251b 14608                                 var parent;
T 14609
14610                                 // When wrap_links is set to false we don't want
14611                                 // to remove the format on children within links
14612                                 if (format.wrap_links === false) {
14613                                     parent = child.parentNode;
14614
14615                                     do {
14616                                         if (parent.nodeName === 'A')
14617                                             return;
14618                                     } while (parent = parent.parentNode);
14619                                 }
14620
69d05c 14621                                 removeFormat(format, vars, child, format.exact ? child : null);
A 14622                             });
14623                         });
14624
2011be 14625                         // Remove child if direct parent is of same type
A 14626                         if (matchNode(node.parentNode, name, vars)) {
14627                             dom.remove(node, 1);
14628                             node = 0;
14629                             return TRUE;
14630                         }
14631
69d05c 14632                         // Look for parent with similar style format
2011be 14633                         if (format.merge_with_parents) {
A 14634                             dom.getParent(node.parentNode, function(parent) {
14635                                 if (matchNode(parent, name, vars)) {
14636                                     dom.remove(node, 1);
14637                                     node = 0;
14638                                     return TRUE;
14639                                 }
14640                             });
14641                         }
69d05c 14642
A 14643                         // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
14644                         if (node) {
14645                             node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
14646                             node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
14647                         }
14648                     }
14649                 });
14650             };
14651
14652             if (format) {
14653                 if (node) {
14654                     rng = dom.createRng();
14655
14656                     rng.setStartBefore(node);
14657                     rng.setEndAfter(node);
14658
2011be 14659                     applyRngStyle(expandRng(rng, formatList));
69d05c 14660                 } else {
a9251b 14661                     if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
T 14662                         // Obtain selection node before selection is unselected by applyRngStyle()
14663                         var curSelNode = ed.selection.getNode();
14664
69d05c 14665                         // Apply formatting to selection
A 14666                         bookmark = selection.getBookmark();
14667                         applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
a9251b 14668
T 14669                         // Colored nodes should be underlined so that the color of the underline matches the text color.
14670                         if (format.styles && (format.styles.color || format.styles.textDecoration)) {
14671                             tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
14672                             processUnderlineAndColor(curSelNode);
14673                         }
69d05c 14674
A 14675                         selection.moveToBookmark(bookmark);
14676                         selection.setRng(moveStart(selection.getRng(TRUE)));
14677                         ed.nodeChanged();
14678                     } else
14679                         performCaretAction('apply', name, vars);
14680                 }
29da64 14681             }
69d05c 14682         };
29da64 14683
69d05c 14684         function remove(name, vars, node) {
A 14685             var formatList = get(name), format = formatList[0], bookmark, i, rng;
a9251b 14686
T 14687             function moveStart(rng) {
14688                 var container = rng.startContainer,
14689                     offset = rng.startOffset,
14690                     walker, node, nodes, tmpNode;
14691
14692                 // Convert text node into index if possible
14693                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
14694                     container = container.parentNode;
14695                     offset = nodeIndex(container) + 1;
14696                 }
14697
14698                 // Move startContainer/startOffset in to a suitable node
14699                 if (container.nodeType == 1) {
14700                     nodes = container.childNodes;
14701                     container = nodes[Math.min(offset, nodes.length - 1)];
14702                     walker = new TreeWalker(container);
14703
14704                     // If offset is at end of the parent node walk to the next one
14705                     if (offset > nodes.length - 1)
14706                         walker.next();
14707
14708                     for (node = walker.current(); node; node = walker.next()) {
14709                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14710                             // IE has a "neat" feature where it moves the start node into the closest element
14711                             // we can avoid this by inserting an element before it and then remove it after we set the selection
14712                             tmpNode = dom.create('a', null, INVISIBLE_CHAR);
14713                             node.parentNode.insertBefore(tmpNode, node);
14714
14715                             // Set selection and remove tmpNode
14716                             rng.setStart(node, 0);
14717                             selection.setRng(rng);
14718                             dom.remove(tmpNode);
14719
14720                             return;
14721                         }
14722                     }
14723                 }
14724             };
29da64 14725
69d05c 14726             // Merges the styles for each node
A 14727             function process(node) {
14728                 var children, i, l;
29da64 14729
69d05c 14730                 // Grab the children first since the nodelist might be changed
A 14731                 children = tinymce.grep(node.childNodes);
14732
14733                 // Process current node
14734                 for (i = 0, l = formatList.length; i < l; i++) {
14735                     if (removeFormat(formatList[i], vars, node, node))
14736                         break;
29da64 14737                 }
A 14738
69d05c 14739                 // Process the children
A 14740                 if (format.deep) {
14741                     for (i = 0, l = children.length; i < l; i++)
14742                         process(children[i]);
14743                 }
14744             };
14745
14746             function findFormatRoot(container) {
14747                 var formatRoot;
14748
14749                 // Find format root
14750                 each(getParents(container.parentNode).reverse(), function(parent) {
14751                     var format;
14752
14753                     // Find format root element
14754                     if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
14755                         // Is the node matching the format we are looking for
14756                         format = matchNode(parent, name, vars);
14757                         if (format && format.split !== false)
14758                             formatRoot = parent;
14759                     }
14760                 });
14761
14762                 return formatRoot;
14763             };
14764
14765             function wrapAndSplit(format_root, container, target, split) {
14766                 var parent, clone, lastClone, firstClone, i, formatRootParent;
14767
14768                 // Format root found then clone formats and split it
14769                 if (format_root) {
14770                     formatRootParent = format_root.parentNode;
14771
14772                     for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14773                         clone = parent.cloneNode(FALSE);
14774
14775                         for (i = 0; i < formatList.length; i++) {
14776                             if (removeFormat(formatList[i], vars, clone, clone)) {
14777                                 clone = 0;
14778                                 break;
14779                             }
14780                         }
14781
14782                         // Build wrapper node
14783                         if (clone) {
14784                             if (lastClone)
14785                                 clone.appendChild(lastClone);
14786
14787                             if (!firstClone)
14788                                 firstClone = clone;
14789
14790                             lastClone = clone;
14791                         }
14792                     }
14793
14794                     // Never split block elements if the format is mixed
14795                     if (split && (!format.mixed || !isBlock(format_root)))
14796                         container = dom.split(format_root, container);
14797
14798                     // Wrap container in cloned formats
14799                     if (lastClone) {
14800                         target.parentNode.insertBefore(lastClone, target);
14801                         firstClone.appendChild(target);
14802                     }
14803                 }
14804
14805                 return container;
14806             };
14807
14808             function splitToFormatRoot(container) {
14809                 return wrapAndSplit(findFormatRoot(container), container, container, true);
14810             };
14811
14812             function unwrap(start) {
14813                 var node = dom.get(start ? '_start' : '_end'),
14814                     out = node[start ? 'firstChild' : 'lastChild'];
14815
2011be 14816                 // If the end is placed within the start the result will be removed
A 14817                 // So this checks if the out node is a bookmark node if it is it
14818                 // checks for another more suitable node
14819                 if (isBookmarkNode(out))
14820                     out = out[start ? 'firstChild' : 'lastChild'];
14821
14822                 dom.remove(node, true);
69d05c 14823
A 14824                 return out;
14825             };
14826
14827             function removeRngStyle(rng) {
14828                 var startContainer, endContainer;
14829
14830                 rng = expandRng(rng, formatList, TRUE);
14831
14832                 if (format.split) {
14833                     startContainer = getContainer(rng, TRUE);
14834                     endContainer = getContainer(rng);
14835
14836                     if (startContainer != endContainer) {
14837                         // Wrap start/end nodes in span element since these might be cloned/moved
a9251b 14838                         startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
T 14839                         endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
69d05c 14840
A 14841                         // Split start/end
14842                         splitToFormatRoot(startContainer);
14843                         splitToFormatRoot(endContainer);
14844
14845                         // Unwrap start/end to get real elements again
14846                         startContainer = unwrap(TRUE);
14847                         endContainer = unwrap();
14848                     } else
14849                         startContainer = endContainer = splitToFormatRoot(startContainer);
14850
14851                     // Update range positions since they might have changed after the split operations
14852                     rng.startContainer = startContainer.parentNode;
14853                     rng.startOffset = nodeIndex(startContainer);
14854                     rng.endContainer = endContainer.parentNode;
14855                     rng.endOffset = nodeIndex(endContainer) + 1;
14856                 }
14857
14858                 // Remove items between start/end
14859                 rangeUtils.walk(rng, function(nodes) {
14860                     each(nodes, function(node) {
14861                         process(node);
a9251b 14862
T 14863                         // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14864                         if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14865                             removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
14866                         }
69d05c 14867                     });
A 14868                 });
14869             };
14870
14871             // Handle node
14872             if (node) {
14873                 rng = dom.createRng();
14874                 rng.setStartBefore(node);
14875                 rng.setEndAfter(node);
14876                 removeRngStyle(rng);
29da64 14877                 return;
A 14878             }
14879
a9251b 14880             if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
69d05c 14881                 bookmark = selection.getBookmark();
A 14882                 removeRngStyle(selection.getRng(TRUE));
14883                 selection.moveToBookmark(bookmark);
a9251b 14884
T 14885                 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
14886                 if (match(name, vars, selection.getStart())) {
14887                     moveStart(selection.getRng(true));
14888                 }
14889
69d05c 14890                 ed.nodeChanged();
A 14891             } else
14892                 performCaretAction('remove', name, vars);
14893         };
14894
14895         function toggle(name, vars, node) {
a9251b 14896             var fmt = get(name);
T 14897
14898             if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
69d05c 14899                 remove(name, vars, node);
A 14900             else
14901                 apply(name, vars, node);
14902         };
14903
2011be 14904         function matchNode(node, name, vars, similar) {
69d05c 14905             var formatList = get(name), format, i, classes;
A 14906
14907             function matchItems(node, format, item_name) {
14908                 var key, value, items = format[item_name], i;
14909
14910                 // Check all items
14911                 if (items) {
14912                     // Non indexed object
14913                     if (items.length === undefined) {
14914                         for (key in items) {
14915                             if (items.hasOwnProperty(key)) {
14916                                 if (item_name === 'attributes')
14917                                     value = dom.getAttrib(node, key);
14918                                 else
14919                                     value = getStyle(node, key);
14920
2011be 14921                                 if (similar && !value && !format.exact)
A 14922                                     return;
14923
14924                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
69d05c 14925                                     return;
A 14926                             }
14927                         }
14928                     } else {
14929                         // Only one match needed for indexed arrays
14930                         for (i = 0; i < items.length; i++) {
14931                             if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
14932                                 return format;
14933                         }
14934                     }
14935                 }
14936
14937                 return format;
14938             };
14939
14940             if (formatList && node) {
14941                 // Check each format in list
14942                 for (i = 0; i < formatList.length; i++) {
14943                     format = formatList[i];
14944
14945                     // Name name, attributes, styles and classes
14946                     if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14947                         // Match classes
14948                         if (classes = format.classes) {
14949                             for (i = 0; i < classes.length; i++) {
14950                                 if (!dom.hasClass(node, classes[i]))
14951                                     return;
14952                             }
14953                         }
14954
14955                         return format;
14956                     }
14957                 }
14958             }
14959         };
14960
14961         function match(name, vars, node) {
14962             var startNode, i;
14963
14964             function matchParents(node) {
14965                 // Find first node with similar format settings
14966                 node = dom.getParent(node, function(node) {
2011be 14967                     return !!matchNode(node, name, vars, true);
69d05c 14968                 });
A 14969
14970                 // Do an exact check on the similar format element
14971                 return matchNode(node, name, vars);
14972             };
14973
14974             // Check specified node
14975             if (node)
14976                 return matchParents(node);
14977
14978             // Check pending formats
14979             if (selection.isCollapsed()) {
14980                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14981                     if (pendingFormats.apply[i].name == name)
14982                         return true;
14983                 }
14984
14985                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14986                     if (pendingFormats.remove[i].name == name)
14987                         return false;
14988                 }
14989
14990                 return matchParents(selection.getNode());
14991             }
14992
14993             // Check selected node
14994             node = selection.getNode();
14995             if (matchParents(node))
14996                 return TRUE;
14997
14998             // Check start node if it's different
14999             startNode = selection.getStart();
15000             if (startNode != node) {
15001                 if (matchParents(startNode))
15002                     return TRUE;
15003             }
15004
15005             return FALSE;
15006         };
15007
799907 15008         function matchAll(names, vars) {
A 15009             var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15010
15011             // If the selection is collapsed then check pending formats
15012             if (selection.isCollapsed()) {
15013                 for (ni = 0; ni < names.length; ni++) {
15014                     // If the name is to be removed, then stop it from being added
15015                     for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15016                         name = names[ni];
15017
15018                         if (pendingFormats.remove[i].name == name) {
15019                             checkedMap[name] = true;
15020                             break;
15021                         }
15022                     }
15023                 }
15024
15025                 // If the format is to be applied
15026                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15027                     for (ni = 0; ni < names.length; ni++) {
15028                         name = names[ni];
15029
15030                         if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
15031                             checkedMap[name] = true;
15032                             matchedFormatNames.push(name);
15033                         }
15034                     }
15035                 }
15036             }
15037
15038             // Check start of selection for formats
15039             startElement = selection.getStart();
15040             dom.getParent(startElement, function(node) {
15041                 var i, name;
15042
15043                 for (i = 0; i < names.length; i++) {
15044                     name = names[i];
15045
15046                     if (!checkedMap[name] && matchNode(node, name, vars)) {
15047                         checkedMap[name] = true;
15048                         matchedFormatNames.push(name);
15049                     }
15050                 }
15051             });
15052
15053             return matchedFormatNames;
15054         };
15055
69d05c 15056         function canApply(name) {
A 15057             var formatList = get(name), startNode, parents, i, x, selector;
15058
15059             if (formatList) {
15060                 startNode = selection.getStart();
15061                 parents = getParents(startNode);
15062
15063                 for (x = formatList.length - 1; x >= 0; x--) {
15064                     selector = formatList[x].selector;
15065
15066                     // Format is not selector based, then always return TRUE
15067                     if (!selector)
15068                         return TRUE;
15069
15070                     for (i = parents.length - 1; i >= 0; i--) {
15071                         if (dom.is(parents[i], selector))
15072                             return TRUE;
15073                     }
15074                 }
15075             }
15076
15077             return FALSE;
15078         };
15079
15080         // Expose to public
15081         tinymce.extend(this, {
15082             get : get,
15083             register : register,
15084             apply : apply,
15085             remove : remove,
15086             toggle : toggle,
15087             match : match,
799907 15088             matchAll : matchAll,
69d05c 15089             matchNode : matchNode,
A 15090             canApply : canApply
29da64 15091         });
A 15092
69d05c 15093         // Private functions
A 15094
15095         function matchName(node, format) {
15096             // Check for inline match
15097             if (isEq(node, format.inline))
15098                 return TRUE;
15099
15100             // Check for block match
15101             if (isEq(node, format.block))
15102                 return TRUE;
15103
15104             // Check for selector match
15105             if (format.selector)
15106                 return dom.is(node, format.selector);
15107         };
15108
15109         function isEq(str1, str2) {
15110             str1 = str1 || '';
15111             str2 = str2 || '';
15112
799907 15113             str1 = '' + (str1.nodeName || str1);
A 15114             str2 = '' + (str2.nodeName || str2);
69d05c 15115
A 15116             return str1.toLowerCase() == str2.toLowerCase();
15117         };
15118
15119         function getStyle(node, name) {
15120             var styleVal = dom.getStyle(node, name);
15121
15122             // Force the format to hex
15123             if (name == 'color' || name == 'backgroundColor')
15124                 styleVal = dom.toHex(styleVal);
15125
15126             // Opera will return bold as 700
15127             if (name == 'fontWeight' && styleVal == 700)
15128                 styleVal = 'bold';
15129
15130             return '' + styleVal;
15131         };
15132
15133         function replaceVars(value, vars) {
15134             if (typeof(value) != "string")
15135                 value = value(vars);
15136             else if (vars) {
15137                 value = value.replace(/%(\w+)/g, function(str, name) {
15138                     return vars[name] || str;
15139                 });
29da64 15140             }
A 15141
69d05c 15142             return value;
A 15143         };
29da64 15144
69d05c 15145         function isWhiteSpaceNode(node) {
2011be 15146             return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
69d05c 15147         };
A 15148
15149         function wrap(node, name, attrs) {
15150             var wrapper = dom.create(name, attrs);
15151
15152             node.parentNode.insertBefore(wrapper, node);
15153             wrapper.appendChild(node);
15154
15155             return wrapper;
15156         };
15157
15158         function expandRng(rng, format, remove) {
15159             var startContainer = rng.startContainer,
15160                 startOffset = rng.startOffset,
15161                 endContainer = rng.endContainer,
a9251b 15162                 endOffset = rng.endOffset, sibling, lastIdx, leaf;
69d05c 15163
A 15164             // This function walks up the tree if there is no siblings before/after the node
15165             function findParentContainer(container, child_name, sibling_name, root) {
15166                 var parent, child;
15167
15168                 root = root || dom.getRoot();
15169
15170                 for (;;) {
15171                     // Check if we can move up are we at root level or body level
15172                     parent = container.parentNode;
15173
15174                     // Stop expanding on block elements or root depending on format
15175                     if (parent == root || (!format[0].block_expand && isBlock(parent)))
15176                         return container;
15177
15178                     for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
15179                         if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15180                             return container;
15181
15182                         if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
15183                             return container;
15184                     }
15185
15186                     container = container.parentNode;
15187                 }
15188
15189                 return container;
15190             };
15191
a9251b 15192             // This function walks down the tree to find the leaf at the selection.
T 15193             // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15194             function findLeaf(node, offset) {
15195                 if (offset === undefined)
15196                     offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15197                 while (node && node.hasChildNodes()) {
15198                     node = node.childNodes[offset];
15199                     if (node)
15200                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15201                 }
15202                 return { node: node, offset: offset };
15203             }
15204
69d05c 15205             // If index based start position then resolve it
A 15206             if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15207                 lastIdx = startContainer.childNodes.length - 1;
15208                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15209
15210                 if (startContainer.nodeType == 3)
15211                     startOffset = 0;
15212             }
15213
15214             // If index based end position then resolve it
15215             if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15216                 lastIdx = endContainer.childNodes.length - 1;
15217                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15218
15219                 if (endContainer.nodeType == 3)
15220                     endOffset = endContainer.nodeValue.length;
15221             }
15222
15223             // Exclude bookmark nodes if possible
15224             if (isBookmarkNode(startContainer.parentNode))
15225                 startContainer = startContainer.parentNode;
15226
15227             if (isBookmarkNode(startContainer))
15228                 startContainer = startContainer.nextSibling || startContainer;
15229
a9251b 15230             if (isBookmarkNode(endContainer.parentNode)) {
T 15231                 endOffset = dom.nodeIndex(endContainer);
69d05c 15232                 endContainer = endContainer.parentNode;
a9251b 15233             }
69d05c 15234
a9251b 15235             if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
T 15236                 endContainer = endContainer.previousSibling;
15237                 endOffset = endContainer.length;
15238             }
69d05c 15239
a9251b 15240             if (format[0].inline) {
T 15241                 // Avoid applying formatting to a trailing space.
15242                 leaf = findLeaf(endContainer, endOffset);
15243                 if (leaf.node) {
15244                     while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
15245                         leaf = findLeaf(leaf.node.previousSibling);
15246
15247                     if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15248                             leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15249
15250                         if (leaf.offset > 1) {
15251                             endContainer = leaf.node;
15252                             endContainer.splitText(leaf.offset - 1);
15253                         } else if (leaf.node.previousSibling) {
15254                             endContainer = leaf.node.previousSibling;
15255                         }
15256                     }
15257                 }
15258             }
15259             
69d05c 15260             // Move start/end point up the tree if the leaves are sharp and if we are in different containers
A 15261             // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15262             // This will reduce the number of wrapper elements that needs to be created
15263             // Move start point up the tree
15264             if (format[0].inline || format[0].block_expand) {
15265                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15266                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15267             }
15268
15269             // Expand start/end container to matching selector
15270             if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15271                 function findSelectorEndPoint(container, sibling_name) {
a9251b 15272                     var parents, i, y, curFormat;
69d05c 15273
A 15274                     if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
15275                         container = container[sibling_name];
15276
15277                     parents = getParents(container);
15278                     for (i = 0; i < parents.length; i++) {
15279                         for (y = 0; y < format.length; y++) {
a9251b 15280                             curFormat = format[y];
T 15281
15282                             // If collapsed state is set then skip formats that doesn't match that
15283                             if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
15284                                 continue;
15285
15286                             if (dom.is(parents[i], curFormat.selector))
69d05c 15287                                 return parents[i];
A 15288                         }
15289                     }
15290
15291                     return container;
15292                 };
15293
15294                 // Find new startContainer/endContainer if there is better one
15295                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15296                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15297             }
15298
15299             // Expand start/end container to matching block element or text node
15300             if (format[0].block || format[0].selector) {
15301                 function findBlockEndPoint(container, sibling_name, sibling_name2) {
15302                     var node;
15303
15304                     // Expand to block of similar type
15305                     if (!format[0].wrapper)
15306                         node = dom.getParent(container, format[0].block);
15307
15308                     // Expand to first wrappable block element or any block element
15309                     if (!node)
15310                         node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
15311
15312                     // Exclude inner lists from wrapping
15313                     if (node && format[0].wrapper)
15314                         node = getParents(node, 'ul,ol').reverse()[0] || node;
15315
15316                     // Didn't find a block element look for first/last wrappable element
15317                     if (!node) {
15318                         node = container;
15319
15320                         while (node[sibling_name] && !isBlock(node[sibling_name])) {
15321                             node = node[sibling_name];
15322
15323                             // Break on BR but include it will be removed later on
15324                             // we can't remove it now since we need to check if it can be wrapped
15325                             if (isEq(node, 'br'))
15326                                 break;
15327                         }
15328                     }
15329
15330                     return node || container;
15331                 };
15332
15333                 // Find new startContainer/endContainer if there is better one
15334                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15335                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15336
15337                 // Non block element then try to expand up the leaf
15338                 if (format[0].block) {
15339                     if (!isBlock(startContainer))
15340                         startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15341
15342                     if (!isBlock(endContainer))
15343                         endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15344                 }
15345             }
15346
15347             // Setup index for startContainer
15348             if (startContainer.nodeType == 1) {
15349                 startOffset = nodeIndex(startContainer);
15350                 startContainer = startContainer.parentNode;
15351             }
15352
15353             // Setup index for endContainer
15354             if (endContainer.nodeType == 1) {
15355                 endOffset = nodeIndex(endContainer) + 1;
15356                 endContainer = endContainer.parentNode;
15357             }
15358
15359             // Return new range like object
15360             return {
15361                 startContainer : startContainer,
15362                 startOffset : startOffset,
15363                 endContainer : endContainer,
15364                 endOffset : endOffset
15365             };
15366         }
15367
15368         function removeFormat(format, vars, node, compare_node) {
15369             var i, attrs, stylesModified;
15370
15371             // Check if node matches format
15372             if (!matchName(node, format))
15373                 return FALSE;
15374
15375             // Should we compare with format attribs and styles
15376             if (format.remove != 'all') {
15377                 // Remove styles
15378                 each(format.styles, function(value, name) {
15379                     value = replaceVars(value, vars);
15380
15381                     // Indexed array
15382                     if (typeof(name) === 'number') {
15383                         name = value;
15384                         compare_node = 0;
15385                     }
15386
15387                     if (!compare_node || isEq(getStyle(compare_node, name), value))
15388                         dom.setStyle(node, name, '');
15389
15390                     stylesModified = 1;
15391                 });
15392
15393                 // Remove style attribute if it's empty
15394                 if (stylesModified && dom.getAttrib(node, 'style') == '') {
15395                     node.removeAttribute('style');
a9251b 15396                     node.removeAttribute('data-mce-style');
69d05c 15397                 }
A 15398
15399                 // Remove attributes
15400                 each(format.attributes, function(value, name) {
15401                     var valueOut;
15402
15403                     value = replaceVars(value, vars);
15404
15405                     // Indexed array
15406                     if (typeof(name) === 'number') {
15407                         name = value;
15408                         compare_node = 0;
15409                     }
15410
15411                     if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15412                         // Keep internal classes
15413                         if (name == 'class') {
15414                             value = dom.getAttrib(node, name);
15415                             if (value) {
15416                                 // Build new class value where everything is removed except the internal prefixed classes
15417                                 valueOut = '';
15418                                 each(value.split(/\s+/), function(cls) {
15419                                     if (/mce\w+/.test(cls))
15420                                         valueOut += (valueOut ? ' ' : '') + cls;
15421                                 });
15422
15423                                 // We got some internal classes left
15424                                 if (valueOut) {
15425                                     dom.setAttrib(node, name, valueOut);
15426                                     return;
15427                                 }
15428                             }
15429                         }
15430
15431                         // IE6 has a bug where the attribute doesn't get removed correctly
15432                         if (name == "class")
15433                             node.removeAttribute('className');
15434
15435                         // Remove mce prefixed attributes
15436                         if (MCE_ATTR_RE.test(name))
a9251b 15437                             node.removeAttribute('data-mce-' + name);
69d05c 15438
A 15439                         node.removeAttribute(name);
15440                     }
15441                 });
15442
15443                 // Remove classes
15444                 each(format.classes, function(value) {
15445                     value = replaceVars(value, vars);
15446
15447                     if (!compare_node || dom.hasClass(compare_node, value))
15448                         dom.removeClass(node, value);
15449                 });
15450
15451                 // Check for non internal attributes
15452                 attrs = dom.getAttribs(node);
15453                 for (i = 0; i < attrs.length; i++) {
15454                     if (attrs[i].nodeName.indexOf('_') !== 0)
15455                         return FALSE;
15456                 }
15457             }
15458
15459             // Remove the inline child if it's empty for example <b> or <span>
15460             if (format.remove != 'none') {
15461                 removeNode(node, format);
15462                 return TRUE;
15463             }
15464         };
15465
15466         function removeNode(node, format) {
15467             var parentNode = node.parentNode, rootBlockElm;
15468
15469             if (format.block) {
15470                 if (!forcedRootBlock) {
15471                     function find(node, next, inc) {
15472                         node = getNonWhiteSpaceSibling(node, next, inc);
15473
15474                         return !node || (node.nodeName == 'BR' || isBlock(node));
15475                     };
15476
15477                     // Append BR elements if needed before we remove the block
15478                     if (isBlock(node) && !isBlock(parentNode)) {
15479                         if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
15480                             node.insertBefore(dom.create('br'), node.firstChild);
15481
15482                         if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
15483                             node.appendChild(dom.create('br'));
15484                     }
15485                 } else {
15486                     // Wrap the block in a forcedRootBlock if we are at the root of document
15487                     if (parentNode == dom.getRoot()) {
15488                         if (!format.list_block || !isEq(node, format.list_block)) {
15489                             each(tinymce.grep(node.childNodes), function(node) {
15490                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15491                                     if (!rootBlockElm)
15492                                         rootBlockElm = wrap(node, forcedRootBlock);
15493                                     else
15494                                         rootBlockElm.appendChild(node);
15495                                 } else
15496                                     rootBlockElm = 0;
15497                             });
15498                         }
15499                     }
15500                 }
15501             }
15502
15503             // Never remove nodes that isn't the specified inline element if a selector is specified too
15504             if (format.selector && format.inline && !isEq(format.inline, node))
15505                 return;
15506
15507             dom.remove(node, 1);
15508         };
15509
15510         function getNonWhiteSpaceSibling(node, next, inc) {
15511             if (node) {
15512                 next = next ? 'nextSibling' : 'previousSibling';
15513
15514                 for (node = inc ? node : node[next]; node; node = node[next]) {
15515                     if (node.nodeType == 1 || !isWhiteSpaceNode(node))
15516                         return node;
15517                 }
15518             }
15519         };
15520
15521         function isBookmarkNode(node) {
a9251b 15522             return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
69d05c 15523         };
A 15524
15525         function mergeSiblings(prev, next) {
15526             var marker, sibling, tmpSibling;
15527
15528             function compareElements(node1, node2) {
15529                 // Not the same name
15530                 if (node1.nodeName != node2.nodeName)
15531                     return FALSE;
15532
15533                 function getAttribs(node) {
15534                     var attribs = {};
15535
15536                     each(dom.getAttribs(node), function(attr) {
15537                         var name = attr.nodeName.toLowerCase();
15538
15539                         // Don't compare internal attributes or style
15540                         if (name.indexOf('_') !== 0 && name !== 'style')
15541                             attribs[name] = dom.getAttrib(node, name);
58fb65 15542                     });
69d05c 15543
A 15544                     return attribs;
15545                 };
15546
15547                 function compareObjects(obj1, obj2) {
15548                     var value, name;
15549
15550                     for (name in obj1) {
15551                         // Obj1 has item obj2 doesn't have
15552                         if (obj1.hasOwnProperty(name)) {
15553                             value = obj2[name];
15554
15555                             // Obj2 doesn't have obj1 item
15556                             if (value === undefined)
15557                                 return FALSE;
15558
15559                             // Obj2 item has a different value
15560                             if (obj1[name] != value)
15561                                 return FALSE;
15562
15563                             // Delete similar value
15564                             delete obj2[name];
15565                         }
15566                     }
15567
15568                     // Check if obj 2 has something obj 1 doesn't have
15569                     for (name in obj2) {
15570                         // Obj2 has item obj1 doesn't have
15571                         if (obj2.hasOwnProperty(name))
15572                             return FALSE;
15573                     }
15574
15575                     return TRUE;
15576                 };
15577
15578                 // Attribs are not the same
15579                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
15580                     return FALSE;
15581
15582                 // Styles are not the same
15583                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
15584                     return FALSE;
15585
15586                 return TRUE;
15587             };
15588
15589             // Check if next/prev exists and that they are elements
15590             if (prev && next) {
15591                 function findElementSibling(node, sibling_name) {
15592                     for (sibling = node; sibling; sibling = sibling[sibling_name]) {
a9251b 15593                         if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
69d05c 15594                             return node;
A 15595
15596                         if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15597                             return sibling;
15598                     }
15599
15600                     return node;
15601                 };
15602
15603                 // If previous sibling is empty then jump over it
15604                 prev = findElementSibling(prev, 'previousSibling');
15605                 next = findElementSibling(next, 'nextSibling');
15606
15607                 // Compare next and previous nodes
15608                 if (compareElements(prev, next)) {
15609                     // Append nodes between
15610                     for (sibling = prev.nextSibling; sibling && sibling != next;) {
15611                         tmpSibling = sibling;
15612                         sibling = sibling.nextSibling;
15613                         prev.appendChild(tmpSibling);
15614                     }
15615
15616                     // Remove next node
15617                     dom.remove(next);
15618
15619                     // Move children into prev node
15620                     each(tinymce.grep(next.childNodes), function(node) {
15621                         prev.appendChild(node);
15622                     });
15623
15624                     return prev;
15625                 }
29da64 15626             }
69d05c 15627
A 15628             return next;
15629         };
15630
15631         function isTextBlock(name) {
2011be 15632             return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
69d05c 15633         };
A 15634
15635         function getContainer(rng, start) {
15636             var container, offset, lastIdx;
15637
15638             container = rng[start ? 'startContainer' : 'endContainer'];
15639             offset = rng[start ? 'startOffset' : 'endOffset'];
15640
15641             if (container.nodeType == 1) {
15642                 lastIdx = container.childNodes.length - 1;
15643
15644                 if (!start && offset)
15645                     offset--;
15646
15647                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
15648             }
15649
15650             return container;
15651         };
15652
15653         function performCaretAction(type, name, vars) {
15654             var i, currentPendingFormats = pendingFormats[type],
15655                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
15656
15657             function hasPending() {
15658                 return pendingFormats.apply.length || pendingFormats.remove.length;
15659             };
15660
15661             function resetPending() {
15662                 pendingFormats.apply = [];
15663                 pendingFormats.remove = [];
15664             };
15665
15666             function perform(caret_node) {
15667                 // Apply pending formats
15668                 each(pendingFormats.apply.reverse(), function(item) {
15669                     apply(item.name, item.vars, caret_node);
a9251b 15670
T 15671                     // Colored nodes should be underlined so that the color of the underline matches the text color.
15672                     if (item.name === 'forecolor' && item.vars.value)
15673                         processUnderlineAndColor(caret_node.parentNode);
69d05c 15674                 });
A 15675
15676                 // Remove pending formats
15677                 each(pendingFormats.remove.reverse(), function(item) {
15678                     remove(item.name, item.vars, caret_node);
15679                 });
15680
15681                 dom.remove(caret_node, 1);
15682                 resetPending();
15683             };
15684
15685             // Check if it already exists then ignore it
15686             for (i = currentPendingFormats.length - 1; i >= 0; i--) {
15687                 if (currentPendingFormats[i].name == name)
15688                     return;
15689             }
15690
15691             currentPendingFormats.push({name : name, vars : vars});
15692
15693             // Check if it's in the other type, then remove it
15694             for (i = otherPendingFormats.length - 1; i >= 0; i--) {
15695                 if (otherPendingFormats[i].name == name)
15696                     otherPendingFormats.splice(i, 1);
15697             }
15698
15699             // Pending apply or remove formats
15700             if (hasPending()) {
15701                 ed.getDoc().execCommand('FontName', false, 'mceinline');
2011be 15702                 pendingFormats.lastRng = selection.getRng();
69d05c 15703
A 15704                 // IE will convert the current word
15705                 each(dom.select('font,span'), function(node) {
15706                     var bookmark;
15707
15708                     if (isCaretNode(node)) {
15709                         bookmark = selection.getBookmark();
15710                         perform(node);
15711                         selection.moveToBookmark(bookmark);
15712                         ed.nodeChanged();
15713                     }
15714                 });
15715
15716                 // Only register listeners once if we need to
15717                 if (!pendingFormats.isListening && hasPending()) {
15718                     pendingFormats.isListening = true;
15719
15720                     each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
15721                         ed[event].addToTop(function(ed, e) {
2011be 15722                             // Do we have pending formats and is the selection moved has moved
A 15723                             if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
69d05c 15724                                 each(dom.select('font,span'), function(node) {
2011be 15725                                     var textNode, rng;
69d05c 15726
A 15727                                     // Look for marker
15728                                     if (isCaretNode(node)) {
15729                                         textNode = node.firstChild;
15730
2011be 15731                                         if (textNode) {
A 15732                                             perform(node);
69d05c 15733
2011be 15734                                             rng = dom.createRng();
A 15735                                             rng.setStart(textNode, textNode.nodeValue.length);
15736                                             rng.setEnd(textNode, textNode.nodeValue.length);
15737                                             selection.setRng(rng);
15738                                             ed.nodeChanged();
15739                                         } else
15740                                             dom.remove(node);
69d05c 15741                                     }
A 15742                                 });
15743
15744                                 // Always unbind and clear pending styles on keyup
15745                                 if (e.type == 'keyup' || e.type == 'mouseup')
15746                                     resetPending();
15747                             }
15748                         });
15749                     });
15750                 }
15751             }
15752         };
15753     };
15754 })(tinymce);
15755
15756 tinymce.onAddEditor.add(function(tinymce, ed) {
15757     var filters, fontSizes, dom, settings = ed.settings;
15758
15759     if (settings.inline_styles) {
15760         fontSizes = tinymce.explode(settings.font_size_style_values);
15761
15762         function replaceWithSpan(node, styles) {
a9251b 15763             tinymce.each(styles, function(value, name) {
T 15764                 if (value)
15765                     dom.setStyle(node, name, value);
15766             });
15767
15768             dom.rename(node, 'span');
69d05c 15769         };
A 15770
15771         filters = {
15772             font : function(dom, node) {
15773                 replaceWithSpan(node, {
15774                     backgroundColor : node.style.backgroundColor,
15775                     color : node.color,
15776                     fontFamily : node.face,
15777                     fontSize : fontSizes[parseInt(node.size) - 1]
15778                 });
15779             },
15780
15781             u : function(dom, node) {
15782                 replaceWithSpan(node, {
15783                     textDecoration : 'underline'
15784                 });
15785             },
15786
15787             strike : function(dom, node) {
15788                 replaceWithSpan(node, {
15789                     textDecoration : 'line-through'
15790                 });
15791             }
15792         };
15793
15794         function convert(editor, params) {
15795             dom = editor.dom;
15796
15797             if (settings.convert_fonts_to_spans) {
15798                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
15799                     filters[node.nodeName.toLowerCase()](ed.dom, node);
15800                 });
15801             }
15802         };
15803
15804         ed.onPreProcess.add(convert);
a9251b 15805         ed.onSetContent.add(convert);
69d05c 15806
A 15807         ed.onInit.add(function() {
15808             ed.selection.onSetContent.add(convert);
29da64 15809         });
69d05c 15810     }
A 15811 });
29da64 15812