comparison static/js/tiny_mce/tiny_mce_src.js @ 312:88b2b9cb8c1f

Fixing #142; cut over to the django.contrib.staticfiles app.
author Brian Neal <bgneal@gmail.com>
date Thu, 27 Jan 2011 02:56:10 +0000
parents
children 6c182ceb7147
comparison
equal deleted inserted replaced
311:b1c39788e511 312:88b2b9cb8c1f
1 (function(win) {
2 var whiteSpaceRe = /^\s*|\s*$/g,
3 undefined;
4
5 var tinymce = {
6 majorVersion : '3',
7
8 minorVersion : '3.9',
9
10 releaseDate : '2010-09-08',
11
12 _init : function() {
13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
14
15 t.isOpera = win.opera && opera.buildNumber;
16
17 t.isWebKit = /WebKit/.test(ua);
18
19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
20
21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
22
23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
24
25 t.isMac = ua.indexOf('Mac') != -1;
26
27 t.isAir = /adobeair/i.test(ua);
28
29 t.isIDevice = /(iPad|iPhone)/.test(ua);
30
31 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
32 if (win.tinyMCEPreInit) {
33 t.suffix = tinyMCEPreInit.suffix;
34 t.baseURL = tinyMCEPreInit.base;
35 t.query = tinyMCEPreInit.query;
36 return;
37 }
38
39 // Get suffix and base
40 t.suffix = '';
41
42 // If base element found, add that infront of baseURL
43 nl = d.getElementsByTagName('base');
44 for (i=0; i<nl.length; i++) {
45 if (v = nl[i].href) {
46 // Host only value like http://site.com or http://site.com:8008
47 if (/^https?:\/\/[^\/]+$/.test(v))
48 v += '/';
49
50 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
51 }
52 }
53
54 function getBase(n) {
55 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
56 if (/_(src|dev)\.js/g.test(n.src))
57 t.suffix = '_src';
58
59 if ((p = n.src.indexOf('?')) != -1)
60 t.query = n.src.substring(p + 1);
61
62 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
63
64 // If path to script is relative and a base href was found add that one infront
65 // the src property will always be an absolute one on non IE browsers and IE 8
66 // so this logic will basically only be executed on older IE versions
67 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
68 t.baseURL = base + t.baseURL;
69
70 return t.baseURL;
71 }
72
73 return null;
74 };
75
76 // Check document
77 nl = d.getElementsByTagName('script');
78 for (i=0; i<nl.length; i++) {
79 if (getBase(nl[i]))
80 return;
81 }
82
83 // Check head
84 n = d.getElementsByTagName('head')[0];
85 if (n) {
86 nl = n.getElementsByTagName('script');
87 for (i=0; i<nl.length; i++) {
88 if (getBase(nl[i]))
89 return;
90 }
91 }
92
93 return;
94 },
95
96 is : function(o, t) {
97 if (!t)
98 return o !== undefined;
99
100 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
101 return true;
102
103 return typeof(o) == t;
104 },
105
106 each : function(o, cb, s) {
107 var n, l;
108
109 if (!o)
110 return 0;
111
112 s = s || o;
113
114 if (o.length !== undefined) {
115 // Indexed arrays, needed for Safari
116 for (n=0, l = o.length; n < l; n++) {
117 if (cb.call(s, o[n], n, o) === false)
118 return 0;
119 }
120 } else {
121 // Hashtables
122 for (n in o) {
123 if (o.hasOwnProperty(n)) {
124 if (cb.call(s, o[n], n, o) === false)
125 return 0;
126 }
127 }
128 }
129
130 return 1;
131 },
132
133
134 map : function(a, f) {
135 var o = [];
136
137 tinymce.each(a, function(v) {
138 o.push(f(v));
139 });
140
141 return o;
142 },
143
144 grep : function(a, f) {
145 var o = [];
146
147 tinymce.each(a, function(v) {
148 if (!f || f(v))
149 o.push(v);
150 });
151
152 return o;
153 },
154
155 inArray : function(a, v) {
156 var i, l;
157
158 if (a) {
159 for (i = 0, l = a.length; i < l; i++) {
160 if (a[i] === v)
161 return i;
162 }
163 }
164
165 return -1;
166 },
167
168 extend : function(o, e) {
169 var i, l, a = arguments;
170
171 for (i = 1, l = a.length; i < l; i++) {
172 e = a[i];
173
174 tinymce.each(e, function(v, n) {
175 if (v !== undefined)
176 o[n] = v;
177 });
178 }
179
180 return o;
181 },
182
183
184 trim : function(s) {
185 return (s ? '' + s : '').replace(whiteSpaceRe, '');
186 },
187
188 create : function(s, p) {
189 var t = this, sp, ns, cn, scn, c, de = 0;
190
191 // Parse : <prefix> <class>:<super class>
192 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
193 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
194
195 // Create namespace for new class
196 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
197
198 // Class already exists
199 if (ns[cn])
200 return;
201
202 // Make pure static class
203 if (s[2] == 'static') {
204 ns[cn] = p;
205
206 if (this.onCreate)
207 this.onCreate(s[2], s[3], ns[cn]);
208
209 return;
210 }
211
212 // Create default constructor
213 if (!p[cn]) {
214 p[cn] = function() {};
215 de = 1;
216 }
217
218 // Add constructor and methods
219 ns[cn] = p[cn];
220 t.extend(ns[cn].prototype, p);
221
222 // Extend
223 if (s[5]) {
224 sp = t.resolve(s[5]).prototype;
225 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
226
227 // Extend constructor
228 c = ns[cn];
229 if (de) {
230 // Add passthrough constructor
231 ns[cn] = function() {
232 return sp[scn].apply(this, arguments);
233 };
234 } else {
235 // Add inherit constructor
236 ns[cn] = function() {
237 this.parent = sp[scn];
238 return c.apply(this, arguments);
239 };
240 }
241 ns[cn].prototype[cn] = ns[cn];
242
243 // Add super methods
244 t.each(sp, function(f, n) {
245 ns[cn].prototype[n] = sp[n];
246 });
247
248 // Add overridden methods
249 t.each(p, function(f, n) {
250 // Extend methods if needed
251 if (sp[n]) {
252 ns[cn].prototype[n] = function() {
253 this.parent = sp[n];
254 return f.apply(this, arguments);
255 };
256 } else {
257 if (n != cn)
258 ns[cn].prototype[n] = f;
259 }
260 });
261 }
262
263 // Add static methods
264 t.each(p['static'], function(f, n) {
265 ns[cn][n] = f;
266 });
267
268 if (this.onCreate)
269 this.onCreate(s[2], s[3], ns[cn].prototype);
270 },
271
272 walk : function(o, f, n, s) {
273 s = s || this;
274
275 if (o) {
276 if (n)
277 o = o[n];
278
279 tinymce.each(o, function(o, i) {
280 if (f.call(s, o, i, n) === false)
281 return false;
282
283 tinymce.walk(o, f, n, s);
284 });
285 }
286 },
287
288 createNS : function(n, o) {
289 var i, v;
290
291 o = o || win;
292
293 n = n.split('.');
294 for (i=0; i<n.length; i++) {
295 v = n[i];
296
297 if (!o[v])
298 o[v] = {};
299
300 o = o[v];
301 }
302
303 return o;
304 },
305
306 resolve : function(n, o) {
307 var i, l;
308
309 o = o || win;
310
311 n = n.split('.');
312 for (i = 0, l = n.length; i < l; i++) {
313 o = o[n[i]];
314
315 if (!o)
316 break;
317 }
318
319 return o;
320 },
321
322 addUnload : function(f, s) {
323 var t = this;
324
325 f = {func : f, scope : s || this};
326
327 if (!t.unloads) {
328 function unload() {
329 var li = t.unloads, o, n;
330
331 if (li) {
332 // Call unload handlers
333 for (n in li) {
334 o = li[n];
335
336 if (o && o.func)
337 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
338 }
339
340 // Detach unload function
341 if (win.detachEvent) {
342 win.detachEvent('onbeforeunload', fakeUnload);
343 win.detachEvent('onunload', unload);
344 } else if (win.removeEventListener)
345 win.removeEventListener('unload', unload, false);
346
347 // Destroy references
348 t.unloads = o = li = w = unload = 0;
349
350 // Run garbarge collector on IE
351 if (win.CollectGarbage)
352 CollectGarbage();
353 }
354 };
355
356 function fakeUnload() {
357 var d = document;
358
359 // Is there things still loading, then do some magic
360 if (d.readyState == 'interactive') {
361 function stop() {
362 // Prevent memory leak
363 d.detachEvent('onstop', stop);
364
365 // Call unload handler
366 if (unload)
367 unload();
368
369 d = 0;
370 };
371
372 // Fire unload when the currently loading page is stopped
373 if (d)
374 d.attachEvent('onstop', stop);
375
376 // Remove onstop listener after a while to prevent the unload function
377 // to execute if the user presses cancel in an onbeforeunload
378 // confirm dialog and then presses the browser stop button
379 win.setTimeout(function() {
380 if (d)
381 d.detachEvent('onstop', stop);
382 }, 0);
383 }
384 };
385
386 // Attach unload handler
387 if (win.attachEvent) {
388 win.attachEvent('onunload', unload);
389 win.attachEvent('onbeforeunload', fakeUnload);
390 } else if (win.addEventListener)
391 win.addEventListener('unload', unload, false);
392
393 // Setup initial unload handler array
394 t.unloads = [f];
395 } else
396 t.unloads.push(f);
397
398 return f;
399 },
400
401 removeUnload : function(f) {
402 var u = this.unloads, r = null;
403
404 tinymce.each(u, function(o, i) {
405 if (o && o.func == f) {
406 u.splice(i, 1);
407 r = f;
408 return false;
409 }
410 });
411
412 return r;
413 },
414
415 explode : function(s, d) {
416 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
417 },
418
419 _addVer : function(u) {
420 var v;
421
422 if (!this.query)
423 return u;
424
425 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
426
427 if (u.indexOf('#') == -1)
428 return u + v;
429
430 return u.replace('#', v + '#');
431 }
432
433 };
434
435 // Initialize the API
436 tinymce._init();
437
438 // Expose tinymce namespace to the global namespace (window)
439 win.tinymce = win.tinyMCE = tinymce;
440 })(window);
441
442
443 tinymce.create('tinymce.util.Dispatcher', {
444 scope : null,
445 listeners : null,
446
447 Dispatcher : function(s) {
448 this.scope = s || this;
449 this.listeners = [];
450 },
451
452 add : function(cb, s) {
453 this.listeners.push({cb : cb, scope : s || this.scope});
454
455 return cb;
456 },
457
458 addToTop : function(cb, s) {
459 this.listeners.unshift({cb : cb, scope : s || this.scope});
460
461 return cb;
462 },
463
464 remove : function(cb) {
465 var l = this.listeners, o = null;
466
467 tinymce.each(l, function(c, i) {
468 if (cb == c.cb) {
469 o = cb;
470 l.splice(i, 1);
471 return false;
472 }
473 });
474
475 return o;
476 },
477
478 dispatch : function() {
479 var s, a = arguments, i, li = this.listeners, c;
480
481 // Needs to be a real loop since the listener count might change while looping
482 // And this is also more efficient
483 for (i = 0; i<li.length; i++) {
484 c = li[i];
485 s = c.cb.apply(c.scope, a);
486
487 if (s === false)
488 break;
489 }
490
491 return s;
492 }
493
494 });
495
496 (function() {
497 var each = tinymce.each;
498
499 tinymce.create('tinymce.util.URI', {
500 URI : function(u, s) {
501 var t = this, o, a, b;
502
503 // Trim whitespace
504 u = tinymce.trim(u);
505
506 // Default settings
507 s = t.settings = s || {};
508
509 // Strange app protocol or local anchor
510 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
511 t.source = u;
512 return;
513 }
514
515 // Absolute path with no host, fake host and protocol
516 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
517 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
518
519 // Relative path http:// or protocol relative //path
520 if (!/^\w*:?\/\//.test(u))
521 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
522
523 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
524 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
525 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
526 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
527 var s = u[i];
528
529 // Zope 3 workaround, they use @@something
530 if (s)
531 s = s.replace(/\(mce_at\)/g, '@@');
532
533 t[v] = s;
534 });
535
536 if (b = s.base_uri) {
537 if (!t.protocol)
538 t.protocol = b.protocol;
539
540 if (!t.userInfo)
541 t.userInfo = b.userInfo;
542
543 if (!t.port && t.host == 'mce_host')
544 t.port = b.port;
545
546 if (!t.host || t.host == 'mce_host')
547 t.host = b.host;
548
549 t.source = '';
550 }
551
552 //t.path = t.path || '/';
553 },
554
555 setPath : function(p) {
556 var t = this;
557
558 p = /^(.*?)\/?(\w+)?$/.exec(p);
559
560 // Update path parts
561 t.path = p[0];
562 t.directory = p[1];
563 t.file = p[2];
564
565 // Rebuild source
566 t.source = '';
567 t.getURI();
568 },
569
570 toRelative : function(u) {
571 var t = this, o;
572
573 if (u === "./")
574 return u;
575
576 u = new tinymce.util.URI(u, {base_uri : t});
577
578 // Not on same domain/port or protocol
579 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
580 return u.getURI();
581
582 o = t.toRelPath(t.path, u.path);
583
584 // Add query
585 if (u.query)
586 o += '?' + u.query;
587
588 // Add anchor
589 if (u.anchor)
590 o += '#' + u.anchor;
591
592 return o;
593 },
594
595 toAbsolute : function(u, nh) {
596 var u = new tinymce.util.URI(u, {base_uri : this});
597
598 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
599 },
600
601 toRelPath : function(base, path) {
602 var items, bp = 0, out = '', i, l;
603
604 // Split the paths
605 base = base.substring(0, base.lastIndexOf('/'));
606 base = base.split('/');
607 items = path.split('/');
608
609 if (base.length >= items.length) {
610 for (i = 0, l = base.length; i < l; i++) {
611 if (i >= items.length || base[i] != items[i]) {
612 bp = i + 1;
613 break;
614 }
615 }
616 }
617
618 if (base.length < items.length) {
619 for (i = 0, l = items.length; i < l; i++) {
620 if (i >= base.length || base[i] != items[i]) {
621 bp = i + 1;
622 break;
623 }
624 }
625 }
626
627 if (bp == 1)
628 return path;
629
630 for (i = 0, l = base.length - (bp - 1); i < l; i++)
631 out += "../";
632
633 for (i = bp - 1, l = items.length; i < l; i++) {
634 if (i != bp - 1)
635 out += "/" + items[i];
636 else
637 out += items[i];
638 }
639
640 return out;
641 },
642
643 toAbsPath : function(base, path) {
644 var i, nb = 0, o = [], tr, outPath;
645
646 // Split paths
647 tr = /\/$/.test(path) ? '/' : '';
648 base = base.split('/');
649 path = path.split('/');
650
651 // Remove empty chunks
652 each(base, function(k) {
653 if (k)
654 o.push(k);
655 });
656
657 base = o;
658
659 // Merge relURLParts chunks
660 for (i = path.length - 1, o = []; i >= 0; i--) {
661 // Ignore empty or .
662 if (path[i].length == 0 || path[i] == ".")
663 continue;
664
665 // Is parent
666 if (path[i] == '..') {
667 nb++;
668 continue;
669 }
670
671 // Move up
672 if (nb > 0) {
673 nb--;
674 continue;
675 }
676
677 o.push(path[i]);
678 }
679
680 i = base.length - nb;
681
682 // If /a/b/c or /
683 if (i <= 0)
684 outPath = o.reverse().join('/');
685 else
686 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
687
688 // Add front / if it's needed
689 if (outPath.indexOf('/') !== 0)
690 outPath = '/' + outPath;
691
692 // Add traling / if it's needed
693 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
694 outPath += tr;
695
696 return outPath;
697 },
698
699 getURI : function(nh) {
700 var s, t = this;
701
702 // Rebuild source
703 if (!t.source || nh) {
704 s = '';
705
706 if (!nh) {
707 if (t.protocol)
708 s += t.protocol + '://';
709
710 if (t.userInfo)
711 s += t.userInfo + '@';
712
713 if (t.host)
714 s += t.host;
715
716 if (t.port)
717 s += ':' + t.port;
718 }
719
720 if (t.path)
721 s += t.path;
722
723 if (t.query)
724 s += '?' + t.query;
725
726 if (t.anchor)
727 s += '#' + t.anchor;
728
729 t.source = s;
730 }
731
732 return t.source;
733 }
734 });
735 })();
736
737 (function() {
738 var each = tinymce.each;
739
740 tinymce.create('static tinymce.util.Cookie', {
741 getHash : function(n) {
742 var v = this.get(n), h;
743
744 if (v) {
745 each(v.split('&'), function(v) {
746 v = v.split('=');
747 h = h || {};
748 h[unescape(v[0])] = unescape(v[1]);
749 });
750 }
751
752 return h;
753 },
754
755 setHash : function(n, v, e, p, d, s) {
756 var o = '';
757
758 each(v, function(v, k) {
759 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
760 });
761
762 this.set(n, o, e, p, d, s);
763 },
764
765 get : function(n) {
766 var c = document.cookie, e, p = n + "=", b;
767
768 // Strict mode
769 if (!c)
770 return;
771
772 b = c.indexOf("; " + p);
773
774 if (b == -1) {
775 b = c.indexOf(p);
776
777 if (b != 0)
778 return null;
779 } else
780 b += 2;
781
782 e = c.indexOf(";", b);
783
784 if (e == -1)
785 e = c.length;
786
787 return unescape(c.substring(b + p.length, e));
788 },
789
790 set : function(n, v, e, p, d, s) {
791 document.cookie = n + "=" + escape(v) +
792 ((e) ? "; expires=" + e.toGMTString() : "") +
793 ((p) ? "; path=" + escape(p) : "") +
794 ((d) ? "; domain=" + d : "") +
795 ((s) ? "; secure" : "");
796 },
797
798 remove : function(n, p) {
799 var d = new Date();
800
801 d.setTime(d.getTime() - 1000);
802
803 this.set(n, '', d, p, d);
804 }
805 });
806 })();
807
808 tinymce.create('static tinymce.util.JSON', {
809 serialize : function(o) {
810 var i, v, s = tinymce.util.JSON.serialize, t;
811
812 if (o == null)
813 return 'null';
814
815 t = typeof o;
816
817 if (t == 'string') {
818 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
819
820 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
821 i = v.indexOf(b);
822
823 if (i + 1)
824 return '\\' + v.charAt(i + 1);
825
826 a = b.charCodeAt().toString(16);
827
828 return '\\u' + '0000'.substring(a.length) + a;
829 }) + '"';
830 }
831
832 if (t == 'object') {
833 if (o.hasOwnProperty && o instanceof Array) {
834 for (i=0, v = '['; i<o.length; i++)
835 v += (i > 0 ? ',' : '') + s(o[i]);
836
837 return v + ']';
838 }
839
840 v = '{';
841
842 for (i in o)
843 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
844
845 return v + '}';
846 }
847
848 return '' + o;
849 },
850
851 parse : function(s) {
852 try {
853 return eval('(' + s + ')');
854 } catch (ex) {
855 // Ignore
856 }
857 }
858
859 });
860
861 tinymce.create('static tinymce.util.XHR', {
862 send : function(o) {
863 var x, t, w = window, c = 0;
864
865 // Default settings
866 o.scope = o.scope || this;
867 o.success_scope = o.success_scope || o.scope;
868 o.error_scope = o.error_scope || o.scope;
869 o.async = o.async === false ? false : true;
870 o.data = o.data || '';
871
872 function get(s) {
873 x = 0;
874
875 try {
876 x = new ActiveXObject(s);
877 } catch (ex) {
878 }
879
880 return x;
881 };
882
883 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
884
885 if (x) {
886 if (x.overrideMimeType)
887 x.overrideMimeType(o.content_type);
888
889 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
890
891 if (o.content_type)
892 x.setRequestHeader('Content-Type', o.content_type);
893
894 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
895
896 x.send(o.data);
897
898 function ready() {
899 if (!o.async || x.readyState == 4 || c++ > 10000) {
900 if (o.success && c < 10000 && x.status == 200)
901 o.success.call(o.success_scope, '' + x.responseText, x, o);
902 else if (o.error)
903 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
904
905 x = null;
906 } else
907 w.setTimeout(ready, 10);
908 };
909
910 // Syncronous request
911 if (!o.async)
912 return ready();
913
914 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
915 t = w.setTimeout(ready, 10);
916 }
917 }
918 });
919
920 (function() {
921 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
922
923 tinymce.create('tinymce.util.JSONRequest', {
924 JSONRequest : function(s) {
925 this.settings = extend({
926 }, s);
927 this.count = 0;
928 },
929
930 send : function(o) {
931 var ecb = o.error, scb = o.success;
932
933 o = extend(this.settings, o);
934
935 o.success = function(c, x) {
936 c = JSON.parse(c);
937
938 if (typeof(c) == 'undefined') {
939 c = {
940 error : 'JSON Parse error.'
941 };
942 }
943
944 if (c.error)
945 ecb.call(o.error_scope || o.scope, c.error, x);
946 else
947 scb.call(o.success_scope || o.scope, c.result);
948 };
949
950 o.error = function(ty, x) {
951 ecb.call(o.error_scope || o.scope, ty, x);
952 };
953
954 o.data = JSON.serialize({
955 id : o.id || 'c' + (this.count++),
956 method : o.method,
957 params : o.params
958 });
959
960 // JSON content type for Ruby on rails. Bug: #1883287
961 o.content_type = 'application/json';
962
963 XHR.send(o);
964 },
965
966 'static' : {
967 sendRPC : function(o) {
968 return new tinymce.util.JSONRequest().send(o);
969 }
970 }
971 });
972 }());
973 (function(tinymce) {
974 // Shorten names
975 var each = tinymce.each,
976 is = tinymce.is,
977 isWebKit = tinymce.isWebKit,
978 isIE = tinymce.isIE,
979 blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,
980 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
981 mceAttribs = makeMap('src,href,style,coords,shape'),
982 encodedChars = {'&' : '&amp;', '"' : '&quot;', '<' : '&lt;', '>' : '&gt;'},
983 encodeCharsRe = /[<>&\"]/g,
984 simpleSelectorRe = /^([a-z0-9],?)+$/i,
985 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
986 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
987
988 function makeMap(str) {
989 var map = {}, i;
990
991 str = str.split(',');
992 for (i = str.length; i >= 0; i--)
993 map[str[i]] = 1;
994
995 return map;
996 };
997
998 tinymce.create('tinymce.dom.DOMUtils', {
999 doc : null,
1000 root : null,
1001 files : null,
1002 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
1003 props : {
1004 "for" : "htmlFor",
1005 "class" : "className",
1006 className : "className",
1007 checked : "checked",
1008 disabled : "disabled",
1009 maxlength : "maxLength",
1010 readonly : "readOnly",
1011 selected : "selected",
1012 value : "value",
1013 id : "id",
1014 name : "name",
1015 type : "type"
1016 },
1017
1018 DOMUtils : function(d, s) {
1019 var t = this, globalStyle;
1020
1021 t.doc = d;
1022 t.win = window;
1023 t.files = {};
1024 t.cssFlicker = false;
1025 t.counter = 0;
1026 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
1027 t.stdMode = d.documentMode === 8;
1028
1029 t.settings = s = tinymce.extend({
1030 keep_values : false,
1031 hex_colors : 1,
1032 process_html : 1
1033 }, s);
1034
1035 // Fix IE6SP2 flicker and check it failed for pre SP2
1036 if (tinymce.isIE6) {
1037 try {
1038 d.execCommand('BackgroundImageCache', false, true);
1039 } catch (e) {
1040 t.cssFlicker = true;
1041 }
1042 }
1043
1044 // Build styles list
1045 if (s.valid_styles) {
1046 t._styles = {};
1047
1048 // Convert styles into a rule list
1049 each(s.valid_styles, function(value, key) {
1050 t._styles[key] = tinymce.explode(value);
1051 });
1052 }
1053
1054 tinymce.addUnload(t.destroy, t);
1055 },
1056
1057 getRoot : function() {
1058 var t = this, s = t.settings;
1059
1060 return (s && t.get(s.root_element)) || t.doc.body;
1061 },
1062
1063 getViewPort : function(w) {
1064 var d, b;
1065
1066 w = !w ? this.win : w;
1067 d = w.document;
1068 b = this.boxModel ? d.documentElement : d.body;
1069
1070 // Returns viewport size excluding scrollbars
1071 return {
1072 x : w.pageXOffset || b.scrollLeft,
1073 y : w.pageYOffset || b.scrollTop,
1074 w : w.innerWidth || b.clientWidth,
1075 h : w.innerHeight || b.clientHeight
1076 };
1077 },
1078
1079 getRect : function(e) {
1080 var p, t = this, sr;
1081
1082 e = t.get(e);
1083 p = t.getPos(e);
1084 sr = t.getSize(e);
1085
1086 return {
1087 x : p.x,
1088 y : p.y,
1089 w : sr.w,
1090 h : sr.h
1091 };
1092 },
1093
1094 getSize : function(e) {
1095 var t = this, w, h;
1096
1097 e = t.get(e);
1098 w = t.getStyle(e, 'width');
1099 h = t.getStyle(e, 'height');
1100
1101 // Non pixel value, then force offset/clientWidth
1102 if (w.indexOf('px') === -1)
1103 w = 0;
1104
1105 // Non pixel value, then force offset/clientWidth
1106 if (h.indexOf('px') === -1)
1107 h = 0;
1108
1109 return {
1110 w : parseInt(w) || e.offsetWidth || e.clientWidth,
1111 h : parseInt(h) || e.offsetHeight || e.clientHeight
1112 };
1113 },
1114
1115 getParent : function(n, f, r) {
1116 return this.getParents(n, f, r, false);
1117 },
1118
1119 getParents : function(n, f, r, c) {
1120 var t = this, na, se = t.settings, o = [];
1121
1122 n = t.get(n);
1123 c = c === undefined;
1124
1125 if (se.strict_root)
1126 r = r || t.getRoot();
1127
1128 // Wrap node name as func
1129 if (is(f, 'string')) {
1130 na = f;
1131
1132 if (f === '*') {
1133 f = function(n) {return n.nodeType == 1;};
1134 } else {
1135 f = function(n) {
1136 return t.is(n, na);
1137 };
1138 }
1139 }
1140
1141 while (n) {
1142 if (n == r || !n.nodeType || n.nodeType === 9)
1143 break;
1144
1145 if (!f || f(n)) {
1146 if (c)
1147 o.push(n);
1148 else
1149 return n;
1150 }
1151
1152 n = n.parentNode;
1153 }
1154
1155 return c ? o : null;
1156 },
1157
1158 get : function(e) {
1159 var n;
1160
1161 if (e && this.doc && typeof(e) == 'string') {
1162 n = e;
1163 e = this.doc.getElementById(e);
1164
1165 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
1166 if (e && e.id !== n)
1167 return this.doc.getElementsByName(n)[1];
1168 }
1169
1170 return e;
1171 },
1172
1173 getNext : function(node, selector) {
1174 return this._findSib(node, selector, 'nextSibling');
1175 },
1176
1177 getPrev : function(node, selector) {
1178 return this._findSib(node, selector, 'previousSibling');
1179 },
1180
1181
1182 select : function(pa, s) {
1183 var t = this;
1184
1185 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
1186 },
1187
1188 is : function(n, selector) {
1189 var i;
1190
1191 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
1192 if (n.length === undefined) {
1193 // Simple all selector
1194 if (selector === '*')
1195 return n.nodeType == 1;
1196
1197 // Simple selector just elements
1198 if (simpleSelectorRe.test(selector)) {
1199 selector = selector.toLowerCase().split(/,/);
1200 n = n.nodeName.toLowerCase();
1201
1202 for (i = selector.length - 1; i >= 0; i--) {
1203 if (selector[i] == n)
1204 return true;
1205 }
1206
1207 return false;
1208 }
1209 }
1210
1211 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
1212 },
1213
1214
1215 add : function(p, n, a, h, c) {
1216 var t = this;
1217
1218 return this.run(p, function(p) {
1219 var e, k;
1220
1221 e = is(n, 'string') ? t.doc.createElement(n) : n;
1222 t.setAttribs(e, a);
1223
1224 if (h) {
1225 if (h.nodeType)
1226 e.appendChild(h);
1227 else
1228 t.setHTML(e, h);
1229 }
1230
1231 return !c ? p.appendChild(e) : e;
1232 });
1233 },
1234
1235 create : function(n, a, h) {
1236 return this.add(this.doc.createElement(n), n, a, h, 1);
1237 },
1238
1239 createHTML : function(n, a, h) {
1240 var o = '', t = this, k;
1241
1242 o += '<' + n;
1243
1244 for (k in a) {
1245 if (a.hasOwnProperty(k))
1246 o += ' ' + k + '="' + t.encode(a[k]) + '"';
1247 }
1248
1249 if (tinymce.is(h))
1250 return o + '>' + h + '</' + n + '>';
1251
1252 return o + ' />';
1253 },
1254
1255 remove : function(node, keep_children) {
1256 return this.run(node, function(node) {
1257 var parent, child;
1258
1259 parent = node.parentNode;
1260
1261 if (!parent)
1262 return null;
1263
1264 if (keep_children) {
1265 while (child = node.firstChild) {
1266 // IE 8 will crash if you don't remove completely empty text nodes
1267 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
1268 parent.insertBefore(child, node);
1269 else
1270 node.removeChild(child);
1271 }
1272 }
1273
1274 return parent.removeChild(node);
1275 });
1276 },
1277
1278 setStyle : function(n, na, v) {
1279 var t = this;
1280
1281 return t.run(n, function(e) {
1282 var s, i;
1283
1284 s = e.style;
1285
1286 // Camelcase it, if needed
1287 na = na.replace(/-(\D)/g, function(a, b){
1288 return b.toUpperCase();
1289 });
1290
1291 // Default px suffix on these
1292 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
1293 v += 'px';
1294
1295 switch (na) {
1296 case 'opacity':
1297 // IE specific opacity
1298 if (isIE) {
1299 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
1300
1301 if (!n.currentStyle || !n.currentStyle.hasLayout)
1302 s.display = 'inline-block';
1303 }
1304
1305 // Fix for older browsers
1306 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
1307 break;
1308
1309 case 'float':
1310 isIE ? s.styleFloat = v : s.cssFloat = v;
1311 break;
1312
1313 default:
1314 s[na] = v || '';
1315 }
1316
1317 // Force update of the style data
1318 if (t.settings.update_styles)
1319 t.setAttrib(e, '_mce_style');
1320 });
1321 },
1322
1323 getStyle : function(n, na, c) {
1324 n = this.get(n);
1325
1326 if (!n)
1327 return false;
1328
1329 // Gecko
1330 if (this.doc.defaultView && c) {
1331 // Remove camelcase
1332 na = na.replace(/[A-Z]/g, function(a){
1333 return '-' + a;
1334 });
1335
1336 try {
1337 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
1338 } catch (ex) {
1339 // Old safari might fail
1340 return null;
1341 }
1342 }
1343
1344 // Camelcase it, if needed
1345 na = na.replace(/-(\D)/g, function(a, b){
1346 return b.toUpperCase();
1347 });
1348
1349 if (na == 'float')
1350 na = isIE ? 'styleFloat' : 'cssFloat';
1351
1352 // IE & Opera
1353 if (n.currentStyle && c)
1354 return n.currentStyle[na];
1355
1356 return n.style[na];
1357 },
1358
1359 setStyles : function(e, o) {
1360 var t = this, s = t.settings, ol;
1361
1362 ol = s.update_styles;
1363 s.update_styles = 0;
1364
1365 each(o, function(v, n) {
1366 t.setStyle(e, n, v);
1367 });
1368
1369 // Update style info
1370 s.update_styles = ol;
1371 if (s.update_styles)
1372 t.setAttrib(e, s.cssText);
1373 },
1374
1375 setAttrib : function(e, n, v) {
1376 var t = this;
1377
1378 // Whats the point
1379 if (!e || !n)
1380 return;
1381
1382 // Strict XML mode
1383 if (t.settings.strict)
1384 n = n.toLowerCase();
1385
1386 return this.run(e, function(e) {
1387 var s = t.settings;
1388
1389 switch (n) {
1390 case "style":
1391 if (!is(v, 'string')) {
1392 each(v, function(v, n) {
1393 t.setStyle(e, n, v);
1394 });
1395
1396 return;
1397 }
1398
1399 // No mce_style for elements with these since they might get resized by the user
1400 if (s.keep_values) {
1401 if (v && !t._isRes(v))
1402 e.setAttribute('_mce_style', v, 2);
1403 else
1404 e.removeAttribute('_mce_style', 2);
1405 }
1406
1407 e.style.cssText = v;
1408 break;
1409
1410 case "class":
1411 e.className = v || ''; // Fix IE null bug
1412 break;
1413
1414 case "src":
1415 case "href":
1416 if (s.keep_values) {
1417 if (s.url_converter)
1418 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
1419
1420 t.setAttrib(e, '_mce_' + n, v, 2);
1421 }
1422
1423 break;
1424
1425 case "shape":
1426 e.setAttribute('_mce_style', v);
1427 break;
1428 }
1429
1430 if (is(v) && v !== null && v.length !== 0)
1431 e.setAttribute(n, '' + v, 2);
1432 else
1433 e.removeAttribute(n, 2);
1434 });
1435 },
1436
1437 setAttribs : function(e, o) {
1438 var t = this;
1439
1440 return this.run(e, function(e) {
1441 each(o, function(v, n) {
1442 t.setAttrib(e, n, v);
1443 });
1444 });
1445 },
1446
1447 getAttrib : function(e, n, dv) {
1448 var v, t = this;
1449
1450 e = t.get(e);
1451
1452 if (!e || e.nodeType !== 1)
1453 return false;
1454
1455 if (!is(dv))
1456 dv = '';
1457
1458 // Try the mce variant for these
1459 if (/^(src|href|style|coords|shape)$/.test(n)) {
1460 v = e.getAttribute("_mce_" + n);
1461
1462 if (v)
1463 return v;
1464 }
1465
1466 if (isIE && t.props[n]) {
1467 v = e[t.props[n]];
1468 v = v && v.nodeValue ? v.nodeValue : v;
1469 }
1470
1471 if (!v)
1472 v = e.getAttribute(n, 2);
1473
1474 // Check boolean attribs
1475 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
1476 if (e[t.props[n]] === true && v === '')
1477 return n;
1478
1479 return v ? n : '';
1480 }
1481
1482 // Inner input elements will override attributes on form elements
1483 if (e.nodeName === "FORM" && e.getAttributeNode(n))
1484 return e.getAttributeNode(n).nodeValue;
1485
1486 if (n === 'style') {
1487 v = v || e.style.cssText;
1488
1489 if (v) {
1490 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
1491
1492 if (t.settings.keep_values && !t._isRes(v))
1493 e.setAttribute('_mce_style', v);
1494 }
1495 }
1496
1497 // Remove Apple and WebKit stuff
1498 if (isWebKit && n === "class" && v)
1499 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
1500
1501 // Handle IE issues
1502 if (isIE) {
1503 switch (n) {
1504 case 'rowspan':
1505 case 'colspan':
1506 // IE returns 1 as default value
1507 if (v === 1)
1508 v = '';
1509
1510 break;
1511
1512 case 'size':
1513 // IE returns +0 as default value for size
1514 if (v === '+0' || v === 20 || v === 0)
1515 v = '';
1516
1517 break;
1518
1519 case 'width':
1520 case 'height':
1521 case 'vspace':
1522 case 'checked':
1523 case 'disabled':
1524 case 'readonly':
1525 if (v === 0)
1526 v = '';
1527
1528 break;
1529
1530 case 'hspace':
1531 // IE returns -1 as default value
1532 if (v === -1)
1533 v = '';
1534
1535 break;
1536
1537 case 'maxlength':
1538 case 'tabindex':
1539 // IE returns default value
1540 if (v === 32768 || v === 2147483647 || v === '32768')
1541 v = '';
1542
1543 break;
1544
1545 case 'multiple':
1546 case 'compact':
1547 case 'noshade':
1548 case 'nowrap':
1549 if (v === 65535)
1550 return n;
1551
1552 return dv;
1553
1554 case 'shape':
1555 v = v.toLowerCase();
1556 break;
1557
1558 default:
1559 // IE has odd anonymous function for event attributes
1560 if (n.indexOf('on') === 0 && v)
1561 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
1562 }
1563 }
1564
1565 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
1566 },
1567
1568 getPos : function(n, ro) {
1569 var t = this, x = 0, y = 0, e, d = t.doc, r;
1570
1571 n = t.get(n);
1572 ro = ro || d.body;
1573
1574 if (n) {
1575 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
1576 if (isIE && !t.stdMode) {
1577 n = n.getBoundingClientRect();
1578 e = t.boxModel ? d.documentElement : d.body;
1579 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
1580 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
1581
1582 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
1583 }
1584
1585 r = n;
1586 while (r && r != ro && r.nodeType) {
1587 x += r.offsetLeft || 0;
1588 y += r.offsetTop || 0;
1589 r = r.offsetParent;
1590 }
1591
1592 r = n.parentNode;
1593 while (r && r != ro && r.nodeType) {
1594 x -= r.scrollLeft || 0;
1595 y -= r.scrollTop || 0;
1596 r = r.parentNode;
1597 }
1598 }
1599
1600 return {x : x, y : y};
1601 },
1602
1603 parseStyle : function(st) {
1604 var t = this, s = t.settings, o = {};
1605
1606 if (!st)
1607 return o;
1608
1609 function compress(p, s, ot) {
1610 var t, r, b, l;
1611
1612 // Get values and check it it needs compressing
1613 t = o[p + '-top' + s];
1614 if (!t)
1615 return;
1616
1617 r = o[p + '-right' + s];
1618 if (t != r)
1619 return;
1620
1621 b = o[p + '-bottom' + s];
1622 if (r != b)
1623 return;
1624
1625 l = o[p + '-left' + s];
1626 if (b != l)
1627 return;
1628
1629 // Compress
1630 o[ot] = l;
1631 delete o[p + '-top' + s];
1632 delete o[p + '-right' + s];
1633 delete o[p + '-bottom' + s];
1634 delete o[p + '-left' + s];
1635 };
1636
1637 function compress2(ta, a, b, c) {
1638 var t;
1639
1640 t = o[a];
1641 if (!t)
1642 return;
1643
1644 t = o[b];
1645 if (!t)
1646 return;
1647
1648 t = o[c];
1649 if (!t)
1650 return;
1651
1652 // Compress
1653 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
1654 delete o[a];
1655 delete o[b];
1656 delete o[c];
1657 };
1658
1659 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
1660
1661 each(st.split(';'), function(v) {
1662 var sv, ur = [];
1663
1664 if (v) {
1665 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
1666 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
1667 v = v.split(':');
1668 sv = tinymce.trim(v[1]);
1669 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
1670
1671 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
1672 return t.toHex(v);
1673 });
1674
1675 if (s.url_converter) {
1676 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
1677 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
1678 });
1679 }
1680
1681 o[tinymce.trim(v[0]).toLowerCase()] = sv;
1682 }
1683 });
1684
1685 compress("border", "", "border");
1686 compress("border", "-width", "border-width");
1687 compress("border", "-color", "border-color");
1688 compress("border", "-style", "border-style");
1689 compress("padding", "", "padding");
1690 compress("margin", "", "margin");
1691 compress2('border', 'border-width', 'border-style', 'border-color');
1692
1693 if (isIE) {
1694 // Remove pointless border
1695 if (o.border == 'medium none')
1696 o.border = '';
1697 }
1698
1699 return o;
1700 },
1701
1702 serializeStyle : function(o, name) {
1703 var t = this, s = '';
1704
1705 function add(v, k) {
1706 if (k && v) {
1707 // Remove browser specific styles like -moz- or -webkit-
1708 if (k.indexOf('-') === 0)
1709 return;
1710
1711 switch (k) {
1712 case 'font-weight':
1713 // Opera will output bold as 700
1714 if (v == 700)
1715 v = 'bold';
1716
1717 break;
1718
1719 case 'color':
1720 case 'background-color':
1721 v = v.toLowerCase();
1722 break;
1723 }
1724
1725 s += (s ? ' ' : '') + k + ': ' + v + ';';
1726 }
1727 };
1728
1729 // Validate style output
1730 if (name && t._styles) {
1731 each(t._styles['*'], function(name) {
1732 add(o[name], name);
1733 });
1734
1735 each(t._styles[name.toLowerCase()], function(name) {
1736 add(o[name], name);
1737 });
1738 } else
1739 each(o, add);
1740
1741 return s;
1742 },
1743
1744 loadCSS : function(u) {
1745 var t = this, d = t.doc, head;
1746
1747 if (!u)
1748 u = '';
1749
1750 head = t.select('head')[0];
1751
1752 each(u.split(','), function(u) {
1753 var link;
1754
1755 if (t.files[u])
1756 return;
1757
1758 t.files[u] = true;
1759 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
1760
1761 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
1762 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
1763 // It's ugly but it seems to work fine.
1764 if (isIE && d.documentMode) {
1765 link.onload = function() {
1766 d.recalc();
1767 link.onload = null;
1768 };
1769 }
1770
1771 head.appendChild(link);
1772 });
1773 },
1774
1775 addClass : function(e, c) {
1776 return this.run(e, function(e) {
1777 var o;
1778
1779 if (!c)
1780 return 0;
1781
1782 if (this.hasClass(e, c))
1783 return e.className;
1784
1785 o = this.removeClass(e, c);
1786
1787 return e.className = (o != '' ? (o + ' ') : '') + c;
1788 });
1789 },
1790
1791 removeClass : function(e, c) {
1792 var t = this, re;
1793
1794 return t.run(e, function(e) {
1795 var v;
1796
1797 if (t.hasClass(e, c)) {
1798 if (!re)
1799 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
1800
1801 v = e.className.replace(re, ' ');
1802 v = tinymce.trim(v != ' ' ? v : '');
1803
1804 e.className = v;
1805
1806 // Empty class attr
1807 if (!v) {
1808 e.removeAttribute('class');
1809 e.removeAttribute('className');
1810 }
1811
1812 return v;
1813 }
1814
1815 return e.className;
1816 });
1817 },
1818
1819 hasClass : function(n, c) {
1820 n = this.get(n);
1821
1822 if (!n || !c)
1823 return false;
1824
1825 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
1826 },
1827
1828 show : function(e) {
1829 return this.setStyle(e, 'display', 'block');
1830 },
1831
1832 hide : function(e) {
1833 return this.setStyle(e, 'display', 'none');
1834 },
1835
1836 isHidden : function(e) {
1837 e = this.get(e);
1838
1839 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
1840 },
1841
1842 uniqueId : function(p) {
1843 return (!p ? 'mce_' : p) + (this.counter++);
1844 },
1845
1846 setHTML : function(e, h) {
1847 var t = this;
1848
1849 return this.run(e, function(e) {
1850 var x, i, nl, n, p, x;
1851
1852 h = t.processHTML(h);
1853
1854 if (isIE) {
1855 function set() {
1856 // Remove all child nodes
1857 while (e.firstChild)
1858 e.firstChild.removeNode();
1859
1860 try {
1861 // IE will remove comments from the beginning
1862 // unless you padd the contents with something
1863 e.innerHTML = '<br />' + h;
1864 e.removeChild(e.firstChild);
1865 } catch (ex) {
1866 // 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
1867 // This seems to fix this problem
1868
1869 // Create new div with HTML contents and a BR infront to keep comments
1870 x = t.create('div');
1871 x.innerHTML = '<br />' + h;
1872
1873 // Add all children from div to target
1874 each (x.childNodes, function(n, i) {
1875 // Skip br element
1876 if (i)
1877 e.appendChild(n);
1878 });
1879 }
1880 };
1881
1882 // IE has a serious bug when it comes to paragraphs it can produce an invalid
1883 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
1884 // It seems to be that IE doesn't like a root block element placed inside another root block element
1885 if (t.settings.fix_ie_paragraphs)
1886 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true">&nbsp;</p>');
1887
1888 set();
1889
1890 if (t.settings.fix_ie_paragraphs) {
1891 // Check for odd paragraphs this is a sign of a broken DOM
1892 nl = e.getElementsByTagName("p");
1893 for (i = nl.length - 1, x = 0; i >= 0; i--) {
1894 n = nl[i];
1895
1896 if (!n.hasChildNodes()) {
1897 if (!n._mce_keep) {
1898 x = 1; // Is broken
1899 break;
1900 }
1901
1902 n.removeAttribute('_mce_keep');
1903 }
1904 }
1905 }
1906
1907 // Time to fix the madness IE left us
1908 if (x) {
1909 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
1910 // after we use innerHTML we can fix the DOM tree
1911 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
1912 h = h.replace(/<\/p>/gi, '</div>');
1913
1914 // Set the new HTML with DIVs
1915 set();
1916
1917 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
1918 // This is needed since IE has a annoying bug see above for details
1919 // This is a slow process but it has to be done. :(
1920 if (t.settings.fix_ie_paragraphs) {
1921 nl = e.getElementsByTagName("DIV");
1922 for (i = nl.length - 1; i >= 0; i--) {
1923 n = nl[i];
1924
1925 // Is it a temp div
1926 if (n._mce_tmp) {
1927 // Create new paragraph
1928 p = t.doc.createElement('p');
1929
1930 // Copy all attributes
1931 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
1932 var v;
1933
1934 if (b !== '_mce_tmp') {
1935 v = n.getAttribute(b);
1936
1937 if (!v && b === 'class')
1938 v = n.className;
1939
1940 p.setAttribute(b, v);
1941 }
1942 });
1943
1944 // Append all children to new paragraph
1945 for (x = 0; x<n.childNodes.length; x++)
1946 p.appendChild(n.childNodes[x].cloneNode(true));
1947
1948 // Replace div with new paragraph
1949 n.swapNode(p);
1950 }
1951 }
1952 }
1953 }
1954 } else
1955 e.innerHTML = h;
1956
1957 return h;
1958 });
1959 },
1960
1961 processHTML : function(h) {
1962 var t = this, s = t.settings, codeBlocks = [];
1963
1964 if (!s.process_html)
1965 return h;
1966
1967 if (isIE) {
1968 h = h.replace(/&apos;/g, '&#39;'); // IE can't handle apos
1969 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
1970 }
1971
1972 // Fix some issues
1973 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
1974
1975 // Store away src and href in _mce_src and mce_href since browsers mess them up
1976 if (s.keep_values) {
1977 // Wrap scripts and styles in comments for serialization purposes
1978 if (/<script|noscript|style/i.test(h)) {
1979 function trim(s) {
1980 // Remove prefix and suffix code for element
1981 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
1982 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
1983 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
1984 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
1985
1986 return s;
1987 };
1988
1989 // Wrap the script contents in CDATA and keep them from executing
1990 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
1991 // Force type attribute
1992 if (!attribs)
1993 attribs = ' type="text/javascript"';
1994
1995 // Convert the src attribute of the scripts
1996 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
1997 if (s.url_converter)
1998 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
1999
2000 return '_mce_src="' + url + '"';
2001 });
2002
2003 // Wrap text contents
2004 if (tinymce.trim(text)) {
2005 codeBlocks.push(trim(text));
2006 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
2007 }
2008
2009 return '<mce:script' + attribs + '>' + text + '</mce:script>';
2010 });
2011
2012 // Wrap style elements
2013 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
2014 // Wrap text contents
2015 if (text) {
2016 codeBlocks.push(trim(text));
2017 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
2018 }
2019
2020 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
2021 });
2022
2023 // Wrap noscript elements
2024 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
2025 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '&#45;&#45;') + '--></mce:noscript>';
2026 });
2027 }
2028
2029 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
2030
2031 // This function processes the attributes in the HTML string to force boolean
2032 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
2033 function processTags(html) {
2034 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
2035 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
2036 var mceValue;
2037
2038 name = name.toLowerCase();
2039 value = value || val2 || val3 || "";
2040
2041 // Treat boolean attributes
2042 if (boolAttrs[name]) {
2043 // false or 0 is treated as a missing attribute
2044 if (value === 'false' || value === '0')
2045 return;
2046
2047 return name + '="' + name + '"';
2048 }
2049
2050 // Is attribute one that needs special treatment
2051 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
2052 mceValue = t.decode(value);
2053
2054 // Convert URLs to relative/absolute ones
2055 if (s.url_converter && (name == "src" || name == "href"))
2056 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
2057
2058 // Process styles lowercases them and compresses them
2059 if (name == 'style')
2060 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
2061
2062 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
2063 }
2064
2065 return match;
2066 }) + end + '>';
2067 });
2068 };
2069
2070 h = processTags(h);
2071
2072 // Restore script blocks
2073 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
2074 return codeBlocks[idx];
2075 });
2076 }
2077
2078 return h;
2079 },
2080
2081 getOuterHTML : function(e) {
2082 var d;
2083
2084 e = this.get(e);
2085
2086 if (!e)
2087 return null;
2088
2089 if (e.outerHTML !== undefined)
2090 return e.outerHTML;
2091
2092 d = (e.ownerDocument || this.doc).createElement("body");
2093 d.appendChild(e.cloneNode(true));
2094
2095 return d.innerHTML;
2096 },
2097
2098 setOuterHTML : function(e, h, d) {
2099 var t = this;
2100
2101 function setHTML(e, h, d) {
2102 var n, tp;
2103
2104 tp = d.createElement("body");
2105 tp.innerHTML = h;
2106
2107 n = tp.lastChild;
2108 while (n) {
2109 t.insertAfter(n.cloneNode(true), e);
2110 n = n.previousSibling;
2111 }
2112
2113 t.remove(e);
2114 };
2115
2116 return this.run(e, function(e) {
2117 e = t.get(e);
2118
2119 // Only set HTML on elements
2120 if (e.nodeType == 1) {
2121 d = d || e.ownerDocument || t.doc;
2122
2123 if (isIE) {
2124 try {
2125 // Try outerHTML for IE it sometimes produces an unknown runtime error
2126 if (isIE && e.nodeType == 1)
2127 e.outerHTML = h;
2128 else
2129 setHTML(e, h, d);
2130 } catch (ex) {
2131 // Fix for unknown runtime error
2132 setHTML(e, h, d);
2133 }
2134 } else
2135 setHTML(e, h, d);
2136 }
2137 });
2138 },
2139
2140 decode : function(s) {
2141 var e, n, v;
2142
2143 // Look for entities to decode
2144 if (/&[\w#]+;/.test(s)) {
2145 // Decode the entities using a div element not super efficient but less code
2146 e = this.doc.createElement("div");
2147 e.innerHTML = s;
2148 n = e.firstChild;
2149 v = '';
2150
2151 if (n) {
2152 do {
2153 v += n.nodeValue;
2154 } while (n = n.nextSibling);
2155 }
2156
2157 return v || s;
2158 }
2159
2160 return s;
2161 },
2162
2163 encode : function(str) {
2164 return ('' + str).replace(encodeCharsRe, function(chr) {
2165 return encodedChars[chr];
2166 });
2167 },
2168
2169 insertAfter : function(node, reference_node) {
2170 reference_node = this.get(reference_node);
2171
2172 return this.run(node, function(node) {
2173 var parent, nextSibling;
2174
2175 parent = reference_node.parentNode;
2176 nextSibling = reference_node.nextSibling;
2177
2178 if (nextSibling)
2179 parent.insertBefore(node, nextSibling);
2180 else
2181 parent.appendChild(node);
2182
2183 return node;
2184 });
2185 },
2186
2187 isBlock : function(n) {
2188 if (n.nodeType && n.nodeType !== 1)
2189 return false;
2190
2191 n = n.nodeName || n;
2192
2193 return blockRe.test(n);
2194 },
2195
2196 replace : function(n, o, k) {
2197 var t = this;
2198
2199 if (is(o, 'array'))
2200 n = n.cloneNode(true);
2201
2202 return t.run(o, function(o) {
2203 if (k) {
2204 each(tinymce.grep(o.childNodes), function(c) {
2205 n.appendChild(c);
2206 });
2207 }
2208
2209 return o.parentNode.replaceChild(n, o);
2210 });
2211 },
2212
2213 rename : function(elm, name) {
2214 var t = this, newElm;
2215
2216 if (elm.nodeName != name.toUpperCase()) {
2217 // Rename block element
2218 newElm = t.create(name);
2219
2220 // Copy attribs to new block
2221 each(t.getAttribs(elm), function(attr_node) {
2222 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
2223 });
2224
2225 // Replace block
2226 t.replace(newElm, elm, 1);
2227 }
2228
2229 return newElm || elm;
2230 },
2231
2232 findCommonAncestor : function(a, b) {
2233 var ps = a, pe;
2234
2235 while (ps) {
2236 pe = b;
2237
2238 while (pe && ps != pe)
2239 pe = pe.parentNode;
2240
2241 if (ps == pe)
2242 break;
2243
2244 ps = ps.parentNode;
2245 }
2246
2247 if (!ps && a.ownerDocument)
2248 return a.ownerDocument.documentElement;
2249
2250 return ps;
2251 },
2252
2253 toHex : function(s) {
2254 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
2255
2256 function hex(s) {
2257 s = parseInt(s).toString(16);
2258
2259 return s.length > 1 ? s : '0' + s; // 0 -> 00
2260 };
2261
2262 if (c) {
2263 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
2264
2265 return s;
2266 }
2267
2268 return s;
2269 },
2270
2271 getClasses : function() {
2272 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
2273
2274 if (t.classes)
2275 return t.classes;
2276
2277 function addClasses(s) {
2278 // IE style imports
2279 each(s.imports, function(r) {
2280 addClasses(r);
2281 });
2282
2283 each(s.cssRules || s.rules, function(r) {
2284 // Real type or fake it on IE
2285 switch (r.type || 1) {
2286 // Rule
2287 case 1:
2288 if (r.selectorText) {
2289 each(r.selectorText.split(','), function(v) {
2290 v = v.replace(/^\s*|\s*$|^\s\./g, "");
2291
2292 // Is internal or it doesn't contain a class
2293 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
2294 return;
2295
2296 // Remove everything but class name
2297 ov = v;
2298 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
2299
2300 // Filter classes
2301 if (f && !(v = f(v, ov)))
2302 return;
2303
2304 if (!lo[v]) {
2305 cl.push({'class' : v});
2306 lo[v] = 1;
2307 }
2308 });
2309 }
2310 break;
2311
2312 // Import
2313 case 3:
2314 addClasses(r.styleSheet);
2315 break;
2316 }
2317 });
2318 };
2319
2320 try {
2321 each(t.doc.styleSheets, addClasses);
2322 } catch (ex) {
2323 // Ignore
2324 }
2325
2326 if (cl.length > 0)
2327 t.classes = cl;
2328
2329 return cl;
2330 },
2331
2332 run : function(e, f, s) {
2333 var t = this, o;
2334
2335 if (t.doc && typeof(e) === 'string')
2336 e = t.get(e);
2337
2338 if (!e)
2339 return false;
2340
2341 s = s || this;
2342 if (!e.nodeType && (e.length || e.length === 0)) {
2343 o = [];
2344
2345 each(e, function(e, i) {
2346 if (e) {
2347 if (typeof(e) == 'string')
2348 e = t.doc.getElementById(e);
2349
2350 o.push(f.call(s, e, i));
2351 }
2352 });
2353
2354 return o;
2355 }
2356
2357 return f.call(s, e);
2358 },
2359
2360 getAttribs : function(n) {
2361 var o;
2362
2363 n = this.get(n);
2364
2365 if (!n)
2366 return [];
2367
2368 if (isIE) {
2369 o = [];
2370
2371 // Object will throw exception in IE
2372 if (n.nodeName == 'OBJECT')
2373 return n.attributes;
2374
2375 // IE doesn't keep the selected attribute if you clone option elements
2376 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
2377 o.push({specified : 1, nodeName : 'selected'});
2378
2379 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
2380 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
2381 o.push({specified : 1, nodeName : a});
2382 });
2383
2384 return o;
2385 }
2386
2387 return n.attributes;
2388 },
2389
2390 destroy : function(s) {
2391 var t = this;
2392
2393 if (t.events)
2394 t.events.destroy();
2395
2396 t.win = t.doc = t.root = t.events = null;
2397
2398 // Manual destroy then remove unload handler
2399 if (!s)
2400 tinymce.removeUnload(t.destroy);
2401 },
2402
2403 createRng : function() {
2404 var d = this.doc;
2405
2406 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
2407 },
2408
2409 nodeIndex : function(node, normalized) {
2410 var idx = 0, lastNodeType, lastNode, nodeType;
2411
2412 if (node) {
2413 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
2414 nodeType = node.nodeType;
2415
2416 // Normalize text nodes
2417 if (normalized && nodeType == 3) {
2418 if (nodeType == lastNodeType || !node.nodeValue.length)
2419 continue;
2420 }
2421
2422 idx++;
2423 lastNodeType = nodeType;
2424 }
2425 }
2426
2427 return idx;
2428 },
2429
2430 split : function(pe, e, re) {
2431 var t = this, r = t.createRng(), bef, aft, pa;
2432
2433 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
2434 // but we don't want that in our code since it serves no purpose for the end user
2435 // For example if this is chopped:
2436 // <p>text 1<span><b>CHOP</b></span>text 2</p>
2437 // would produce:
2438 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
2439 // this function will then trim of empty edges and produce:
2440 // <p>text 1</p><b>CHOP</b><p>text 2</p>
2441 function trim(node) {
2442 var i, children = node.childNodes;
2443
2444 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
2445 return;
2446
2447 for (i = children.length - 1; i >= 0; i--)
2448 trim(children[i]);
2449
2450 if (node.nodeType != 9) {
2451 // Keep non whitespace text nodes
2452 if (node.nodeType == 3 && node.nodeValue.length > 0)
2453 return;
2454
2455 if (node.nodeType == 1) {
2456 // If the only child is a bookmark then move it up
2457 children = node.childNodes;
2458 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
2459 node.parentNode.insertBefore(children[0], node);
2460
2461 // Keep non empty elements or img, hr etc
2462 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
2463 return;
2464 }
2465
2466 t.remove(node);
2467 }
2468
2469 return node;
2470 };
2471
2472 if (pe && e) {
2473 // Get before chunk
2474 r.setStart(pe.parentNode, t.nodeIndex(pe));
2475 r.setEnd(e.parentNode, t.nodeIndex(e));
2476 bef = r.extractContents();
2477
2478 // Get after chunk
2479 r = t.createRng();
2480 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
2481 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
2482 aft = r.extractContents();
2483
2484 // Insert before chunk
2485 pa = pe.parentNode;
2486 pa.insertBefore(trim(bef), pe);
2487
2488 // Insert middle chunk
2489 if (re)
2490 pa.replaceChild(re, e);
2491 else
2492 pa.insertBefore(e, pe);
2493
2494 // Insert after chunk
2495 pa.insertBefore(trim(aft), pe);
2496 t.remove(pe);
2497
2498 return re || e;
2499 }
2500 },
2501
2502 bind : function(target, name, func, scope) {
2503 var t = this;
2504
2505 if (!t.events)
2506 t.events = new tinymce.dom.EventUtils();
2507
2508 return t.events.add(target, name, func, scope || this);
2509 },
2510
2511 unbind : function(target, name, func) {
2512 var t = this;
2513
2514 if (!t.events)
2515 t.events = new tinymce.dom.EventUtils();
2516
2517 return t.events.remove(target, name, func);
2518 },
2519
2520
2521 _findSib : function(node, selector, name) {
2522 var t = this, f = selector;
2523
2524 if (node) {
2525 // If expression make a function of it using is
2526 if (is(f, 'string')) {
2527 f = function(node) {
2528 return t.is(node, selector);
2529 };
2530 }
2531
2532 // Loop all siblings
2533 for (node = node[name]; node; node = node[name]) {
2534 if (f(node))
2535 return node;
2536 }
2537 }
2538
2539 return null;
2540 },
2541
2542 _isRes : function(c) {
2543 // Is live resizble element
2544 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
2545 }
2546
2547 /*
2548 walk : function(n, f, s) {
2549 var d = this.doc, w;
2550
2551 if (d.createTreeWalker) {
2552 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
2553
2554 while ((n = w.nextNode()) != null)
2555 f.call(s || this, n);
2556 } else
2557 tinymce.walk(n, f, 'childNodes', s);
2558 }
2559 */
2560
2561 /*
2562 toRGB : function(s) {
2563 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
2564
2565 if (c) {
2566 // #FFF -> #FFFFFF
2567 if (!is(c[3]))
2568 c[3] = c[2] = c[1];
2569
2570 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
2571 }
2572
2573 return s;
2574 }
2575 */
2576 });
2577
2578 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
2579 })(tinymce);
2580
2581 (function(ns) {
2582 // Range constructor
2583 function Range(dom) {
2584 var t = this,
2585 doc = dom.doc,
2586 EXTRACT = 0,
2587 CLONE = 1,
2588 DELETE = 2,
2589 TRUE = true,
2590 FALSE = false,
2591 START_OFFSET = 'startOffset',
2592 START_CONTAINER = 'startContainer',
2593 END_CONTAINER = 'endContainer',
2594 END_OFFSET = 'endOffset',
2595 extend = tinymce.extend,
2596 nodeIndex = dom.nodeIndex;
2597
2598 extend(t, {
2599 // Inital states
2600 startContainer : doc,
2601 startOffset : 0,
2602 endContainer : doc,
2603 endOffset : 0,
2604 collapsed : TRUE,
2605 commonAncestorContainer : doc,
2606
2607 // Range constants
2608 START_TO_START : 0,
2609 START_TO_END : 1,
2610 END_TO_END : 2,
2611 END_TO_START : 3,
2612
2613 // Public methods
2614 setStart : setStart,
2615 setEnd : setEnd,
2616 setStartBefore : setStartBefore,
2617 setStartAfter : setStartAfter,
2618 setEndBefore : setEndBefore,
2619 setEndAfter : setEndAfter,
2620 collapse : collapse,
2621 selectNode : selectNode,
2622 selectNodeContents : selectNodeContents,
2623 compareBoundaryPoints : compareBoundaryPoints,
2624 deleteContents : deleteContents,
2625 extractContents : extractContents,
2626 cloneContents : cloneContents,
2627 insertNode : insertNode,
2628 surroundContents : surroundContents,
2629 cloneRange : cloneRange
2630 });
2631
2632 function setStart(n, o) {
2633 _setEndPoint(TRUE, n, o);
2634 };
2635
2636 function setEnd(n, o) {
2637 _setEndPoint(FALSE, n, o);
2638 };
2639
2640 function setStartBefore(n) {
2641 setStart(n.parentNode, nodeIndex(n));
2642 };
2643
2644 function setStartAfter(n) {
2645 setStart(n.parentNode, nodeIndex(n) + 1);
2646 };
2647
2648 function setEndBefore(n) {
2649 setEnd(n.parentNode, nodeIndex(n));
2650 };
2651
2652 function setEndAfter(n) {
2653 setEnd(n.parentNode, nodeIndex(n) + 1);
2654 };
2655
2656 function collapse(ts) {
2657 if (ts) {
2658 t[END_CONTAINER] = t[START_CONTAINER];
2659 t[END_OFFSET] = t[START_OFFSET];
2660 } else {
2661 t[START_CONTAINER] = t[END_CONTAINER];
2662 t[START_OFFSET] = t[END_OFFSET];
2663 }
2664
2665 t.collapsed = TRUE;
2666 };
2667
2668 function selectNode(n) {
2669 setStartBefore(n);
2670 setEndAfter(n);
2671 };
2672
2673 function selectNodeContents(n) {
2674 setStart(n, 0);
2675 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
2676 };
2677
2678 function compareBoundaryPoints(h, r) {
2679 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
2680
2681 // Check START_TO_START
2682 if (h === 0)
2683 return _compareBoundaryPoints(sc, so, sc, so);
2684
2685 // Check START_TO_END
2686 if (h === 1)
2687 return _compareBoundaryPoints(sc, so, ec, eo);
2688
2689 // Check END_TO_END
2690 if (h === 2)
2691 return _compareBoundaryPoints(ec, eo, ec, eo);
2692
2693 // Check END_TO_START
2694 if (h === 3)
2695 return _compareBoundaryPoints(ec, eo, sc, so);
2696 };
2697
2698 function deleteContents() {
2699 _traverse(DELETE);
2700 };
2701
2702 function extractContents() {
2703 return _traverse(EXTRACT);
2704 };
2705
2706 function cloneContents() {
2707 return _traverse(CLONE);
2708 };
2709
2710 function insertNode(n) {
2711 var startContainer = this[START_CONTAINER],
2712 startOffset = this[START_OFFSET], nn, o;
2713
2714 // Node is TEXT_NODE or CDATA
2715 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
2716 if (!startOffset) {
2717 // At the start of text
2718 startContainer.parentNode.insertBefore(n, startContainer);
2719 } else if (startOffset >= startContainer.nodeValue.length) {
2720 // At the end of text
2721 dom.insertAfter(n, startContainer);
2722 } else {
2723 // Middle, need to split
2724 nn = startContainer.splitText(startOffset);
2725 startContainer.parentNode.insertBefore(n, nn);
2726 }
2727 } else {
2728 // Insert element node
2729 if (startContainer.childNodes.length > 0)
2730 o = startContainer.childNodes[startOffset];
2731
2732 if (o)
2733 startContainer.insertBefore(n, o);
2734 else
2735 startContainer.appendChild(n);
2736 }
2737 };
2738
2739 function surroundContents(n) {
2740 var f = t.extractContents();
2741
2742 t.insertNode(n);
2743 n.appendChild(f);
2744 t.selectNode(n);
2745 };
2746
2747 function cloneRange() {
2748 return extend(new Range(dom), {
2749 startContainer : t[START_CONTAINER],
2750 startOffset : t[START_OFFSET],
2751 endContainer : t[END_CONTAINER],
2752 endOffset : t[END_OFFSET],
2753 collapsed : t.collapsed,
2754 commonAncestorContainer : t.commonAncestorContainer
2755 });
2756 };
2757
2758 // Private methods
2759
2760 function _getSelectedNode(container, offset) {
2761 var child;
2762
2763 if (container.nodeType == 3 /* TEXT_NODE */)
2764 return container;
2765
2766 if (offset < 0)
2767 return container;
2768
2769 child = container.firstChild;
2770 while (child && offset > 0) {
2771 --offset;
2772 child = child.nextSibling;
2773 }
2774
2775 if (child)
2776 return child;
2777
2778 return container;
2779 };
2780
2781 function _isCollapsed() {
2782 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
2783 };
2784
2785 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
2786 var c, offsetC, n, cmnRoot, childA, childB;
2787
2788 // In the first case the boundary-points have the same container. A is before B
2789 // if its offset is less than the offset of B, A is equal to B if its offset is
2790 // equal to the offset of B, and A is after B if its offset is greater than the
2791 // offset of B.
2792 if (containerA == containerB) {
2793 if (offsetA == offsetB)
2794 return 0; // equal
2795
2796 if (offsetA < offsetB)
2797 return -1; // before
2798
2799 return 1; // after
2800 }
2801
2802 // In the second case a child node C of the container of A is an ancestor
2803 // container of B. In this case, A is before B if the offset of A is less than or
2804 // equal to the index of the child node C and A is after B otherwise.
2805 c = containerB;
2806 while (c && c.parentNode != containerA)
2807 c = c.parentNode;
2808
2809 if (c) {
2810 offsetC = 0;
2811 n = containerA.firstChild;
2812
2813 while (n != c && offsetC < offsetA) {
2814 offsetC++;
2815 n = n.nextSibling;
2816 }
2817
2818 if (offsetA <= offsetC)
2819 return -1; // before
2820
2821 return 1; // after
2822 }
2823
2824 // In the third case a child node C of the container of B is an ancestor container
2825 // of A. In this case, A is before B if the index of the child node C is less than
2826 // the offset of B and A is after B otherwise.
2827 c = containerA;
2828 while (c && c.parentNode != containerB) {
2829 c = c.parentNode;
2830 }
2831
2832 if (c) {
2833 offsetC = 0;
2834 n = containerB.firstChild;
2835
2836 while (n != c && offsetC < offsetB) {
2837 offsetC++;
2838 n = n.nextSibling;
2839 }
2840
2841 if (offsetC < offsetB)
2842 return -1; // before
2843
2844 return 1; // after
2845 }
2846
2847 // In the fourth case, none of three other cases hold: the containers of A and B
2848 // are siblings or descendants of sibling nodes. In this case, A is before B if
2849 // the container of A is before the container of B in a pre-order traversal of the
2850 // Ranges' context tree and A is after B otherwise.
2851 cmnRoot = dom.findCommonAncestor(containerA, containerB);
2852 childA = containerA;
2853
2854 while (childA && childA.parentNode != cmnRoot)
2855 childA = childA.parentNode;
2856
2857 if (!childA)
2858 childA = cmnRoot;
2859
2860 childB = containerB;
2861 while (childB && childB.parentNode != cmnRoot)
2862 childB = childB.parentNode;
2863
2864 if (!childB)
2865 childB = cmnRoot;
2866
2867 if (childA == childB)
2868 return 0; // equal
2869
2870 n = cmnRoot.firstChild;
2871 while (n) {
2872 if (n == childA)
2873 return -1; // before
2874
2875 if (n == childB)
2876 return 1; // after
2877
2878 n = n.nextSibling;
2879 }
2880 };
2881
2882 function _setEndPoint(st, n, o) {
2883 var ec, sc;
2884
2885 if (st) {
2886 t[START_CONTAINER] = n;
2887 t[START_OFFSET] = o;
2888 } else {
2889 t[END_CONTAINER] = n;
2890 t[END_OFFSET] = o;
2891 }
2892
2893 // If one boundary-point of a Range is set to have a root container
2894 // other than the current one for the Range, the Range is collapsed to
2895 // the new position. This enforces the restriction that both boundary-
2896 // points of a Range must have the same root container.
2897 ec = t[END_CONTAINER];
2898 while (ec.parentNode)
2899 ec = ec.parentNode;
2900
2901 sc = t[START_CONTAINER];
2902 while (sc.parentNode)
2903 sc = sc.parentNode;
2904
2905 if (sc == ec) {
2906 // The start position of a Range is guaranteed to never be after the
2907 // end position. To enforce this restriction, if the start is set to
2908 // be at a position after the end, the Range is collapsed to that
2909 // position.
2910 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
2911 t.collapse(st);
2912 } else
2913 t.collapse(st);
2914
2915 t.collapsed = _isCollapsed();
2916 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
2917 };
2918
2919 function _traverse(how) {
2920 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
2921
2922 if (t[START_CONTAINER] == t[END_CONTAINER])
2923 return _traverseSameContainer(how);
2924
2925 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
2926 if (p == t[START_CONTAINER])
2927 return _traverseCommonStartContainer(c, how);
2928
2929 ++endContainerDepth;
2930 }
2931
2932 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
2933 if (p == t[END_CONTAINER])
2934 return _traverseCommonEndContainer(c, how);
2935
2936 ++startContainerDepth;
2937 }
2938
2939 depthDiff = startContainerDepth - endContainerDepth;
2940
2941 startNode = t[START_CONTAINER];
2942 while (depthDiff > 0) {
2943 startNode = startNode.parentNode;
2944 depthDiff--;
2945 }
2946
2947 endNode = t[END_CONTAINER];
2948 while (depthDiff < 0) {
2949 endNode = endNode.parentNode;
2950 depthDiff++;
2951 }
2952
2953 // ascend the ancestor hierarchy until we have a common parent.
2954 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
2955 startNode = sp;
2956 endNode = ep;
2957 }
2958
2959 return _traverseCommonAncestors(startNode, endNode, how);
2960 };
2961
2962 function _traverseSameContainer(how) {
2963 var frag, s, sub, n, cnt, sibling, xferNode;
2964
2965 if (how != DELETE)
2966 frag = doc.createDocumentFragment();
2967
2968 // If selection is empty, just return the fragment
2969 if (t[START_OFFSET] == t[END_OFFSET])
2970 return frag;
2971
2972 // Text node needs special case handling
2973 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
2974 // get the substring
2975 s = t[START_CONTAINER].nodeValue;
2976 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
2977
2978 // set the original text node to its new value
2979 if (how != CLONE) {
2980 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
2981
2982 // Nothing is partially selected, so collapse to start point
2983 t.collapse(TRUE);
2984 }
2985
2986 if (how == DELETE)
2987 return;
2988
2989 frag.appendChild(doc.createTextNode(sub));
2990 return frag;
2991 }
2992
2993 // Copy nodes between the start/end offsets.
2994 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
2995 cnt = t[END_OFFSET] - t[START_OFFSET];
2996
2997 while (cnt > 0) {
2998 sibling = n.nextSibling;
2999 xferNode = _traverseFullySelected(n, how);
3000
3001 if (frag)
3002 frag.appendChild( xferNode );
3003
3004 --cnt;
3005 n = sibling;
3006 }
3007
3008 // Nothing is partially selected, so collapse to start point
3009 if (how != CLONE)
3010 t.collapse(TRUE);
3011
3012 return frag;
3013 };
3014
3015 function _traverseCommonStartContainer(endAncestor, how) {
3016 var frag, n, endIdx, cnt, sibling, xferNode;
3017
3018 if (how != DELETE)
3019 frag = doc.createDocumentFragment();
3020
3021 n = _traverseRightBoundary(endAncestor, how);
3022
3023 if (frag)
3024 frag.appendChild(n);
3025
3026 endIdx = nodeIndex(endAncestor);
3027 cnt = endIdx - t[START_OFFSET];
3028
3029 if (cnt <= 0) {
3030 // Collapse to just before the endAncestor, which
3031 // is partially selected.
3032 if (how != CLONE) {
3033 t.setEndBefore(endAncestor);
3034 t.collapse(FALSE);
3035 }
3036
3037 return frag;
3038 }
3039
3040 n = endAncestor.previousSibling;
3041 while (cnt > 0) {
3042 sibling = n.previousSibling;
3043 xferNode = _traverseFullySelected(n, how);
3044
3045 if (frag)
3046 frag.insertBefore(xferNode, frag.firstChild);
3047
3048 --cnt;
3049 n = sibling;
3050 }
3051
3052 // Collapse to just before the endAncestor, which
3053 // is partially selected.
3054 if (how != CLONE) {
3055 t.setEndBefore(endAncestor);
3056 t.collapse(FALSE);
3057 }
3058
3059 return frag;
3060 };
3061
3062 function _traverseCommonEndContainer(startAncestor, how) {
3063 var frag, startIdx, n, cnt, sibling, xferNode;
3064
3065 if (how != DELETE)
3066 frag = doc.createDocumentFragment();
3067
3068 n = _traverseLeftBoundary(startAncestor, how);
3069 if (frag)
3070 frag.appendChild(n);
3071
3072 startIdx = nodeIndex(startAncestor);
3073 ++startIdx; // Because we already traversed it....
3074
3075 cnt = t[END_OFFSET] - startIdx;
3076 n = startAncestor.nextSibling;
3077 while (cnt > 0) {
3078 sibling = n.nextSibling;
3079 xferNode = _traverseFullySelected(n, how);
3080
3081 if (frag)
3082 frag.appendChild(xferNode);
3083
3084 --cnt;
3085 n = sibling;
3086 }
3087
3088 if (how != CLONE) {
3089 t.setStartAfter(startAncestor);
3090 t.collapse(TRUE);
3091 }
3092
3093 return frag;
3094 };
3095
3096 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
3097 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
3098
3099 if (how != DELETE)
3100 frag = doc.createDocumentFragment();
3101
3102 n = _traverseLeftBoundary(startAncestor, how);
3103 if (frag)
3104 frag.appendChild(n);
3105
3106 commonParent = startAncestor.parentNode;
3107 startOffset = nodeIndex(startAncestor);
3108 endOffset = nodeIndex(endAncestor);
3109 ++startOffset;
3110
3111 cnt = endOffset - startOffset;
3112 sibling = startAncestor.nextSibling;
3113
3114 while (cnt > 0) {
3115 nextSibling = sibling.nextSibling;
3116 n = _traverseFullySelected(sibling, how);
3117
3118 if (frag)
3119 frag.appendChild(n);
3120
3121 sibling = nextSibling;
3122 --cnt;
3123 }
3124
3125 n = _traverseRightBoundary(endAncestor, how);
3126
3127 if (frag)
3128 frag.appendChild(n);
3129
3130 if (how != CLONE) {
3131 t.setStartAfter(startAncestor);
3132 t.collapse(TRUE);
3133 }
3134
3135 return frag;
3136 };
3137
3138 function _traverseRightBoundary(root, how) {
3139 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
3140
3141 if (next == root)
3142 return _traverseNode(next, isFullySelected, FALSE, how);
3143
3144 parent = next.parentNode;
3145 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
3146
3147 while (parent) {
3148 while (next) {
3149 prevSibling = next.previousSibling;
3150 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
3151
3152 if (how != DELETE)
3153 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
3154
3155 isFullySelected = TRUE;
3156 next = prevSibling;
3157 }
3158
3159 if (parent == root)
3160 return clonedParent;
3161
3162 next = parent.previousSibling;
3163 parent = parent.parentNode;
3164
3165 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
3166
3167 if (how != DELETE)
3168 clonedGrandParent.appendChild(clonedParent);
3169
3170 clonedParent = clonedGrandParent;
3171 }
3172 };
3173
3174 function _traverseLeftBoundary(root, how) {
3175 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
3176
3177 if (next == root)
3178 return _traverseNode(next, isFullySelected, TRUE, how);
3179
3180 parent = next.parentNode;
3181 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
3182
3183 while (parent) {
3184 while (next) {
3185 nextSibling = next.nextSibling;
3186 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
3187
3188 if (how != DELETE)
3189 clonedParent.appendChild(clonedChild);
3190
3191 isFullySelected = TRUE;
3192 next = nextSibling;
3193 }
3194
3195 if (parent == root)
3196 return clonedParent;
3197
3198 next = parent.nextSibling;
3199 parent = parent.parentNode;
3200
3201 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
3202
3203 if (how != DELETE)
3204 clonedGrandParent.appendChild(clonedParent);
3205
3206 clonedParent = clonedGrandParent;
3207 }
3208 };
3209
3210 function _traverseNode(n, isFullySelected, isLeft, how) {
3211 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
3212
3213 if (isFullySelected)
3214 return _traverseFullySelected(n, how);
3215
3216 if (n.nodeType == 3 /* TEXT_NODE */) {
3217 txtValue = n.nodeValue;
3218
3219 if (isLeft) {
3220 offset = t[START_OFFSET];
3221 newNodeValue = txtValue.substring(offset);
3222 oldNodeValue = txtValue.substring(0, offset);
3223 } else {
3224 offset = t[END_OFFSET];
3225 newNodeValue = txtValue.substring(0, offset);
3226 oldNodeValue = txtValue.substring(offset);
3227 }
3228
3229 if (how != CLONE)
3230 n.nodeValue = oldNodeValue;
3231
3232 if (how == DELETE)
3233 return;
3234
3235 newNode = n.cloneNode(FALSE);
3236 newNode.nodeValue = newNodeValue;
3237
3238 return newNode;
3239 }
3240
3241 if (how == DELETE)
3242 return;
3243
3244 return n.cloneNode(FALSE);
3245 };
3246
3247 function _traverseFullySelected(n, how) {
3248 if (how != DELETE)
3249 return how == CLONE ? n.cloneNode(TRUE) : n;
3250
3251 n.parentNode.removeChild(n);
3252 };
3253 };
3254
3255 ns.Range = Range;
3256 })(tinymce.dom);
3257
3258 (function() {
3259 function Selection(selection) {
3260 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
3261
3262 // Returns a W3C DOM compatible range object by using the IE Range API
3263 function getRange() {
3264 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
3265
3266 // If selection is outside the current document just return an empty range
3267 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
3268 if (element.ownerDocument != dom.doc)
3269 return domRange;
3270
3271 // Handle control selection or text selection of a image
3272 if (ieRange.item || !element.hasChildNodes()) {
3273 domRange.setStart(element.parentNode, dom.nodeIndex(element));
3274 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
3275
3276 return domRange;
3277 }
3278
3279 collapsed = selection.isCollapsed();
3280
3281 function findEndPoint(start) {
3282 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
3283
3284 // Setup temp range and collapse it
3285 checkRng = ieRange.duplicate();
3286 checkRng.collapse(start);
3287
3288 // Create marker and insert it at the end of the endpoints parent
3289 marker = dom.create('a');
3290 parent = checkRng.parentElement();
3291
3292 // If parent doesn't have any children then set the container to that parent and the index to 0
3293 if (!parent.hasChildNodes()) {
3294 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
3295 return;
3296 }
3297
3298 parent.appendChild(marker);
3299 checkRng.moveToElementText(marker);
3300 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
3301 if (position > 0) {
3302 // The position is after the end of the parent element.
3303 // This is the case where IE puts the caret to the left edge of a table.
3304 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
3305 dom.remove(marker);
3306 return;
3307 }
3308
3309 // Setup node list and endIndex
3310 nodes = tinymce.grep(parent.childNodes);
3311 endIndex = nodes.length - 1;
3312 // Perform a binary search for the position
3313 while (startIndex <= endIndex) {
3314 index = Math.floor((startIndex + endIndex) / 2);
3315
3316 // Insert marker and check it's position relative to the selection
3317 parent.insertBefore(marker, nodes[index]);
3318 checkRng.moveToElementText(marker);
3319 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
3320 if (position > 0) {
3321 // Marker is to the right
3322 startIndex = index + 1;
3323 } else if (position < 0) {
3324 // Marker is to the left
3325 endIndex = index - 1;
3326 } else {
3327 // Maker is where we are
3328 found = true;
3329 break;
3330 }
3331 }
3332
3333 // Setup container
3334 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
3335
3336 // Handle element selection
3337 if (container.nodeType == 1) {
3338 dom.remove(marker);
3339
3340 // Find offset and container
3341 offset = dom.nodeIndex(container);
3342 container = container.parentNode;
3343
3344 // Move the offset if we are setting the end or the position is after an element
3345 if (!start || index > 0)
3346 offset++;
3347 } else {
3348 // Calculate offset within text node
3349 if (position > 0 || index == 0) {
3350 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
3351 offset = checkRng.text.length;
3352 } else {
3353 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
3354 offset = container.nodeValue.length - checkRng.text.length;
3355 }
3356
3357 dom.remove(marker);
3358 }
3359
3360 domRange[start ? 'setStart' : 'setEnd'](container, offset);
3361 };
3362
3363 // Find start point
3364 findEndPoint(true);
3365
3366 // Find end point if needed
3367 if (!collapsed)
3368 findEndPoint();
3369
3370 return domRange;
3371 };
3372
3373 this.addRange = function(rng) {
3374 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
3375
3376 function setEndPoint(start) {
3377 var container, offset, marker, tmpRng, nodes;
3378
3379 marker = dom.create('a');
3380 container = start ? startContainer : endContainer;
3381 offset = start ? startOffset : endOffset;
3382 tmpRng = ieRng.duplicate();
3383
3384 if (container == doc) {
3385 container = body;
3386 offset = 0;
3387 }
3388
3389 if (container.nodeType == 3) {
3390 container.parentNode.insertBefore(marker, container);
3391 tmpRng.moveToElementText(marker);
3392 tmpRng.moveStart('character', offset);
3393 dom.remove(marker);
3394 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
3395 } else {
3396 nodes = container.childNodes;
3397
3398 if (nodes.length) {
3399 if (offset >= nodes.length) {
3400 dom.insertAfter(marker, nodes[nodes.length - 1]);
3401 } else {
3402 container.insertBefore(marker, nodes[offset]);
3403 }
3404
3405 tmpRng.moveToElementText(marker);
3406 } else {
3407 // Empty node selection for example <div>|</div>
3408 marker = doc.createTextNode(invisibleChar);
3409 container.appendChild(marker);
3410 tmpRng.moveToElementText(marker.parentNode);
3411 tmpRng.collapse(TRUE);
3412 }
3413
3414 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
3415 dom.remove(marker);
3416 }
3417 }
3418
3419 // Destroy cached range
3420 this.destroy();
3421
3422 // Setup some shorter versions
3423 startContainer = rng.startContainer;
3424 startOffset = rng.startOffset;
3425 endContainer = rng.endContainer;
3426 endOffset = rng.endOffset;
3427 ieRng = body.createTextRange();
3428
3429 // If single element selection then try making a control selection out of it
3430 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
3431 if (startOffset == endOffset - 1) {
3432 try {
3433 ctrlRng = body.createControlRange();
3434 ctrlRng.addElement(startContainer.childNodes[startOffset]);
3435 ctrlRng.select();
3436 ctrlRng.scrollIntoView();
3437 return;
3438 } catch (ex) {
3439 // Ignore
3440 }
3441 }
3442 }
3443
3444 // Set start/end point of selection
3445 setEndPoint(true);
3446 setEndPoint();
3447
3448 // Select the new range and scroll it into view
3449 ieRng.select();
3450 ieRng.scrollIntoView();
3451 };
3452
3453 this.getRangeAt = function() {
3454 // Setup new range if the cache is empty
3455 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
3456 range = getRange();
3457
3458 // Store away text range for next call
3459 lastIERng = selection.getRng();
3460 }
3461
3462 // IE will say that the range is equal then produce an invalid argument exception
3463 // if you perform specific operations in a keyup event. For example Ctrl+Del.
3464 // This hack will invalidate the range cache if the exception occurs
3465 try {
3466 range.startContainer.nextSibling;
3467 } catch (ex) {
3468 range = getRange();
3469 lastIERng = null;
3470 }
3471
3472 // Return cached range
3473 return range;
3474 };
3475
3476 this.destroy = function() {
3477 // Destroy cached range and last IE range to avoid memory leaks
3478 lastIERng = range = null;
3479 };
3480
3481 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
3482 if (selection.dom.boxModel) {
3483 (function() {
3484 var doc = dom.doc, body = doc.body, started, startRng;
3485
3486 // Make HTML element unselectable since we are going to handle selection by hand
3487 doc.documentElement.unselectable = TRUE;
3488
3489 // Return range from point or null if it failed
3490 function rngFromPoint(x, y) {
3491 var rng = body.createTextRange();
3492
3493 try {
3494 rng.moveToPoint(x, y);
3495 } catch (ex) {
3496 // IE sometimes throws and exception, so lets just ignore it
3497 rng = null;
3498 }
3499
3500 return rng;
3501 };
3502
3503 // Fires while the selection is changing
3504 function selectionChange(e) {
3505 var pointRng;
3506
3507 // Check if the button is down or not
3508 if (e.button) {
3509 // Create range from mouse position
3510 pointRng = rngFromPoint(e.x, e.y);
3511
3512 if (pointRng) {
3513 // Check if pointRange is before/after selection then change the endPoint
3514 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
3515 pointRng.setEndPoint('StartToStart', startRng);
3516 else
3517 pointRng.setEndPoint('EndToEnd', startRng);
3518
3519 pointRng.select();
3520 }
3521 } else
3522 endSelection();
3523 }
3524
3525 // Removes listeners
3526 function endSelection() {
3527 dom.unbind(doc, 'mouseup', endSelection);
3528 dom.unbind(doc, 'mousemove', selectionChange);
3529 started = 0;
3530 };
3531
3532 // Detect when user selects outside BODY
3533 dom.bind(doc, 'mousedown', function(e) {
3534 if (e.target.nodeName === 'HTML') {
3535 if (started)
3536 endSelection();
3537
3538 started = 1;
3539
3540 // Setup start position
3541 startRng = rngFromPoint(e.x, e.y);
3542 if (startRng) {
3543 // Listen for selection change events
3544 dom.bind(doc, 'mouseup', endSelection);
3545 dom.bind(doc, 'mousemove', selectionChange);
3546
3547 startRng.select();
3548 }
3549 }
3550 });
3551 })();
3552 }
3553 };
3554
3555 // Expose the selection object
3556 tinymce.dom.TridentSelection = Selection;
3557 })();
3558
3559
3560 /*
3561 * Sizzle CSS Selector Engine - v1.0
3562 * Copyright 2009, The Dojo Foundation
3563 * Released under the MIT, BSD, and GPL Licenses.
3564 * More information: http://sizzlejs.com/
3565 */
3566 (function(){
3567
3568 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
3569 done = 0,
3570 toString = Object.prototype.toString,
3571 hasDuplicate = false,
3572 baseHasDuplicate = true;
3573
3574 // Here we check if the JavaScript engine is using some sort of
3575 // optimization where it does not always call our comparision
3576 // function. If that is the case, discard the hasDuplicate value.
3577 // Thus far that includes Google Chrome.
3578 [0, 0].sort(function(){
3579 baseHasDuplicate = false;
3580 return 0;
3581 });
3582
3583 var Sizzle = function(selector, context, results, seed) {
3584 results = results || [];
3585 context = context || document;
3586
3587 var origContext = context;
3588
3589 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
3590 return [];
3591 }
3592
3593 if ( !selector || typeof selector !== "string" ) {
3594 return results;
3595 }
3596
3597 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
3598 soFar = selector, ret, cur, pop, i;
3599
3600 // Reset the position of the chunker regexp (start from head)
3601 do {
3602 chunker.exec("");
3603 m = chunker.exec(soFar);
3604
3605 if ( m ) {
3606 soFar = m[3];
3607
3608 parts.push( m[1] );
3609
3610 if ( m[2] ) {
3611 extra = m[3];
3612 break;
3613 }
3614 }
3615 } while ( m );
3616
3617 if ( parts.length > 1 && origPOS.exec( selector ) ) {
3618 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
3619 set = posProcess( parts[0] + parts[1], context );
3620 } else {
3621 set = Expr.relative[ parts[0] ] ?
3622 [ context ] :
3623 Sizzle( parts.shift(), context );
3624
3625 while ( parts.length ) {
3626 selector = parts.shift();
3627
3628 if ( Expr.relative[ selector ] ) {
3629 selector += parts.shift();
3630 }
3631
3632 set = posProcess( selector, set );
3633 }
3634 }
3635 } else {
3636 // Take a shortcut and set the context if the root selector is an ID
3637 // (but not if it'll be faster if the inner selector is an ID)
3638 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
3639 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
3640 ret = Sizzle.find( parts.shift(), context, contextXML );
3641 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
3642 }
3643
3644 if ( context ) {
3645 ret = seed ?
3646 { expr: parts.pop(), set: makeArray(seed) } :
3647 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
3648 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
3649
3650 if ( parts.length > 0 ) {
3651 checkSet = makeArray(set);
3652 } else {
3653 prune = false;
3654 }
3655
3656 while ( parts.length ) {
3657 cur = parts.pop();
3658 pop = cur;
3659
3660 if ( !Expr.relative[ cur ] ) {
3661 cur = "";
3662 } else {
3663 pop = parts.pop();
3664 }
3665
3666 if ( pop == null ) {
3667 pop = context;
3668 }
3669
3670 Expr.relative[ cur ]( checkSet, pop, contextXML );
3671 }
3672 } else {
3673 checkSet = parts = [];
3674 }
3675 }
3676
3677 if ( !checkSet ) {
3678 checkSet = set;
3679 }
3680
3681 if ( !checkSet ) {
3682 Sizzle.error( cur || selector );
3683 }
3684
3685 if ( toString.call(checkSet) === "[object Array]" ) {
3686 if ( !prune ) {
3687 results.push.apply( results, checkSet );
3688 } else if ( context && context.nodeType === 1 ) {
3689 for ( i = 0; checkSet[i] != null; i++ ) {
3690 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
3691 results.push( set[i] );
3692 }
3693 }
3694 } else {
3695 for ( i = 0; checkSet[i] != null; i++ ) {
3696 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
3697 results.push( set[i] );
3698 }
3699 }
3700 }
3701 } else {
3702 makeArray( checkSet, results );
3703 }
3704
3705 if ( extra ) {
3706 Sizzle( extra, origContext, results, seed );
3707 Sizzle.uniqueSort( results );
3708 }
3709
3710 return results;
3711 };
3712
3713 Sizzle.uniqueSort = function(results){
3714 if ( sortOrder ) {
3715 hasDuplicate = baseHasDuplicate;
3716 results.sort(sortOrder);
3717
3718 if ( hasDuplicate ) {
3719 for ( var i = 1; i < results.length; i++ ) {
3720 if ( results[i] === results[i-1] ) {
3721 results.splice(i--, 1);
3722 }
3723 }
3724 }
3725 }
3726
3727 return results;
3728 };
3729
3730 Sizzle.matches = function(expr, set){
3731 return Sizzle(expr, null, null, set);
3732 };
3733
3734 Sizzle.find = function(expr, context, isXML){
3735 var set;
3736
3737 if ( !expr ) {
3738 return [];
3739 }
3740
3741 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
3742 var type = Expr.order[i], match;
3743
3744 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
3745 var left = match[1];
3746 match.splice(1,1);
3747
3748 if ( left.substr( left.length - 1 ) !== "\\" ) {
3749 match[1] = (match[1] || "").replace(/\\/g, "");
3750 set = Expr.find[ type ]( match, context, isXML );
3751 if ( set != null ) {
3752 expr = expr.replace( Expr.match[ type ], "" );
3753 break;
3754 }
3755 }
3756 }
3757 }
3758
3759 if ( !set ) {
3760 set = context.getElementsByTagName("*");
3761 }
3762
3763 return {set: set, expr: expr};
3764 };
3765
3766 Sizzle.filter = function(expr, set, inplace, not){
3767 var old = expr, result = [], curLoop = set, match, anyFound,
3768 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
3769
3770 while ( expr && set.length ) {
3771 for ( var type in Expr.filter ) {
3772 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
3773 var filter = Expr.filter[ type ], found, item, left = match[1];
3774 anyFound = false;
3775
3776 match.splice(1,1);
3777
3778 if ( left.substr( left.length - 1 ) === "\\" ) {
3779 continue;
3780 }
3781
3782 if ( curLoop === result ) {
3783 result = [];
3784 }
3785
3786 if ( Expr.preFilter[ type ] ) {
3787 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
3788
3789 if ( !match ) {
3790 anyFound = found = true;
3791 } else if ( match === true ) {
3792 continue;
3793 }
3794 }
3795
3796 if ( match ) {
3797 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
3798 if ( item ) {
3799 found = filter( item, match, i, curLoop );
3800 var pass = not ^ !!found;
3801
3802 if ( inplace && found != null ) {
3803 if ( pass ) {
3804 anyFound = true;
3805 } else {
3806 curLoop[i] = false;
3807 }
3808 } else if ( pass ) {
3809 result.push( item );
3810 anyFound = true;
3811 }
3812 }
3813 }
3814 }
3815
3816 if ( found !== undefined ) {
3817 if ( !inplace ) {
3818 curLoop = result;
3819 }
3820
3821 expr = expr.replace( Expr.match[ type ], "" );
3822
3823 if ( !anyFound ) {
3824 return [];
3825 }
3826
3827 break;
3828 }
3829 }
3830 }
3831
3832 // Improper expression
3833 if ( expr === old ) {
3834 if ( anyFound == null ) {
3835 Sizzle.error( expr );
3836 } else {
3837 break;
3838 }
3839 }
3840
3841 old = expr;
3842 }
3843
3844 return curLoop;
3845 };
3846
3847 Sizzle.error = function( msg ) {
3848 throw "Syntax error, unrecognized expression: " + msg;
3849 };
3850
3851 var Expr = Sizzle.selectors = {
3852 order: [ "ID", "NAME", "TAG" ],
3853 match: {
3854 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
3855 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
3856 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
3857 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
3858 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
3859 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
3860 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
3861 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
3862 },
3863 leftMatch: {},
3864 attrMap: {
3865 "class": "className",
3866 "for": "htmlFor"
3867 },
3868 attrHandle: {
3869 href: function(elem){
3870 return elem.getAttribute("href");
3871 }
3872 },
3873 relative: {
3874 "+": function(checkSet, part){
3875 var isPartStr = typeof part === "string",
3876 isTag = isPartStr && !/\W/.test(part),
3877 isPartStrNotTag = isPartStr && !isTag;
3878
3879 if ( isTag ) {
3880 part = part.toLowerCase();
3881 }
3882
3883 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
3884 if ( (elem = checkSet[i]) ) {
3885 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
3886
3887 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
3888 elem || false :
3889 elem === part;
3890 }
3891 }
3892
3893 if ( isPartStrNotTag ) {
3894 Sizzle.filter( part, checkSet, true );
3895 }
3896 },
3897 ">": function(checkSet, part){
3898 var isPartStr = typeof part === "string",
3899 elem, i = 0, l = checkSet.length;
3900
3901 if ( isPartStr && !/\W/.test(part) ) {
3902 part = part.toLowerCase();
3903
3904 for ( ; i < l; i++ ) {
3905 elem = checkSet[i];
3906 if ( elem ) {
3907 var parent = elem.parentNode;
3908 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
3909 }
3910 }
3911 } else {
3912 for ( ; i < l; i++ ) {
3913 elem = checkSet[i];
3914 if ( elem ) {
3915 checkSet[i] = isPartStr ?
3916 elem.parentNode :
3917 elem.parentNode === part;
3918 }
3919 }
3920
3921 if ( isPartStr ) {
3922 Sizzle.filter( part, checkSet, true );
3923 }
3924 }
3925 },
3926 "": function(checkSet, part, isXML){
3927 var doneName = done++, checkFn = dirCheck, nodeCheck;
3928
3929 if ( typeof part === "string" && !/\W/.test(part) ) {
3930 part = part.toLowerCase();
3931 nodeCheck = part;
3932 checkFn = dirNodeCheck;
3933 }
3934
3935 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
3936 },
3937 "~": function(checkSet, part, isXML){
3938 var doneName = done++, checkFn = dirCheck, nodeCheck;
3939
3940 if ( typeof part === "string" && !/\W/.test(part) ) {
3941 part = part.toLowerCase();
3942 nodeCheck = part;
3943 checkFn = dirNodeCheck;
3944 }
3945
3946 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
3947 }
3948 },
3949 find: {
3950 ID: function(match, context, isXML){
3951 if ( typeof context.getElementById !== "undefined" && !isXML ) {
3952 var m = context.getElementById(match[1]);
3953 return m ? [m] : [];
3954 }
3955 },
3956 NAME: function(match, context){
3957 if ( typeof context.getElementsByName !== "undefined" ) {
3958 var ret = [], results = context.getElementsByName(match[1]);
3959
3960 for ( var i = 0, l = results.length; i < l; i++ ) {
3961 if ( results[i].getAttribute("name") === match[1] ) {
3962 ret.push( results[i] );
3963 }
3964 }
3965
3966 return ret.length === 0 ? null : ret;
3967 }
3968 },
3969 TAG: function(match, context){
3970 return context.getElementsByTagName(match[1]);
3971 }
3972 },
3973 preFilter: {
3974 CLASS: function(match, curLoop, inplace, result, not, isXML){
3975 match = " " + match[1].replace(/\\/g, "") + " ";
3976
3977 if ( isXML ) {
3978 return match;
3979 }
3980
3981 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
3982 if ( elem ) {
3983 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
3984 if ( !inplace ) {
3985 result.push( elem );
3986 }
3987 } else if ( inplace ) {
3988 curLoop[i] = false;
3989 }
3990 }
3991 }
3992
3993 return false;
3994 },
3995 ID: function(match){
3996 return match[1].replace(/\\/g, "");
3997 },
3998 TAG: function(match, curLoop){
3999 return match[1].toLowerCase();
4000 },
4001 CHILD: function(match){
4002 if ( match[1] === "nth" ) {
4003 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
4004 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
4005 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
4006 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
4007
4008 // calculate the numbers (first)n+(last) including if they are negative
4009 match[2] = (test[1] + (test[2] || 1)) - 0;
4010 match[3] = test[3] - 0;
4011 }
4012
4013 // TODO: Move to normal caching system
4014 match[0] = done++;
4015
4016 return match;
4017 },
4018 ATTR: function(match, curLoop, inplace, result, not, isXML){
4019 var name = match[1].replace(/\\/g, "");
4020
4021 if ( !isXML && Expr.attrMap[name] ) {
4022 match[1] = Expr.attrMap[name];
4023 }
4024
4025 if ( match[2] === "~=" ) {
4026 match[4] = " " + match[4] + " ";
4027 }
4028
4029 return match;
4030 },
4031 PSEUDO: function(match, curLoop, inplace, result, not){
4032 if ( match[1] === "not" ) {
4033 // If we're dealing with a complex expression, or a simple one
4034 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
4035 match[3] = Sizzle(match[3], null, null, curLoop);
4036 } else {
4037 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
4038 if ( !inplace ) {
4039 result.push.apply( result, ret );
4040 }
4041 return false;
4042 }
4043 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
4044 return true;
4045 }
4046
4047 return match;
4048 },
4049 POS: function(match){
4050 match.unshift( true );
4051 return match;
4052 }
4053 },
4054 filters: {
4055 enabled: function(elem){
4056 return elem.disabled === false && elem.type !== "hidden";
4057 },
4058 disabled: function(elem){
4059 return elem.disabled === true;
4060 },
4061 checked: function(elem){
4062 return elem.checked === true;
4063 },
4064 selected: function(elem){
4065 // Accessing this property makes selected-by-default
4066 // options in Safari work properly
4067 elem.parentNode.selectedIndex;
4068 return elem.selected === true;
4069 },
4070 parent: function(elem){
4071 return !!elem.firstChild;
4072 },
4073 empty: function(elem){
4074 return !elem.firstChild;
4075 },
4076 has: function(elem, i, match){
4077 return !!Sizzle( match[3], elem ).length;
4078 },
4079 header: function(elem){
4080 return (/h\d/i).test( elem.nodeName );
4081 },
4082 text: function(elem){
4083 return "text" === elem.type;
4084 },
4085 radio: function(elem){
4086 return "radio" === elem.type;
4087 },
4088 checkbox: function(elem){
4089 return "checkbox" === elem.type;
4090 },
4091 file: function(elem){
4092 return "file" === elem.type;
4093 },
4094 password: function(elem){
4095 return "password" === elem.type;
4096 },
4097 submit: function(elem){
4098 return "submit" === elem.type;
4099 },
4100 image: function(elem){
4101 return "image" === elem.type;
4102 },
4103 reset: function(elem){
4104 return "reset" === elem.type;
4105 },
4106 button: function(elem){
4107 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
4108 },
4109 input: function(elem){
4110 return (/input|select|textarea|button/i).test(elem.nodeName);
4111 }
4112 },
4113 setFilters: {
4114 first: function(elem, i){
4115 return i === 0;
4116 },
4117 last: function(elem, i, match, array){
4118 return i === array.length - 1;
4119 },
4120 even: function(elem, i){
4121 return i % 2 === 0;
4122 },
4123 odd: function(elem, i){
4124 return i % 2 === 1;
4125 },
4126 lt: function(elem, i, match){
4127 return i < match[3] - 0;
4128 },
4129 gt: function(elem, i, match){
4130 return i > match[3] - 0;
4131 },
4132 nth: function(elem, i, match){
4133 return match[3] - 0 === i;
4134 },
4135 eq: function(elem, i, match){
4136 return match[3] - 0 === i;
4137 }
4138 },
4139 filter: {
4140 PSEUDO: function(elem, match, i, array){
4141 var name = match[1], filter = Expr.filters[ name ];
4142
4143 if ( filter ) {
4144 return filter( elem, i, match, array );
4145 } else if ( name === "contains" ) {
4146 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
4147 } else if ( name === "not" ) {
4148 var not = match[3];
4149
4150 for ( var j = 0, l = not.length; j < l; j++ ) {
4151 if ( not[j] === elem ) {
4152 return false;
4153 }
4154 }
4155
4156 return true;
4157 } else {
4158 Sizzle.error( "Syntax error, unrecognized expression: " + name );
4159 }
4160 },
4161 CHILD: function(elem, match){
4162 var type = match[1], node = elem;
4163 switch (type) {
4164 case 'only':
4165 case 'first':
4166 while ( (node = node.previousSibling) ) {
4167 if ( node.nodeType === 1 ) {
4168 return false;
4169 }
4170 }
4171 if ( type === "first" ) {
4172 return true;
4173 }
4174 node = elem;
4175 case 'last':
4176 while ( (node = node.nextSibling) ) {
4177 if ( node.nodeType === 1 ) {
4178 return false;
4179 }
4180 }
4181 return true;
4182 case 'nth':
4183 var first = match[2], last = match[3];
4184
4185 if ( first === 1 && last === 0 ) {
4186 return true;
4187 }
4188
4189 var doneName = match[0],
4190 parent = elem.parentNode;
4191
4192 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
4193 var count = 0;
4194 for ( node = parent.firstChild; node; node = node.nextSibling ) {
4195 if ( node.nodeType === 1 ) {
4196 node.nodeIndex = ++count;
4197 }
4198 }
4199 parent.sizcache = doneName;
4200 }
4201
4202 var diff = elem.nodeIndex - last;
4203 if ( first === 0 ) {
4204 return diff === 0;
4205 } else {
4206 return ( diff % first === 0 && diff / first >= 0 );
4207 }
4208 }
4209 },
4210 ID: function(elem, match){
4211 return elem.nodeType === 1 && elem.getAttribute("id") === match;
4212 },
4213 TAG: function(elem, match){
4214 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
4215 },
4216 CLASS: function(elem, match){
4217 return (" " + (elem.className || elem.getAttribute("class")) + " ")
4218 .indexOf( match ) > -1;
4219 },
4220 ATTR: function(elem, match){
4221 var name = match[1],
4222 result = Expr.attrHandle[ name ] ?
4223 Expr.attrHandle[ name ]( elem ) :
4224 elem[ name ] != null ?
4225 elem[ name ] :
4226 elem.getAttribute( name ),
4227 value = result + "",
4228 type = match[2],
4229 check = match[4];
4230
4231 return result == null ?
4232 type === "!=" :
4233 type === "=" ?
4234 value === check :
4235 type === "*=" ?
4236 value.indexOf(check) >= 0 :
4237 type === "~=" ?
4238 (" " + value + " ").indexOf(check) >= 0 :
4239 !check ?
4240 value && result !== false :
4241 type === "!=" ?
4242 value !== check :
4243 type === "^=" ?
4244 value.indexOf(check) === 0 :
4245 type === "$=" ?
4246 value.substr(value.length - check.length) === check :
4247 type === "|=" ?
4248 value === check || value.substr(0, check.length + 1) === check + "-" :
4249 false;
4250 },
4251 POS: function(elem, match, i, array){
4252 var name = match[2], filter = Expr.setFilters[ name ];
4253
4254 if ( filter ) {
4255 return filter( elem, i, match, array );
4256 }
4257 }
4258 }
4259 };
4260
4261 var origPOS = Expr.match.POS,
4262 fescape = function(all, num){
4263 return "\\" + (num - 0 + 1);
4264 };
4265
4266 for ( var type in Expr.match ) {
4267 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
4268 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
4269 }
4270
4271 var makeArray = function(array, results) {
4272 array = Array.prototype.slice.call( array, 0 );
4273
4274 if ( results ) {
4275 results.push.apply( results, array );
4276 return results;
4277 }
4278
4279 return array;
4280 };
4281
4282 // Perform a simple check to determine if the browser is capable of
4283 // converting a NodeList to an array using builtin methods.
4284 // Also verifies that the returned array holds DOM nodes
4285 // (which is not the case in the Blackberry browser)
4286 try {
4287 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
4288
4289 // Provide a fallback method if it does not work
4290 } catch(e){
4291 makeArray = function(array, results) {
4292 var ret = results || [], i = 0;
4293
4294 if ( toString.call(array) === "[object Array]" ) {
4295 Array.prototype.push.apply( ret, array );
4296 } else {
4297 if ( typeof array.length === "number" ) {
4298 for ( var l = array.length; i < l; i++ ) {
4299 ret.push( array[i] );
4300 }
4301 } else {
4302 for ( ; array[i]; i++ ) {
4303 ret.push( array[i] );
4304 }
4305 }
4306 }
4307
4308 return ret;
4309 };
4310 }
4311
4312 var sortOrder;
4313
4314 if ( document.documentElement.compareDocumentPosition ) {
4315 sortOrder = function( a, b ) {
4316 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
4317 if ( a == b ) {
4318 hasDuplicate = true;
4319 }
4320 return a.compareDocumentPosition ? -1 : 1;
4321 }
4322
4323 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
4324 if ( ret === 0 ) {
4325 hasDuplicate = true;
4326 }
4327 return ret;
4328 };
4329 } else if ( "sourceIndex" in document.documentElement ) {
4330 sortOrder = function( a, b ) {
4331 if ( !a.sourceIndex || !b.sourceIndex ) {
4332 if ( a == b ) {
4333 hasDuplicate = true;
4334 }
4335 return a.sourceIndex ? -1 : 1;
4336 }
4337
4338 var ret = a.sourceIndex - b.sourceIndex;
4339 if ( ret === 0 ) {
4340 hasDuplicate = true;
4341 }
4342 return ret;
4343 };
4344 } else if ( document.createRange ) {
4345 sortOrder = function( a, b ) {
4346 if ( !a.ownerDocument || !b.ownerDocument ) {
4347 if ( a == b ) {
4348 hasDuplicate = true;
4349 }
4350 return a.ownerDocument ? -1 : 1;
4351 }
4352
4353 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
4354 aRange.setStart(a, 0);
4355 aRange.setEnd(a, 0);
4356 bRange.setStart(b, 0);
4357 bRange.setEnd(b, 0);
4358 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
4359 if ( ret === 0 ) {
4360 hasDuplicate = true;
4361 }
4362 return ret;
4363 };
4364 }
4365
4366 // Utility function for retreiving the text value of an array of DOM nodes
4367 Sizzle.getText = function( elems ) {
4368 var ret = "", elem;
4369
4370 for ( var i = 0; elems[i]; i++ ) {
4371 elem = elems[i];
4372
4373 // Get the text from text nodes and CDATA nodes
4374 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
4375 ret += elem.nodeValue;
4376
4377 // Traverse everything else, except comment nodes
4378 } else if ( elem.nodeType !== 8 ) {
4379 ret += Sizzle.getText( elem.childNodes );
4380 }
4381 }
4382
4383 return ret;
4384 };
4385
4386 // Check to see if the browser returns elements by name when
4387 // querying by getElementById (and provide a workaround)
4388 (function(){
4389 // We're going to inject a fake input element with a specified name
4390 var form = document.createElement("div"),
4391 id = "script" + (new Date()).getTime();
4392 form.innerHTML = "<a name='" + id + "'/>";
4393
4394 // Inject it into the root element, check its status, and remove it quickly
4395 var root = document.documentElement;
4396 root.insertBefore( form, root.firstChild );
4397
4398 // The workaround has to do additional checks after a getElementById
4399 // Which slows things down for other browsers (hence the branching)
4400 if ( document.getElementById( id ) ) {
4401 Expr.find.ID = function(match, context, isXML){
4402 if ( typeof context.getElementById !== "undefined" && !isXML ) {
4403 var m = context.getElementById(match[1]);
4404 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
4405 }
4406 };
4407
4408 Expr.filter.ID = function(elem, match){
4409 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
4410 return elem.nodeType === 1 && node && node.nodeValue === match;
4411 };
4412 }
4413
4414 root.removeChild( form );
4415 root = form = null; // release memory in IE
4416 })();
4417
4418 (function(){
4419 // Check to see if the browser returns only elements
4420 // when doing getElementsByTagName("*")
4421
4422 // Create a fake element
4423 var div = document.createElement("div");
4424 div.appendChild( document.createComment("") );
4425
4426 // Make sure no comments are found
4427 if ( div.getElementsByTagName("*").length > 0 ) {
4428 Expr.find.TAG = function(match, context){
4429 var results = context.getElementsByTagName(match[1]);
4430
4431 // Filter out possible comments
4432 if ( match[1] === "*" ) {
4433 var tmp = [];
4434
4435 for ( var i = 0; results[i]; i++ ) {
4436 if ( results[i].nodeType === 1 ) {
4437 tmp.push( results[i] );
4438 }
4439 }
4440
4441 results = tmp;
4442 }
4443
4444 return results;
4445 };
4446 }
4447
4448 // Check to see if an attribute returns normalized href attributes
4449 div.innerHTML = "<a href='#'></a>";
4450 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
4451 div.firstChild.getAttribute("href") !== "#" ) {
4452 Expr.attrHandle.href = function(elem){
4453 return elem.getAttribute("href", 2);
4454 };
4455 }
4456
4457 div = null; // release memory in IE
4458 })();
4459
4460 if ( document.querySelectorAll ) {
4461 (function(){
4462 var oldSizzle = Sizzle, div = document.createElement("div");
4463 div.innerHTML = "<p class='TEST'></p>";
4464
4465 // Safari can't handle uppercase or unicode characters when
4466 // in quirks mode.
4467 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
4468 return;
4469 }
4470
4471 Sizzle = function(query, context, extra, seed){
4472 context = context || document;
4473
4474 // Only use querySelectorAll on non-XML documents
4475 // (ID selectors don't work in non-HTML documents)
4476 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
4477 try {
4478 return makeArray( context.querySelectorAll(query), extra );
4479 } catch(e){}
4480 }
4481
4482 return oldSizzle(query, context, extra, seed);
4483 };
4484
4485 for ( var prop in oldSizzle ) {
4486 Sizzle[ prop ] = oldSizzle[ prop ];
4487 }
4488
4489 div = null; // release memory in IE
4490 })();
4491 }
4492
4493 (function(){
4494 var div = document.createElement("div");
4495
4496 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
4497
4498 // Opera can't find a second classname (in 9.6)
4499 // Also, make sure that getElementsByClassName actually exists
4500 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
4501 return;
4502 }
4503
4504 // Safari caches class attributes, doesn't catch changes (in 3.2)
4505 div.lastChild.className = "e";
4506
4507 if ( div.getElementsByClassName("e").length === 1 ) {
4508 return;
4509 }
4510
4511 Expr.order.splice(1, 0, "CLASS");
4512 Expr.find.CLASS = function(match, context, isXML) {
4513 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
4514 return context.getElementsByClassName(match[1]);
4515 }
4516 };
4517
4518 div = null; // release memory in IE
4519 })();
4520
4521 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
4522 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
4523 var elem = checkSet[i];
4524 if ( elem ) {
4525 elem = elem[dir];
4526 var match = false;
4527
4528 while ( elem ) {
4529 if ( elem.sizcache === doneName ) {
4530 match = checkSet[elem.sizset];
4531 break;
4532 }
4533
4534 if ( elem.nodeType === 1 && !isXML ){
4535 elem.sizcache = doneName;
4536 elem.sizset = i;
4537 }
4538
4539 if ( elem.nodeName.toLowerCase() === cur ) {
4540 match = elem;
4541 break;
4542 }
4543
4544 elem = elem[dir];
4545 }
4546
4547 checkSet[i] = match;
4548 }
4549 }
4550 }
4551
4552 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
4553 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
4554 var elem = checkSet[i];
4555 if ( elem ) {
4556 elem = elem[dir];
4557 var match = false;
4558
4559 while ( elem ) {
4560 if ( elem.sizcache === doneName ) {
4561 match = checkSet[elem.sizset];
4562 break;
4563 }
4564
4565 if ( elem.nodeType === 1 ) {
4566 if ( !isXML ) {
4567 elem.sizcache = doneName;
4568 elem.sizset = i;
4569 }
4570 if ( typeof cur !== "string" ) {
4571 if ( elem === cur ) {
4572 match = true;
4573 break;
4574 }
4575
4576 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
4577 match = elem;
4578 break;
4579 }
4580 }
4581
4582 elem = elem[dir];
4583 }
4584
4585 checkSet[i] = match;
4586 }
4587 }
4588 }
4589
4590 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
4591 return !!(a.compareDocumentPosition(b) & 16);
4592 } : function(a, b){
4593 return a !== b && (a.contains ? a.contains(b) : true);
4594 };
4595
4596 Sizzle.isXML = function(elem){
4597 // documentElement is verified for cases where it doesn't yet exist
4598 // (such as loading iframes in IE - #4833)
4599 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
4600 return documentElement ? documentElement.nodeName !== "HTML" : false;
4601 };
4602
4603 var posProcess = function(selector, context){
4604 var tmpSet = [], later = "", match,
4605 root = context.nodeType ? [context] : context;
4606
4607 // Position selectors must be done after the filter
4608 // And so must :not(positional) so we move all PSEUDOs to the end
4609 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
4610 later += match[0];
4611 selector = selector.replace( Expr.match.PSEUDO, "" );
4612 }
4613
4614 selector = Expr.relative[selector] ? selector + "*" : selector;
4615
4616 for ( var i = 0, l = root.length; i < l; i++ ) {
4617 Sizzle( selector, root[i], tmpSet );
4618 }
4619
4620 return Sizzle.filter( later, tmpSet );
4621 };
4622
4623 // EXPOSE
4624
4625 window.tinymce.dom.Sizzle = Sizzle;
4626
4627 })();
4628
4629
4630 (function(tinymce) {
4631 // Shorten names
4632 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
4633
4634 tinymce.create('tinymce.dom.EventUtils', {
4635 EventUtils : function() {
4636 this.inits = [];
4637 this.events = [];
4638 },
4639
4640 add : function(o, n, f, s) {
4641 var cb, t = this, el = t.events, r;
4642
4643 if (n instanceof Array) {
4644 r = [];
4645
4646 each(n, function(n) {
4647 r.push(t.add(o, n, f, s));
4648 });
4649
4650 return r;
4651 }
4652
4653 // Handle array
4654 if (o && o.hasOwnProperty && o instanceof Array) {
4655 r = [];
4656
4657 each(o, function(o) {
4658 o = DOM.get(o);
4659 r.push(t.add(o, n, f, s));
4660 });
4661
4662 return r;
4663 }
4664
4665 o = DOM.get(o);
4666
4667 if (!o)
4668 return;
4669
4670 // Setup event callback
4671 cb = function(e) {
4672 // Is all events disabled
4673 if (t.disabled)
4674 return;
4675
4676 e = e || window.event;
4677
4678 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
4679 if (e && isIE) {
4680 if (!e.target)
4681 e.target = e.srcElement;
4682
4683 // Patch in preventDefault, stopPropagation methods for W3C compatibility
4684 tinymce.extend(e, t._stoppers);
4685 }
4686
4687 if (!s)
4688 return f(e);
4689
4690 return f.call(s, e);
4691 };
4692
4693 if (n == 'unload') {
4694 tinymce.unloads.unshift({func : cb});
4695 return cb;
4696 }
4697
4698 if (n == 'init') {
4699 if (t.domLoaded)
4700 cb();
4701 else
4702 t.inits.push(cb);
4703
4704 return cb;
4705 }
4706
4707 // Store away listener reference
4708 el.push({
4709 obj : o,
4710 name : n,
4711 func : f,
4712 cfunc : cb,
4713 scope : s
4714 });
4715
4716 t._add(o, n, cb);
4717
4718 return f;
4719 },
4720
4721 remove : function(o, n, f) {
4722 var t = this, a = t.events, s = false, r;
4723
4724 // Handle array
4725 if (o && o.hasOwnProperty && o instanceof Array) {
4726 r = [];
4727
4728 each(o, function(o) {
4729 o = DOM.get(o);
4730 r.push(t.remove(o, n, f));
4731 });
4732
4733 return r;
4734 }
4735
4736 o = DOM.get(o);
4737
4738 each(a, function(e, i) {
4739 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
4740 a.splice(i, 1);
4741 t._remove(o, n, e.cfunc);
4742 s = true;
4743 return false;
4744 }
4745 });
4746
4747 return s;
4748 },
4749
4750 clear : function(o) {
4751 var t = this, a = t.events, i, e;
4752
4753 if (o) {
4754 o = DOM.get(o);
4755
4756 for (i = a.length - 1; i >= 0; i--) {
4757 e = a[i];
4758
4759 if (e.obj === o) {
4760 t._remove(e.obj, e.name, e.cfunc);
4761 e.obj = e.cfunc = null;
4762 a.splice(i, 1);
4763 }
4764 }
4765 }
4766 },
4767
4768 cancel : function(e) {
4769 if (!e)
4770 return false;
4771
4772 this.stop(e);
4773
4774 return this.prevent(e);
4775 },
4776
4777 stop : function(e) {
4778 if (e.stopPropagation)
4779 e.stopPropagation();
4780 else
4781 e.cancelBubble = true;
4782
4783 return false;
4784 },
4785
4786 prevent : function(e) {
4787 if (e.preventDefault)
4788 e.preventDefault();
4789 else
4790 e.returnValue = false;
4791
4792 return false;
4793 },
4794
4795 destroy : function() {
4796 var t = this;
4797
4798 each(t.events, function(e, i) {
4799 t._remove(e.obj, e.name, e.cfunc);
4800 e.obj = e.cfunc = null;
4801 });
4802
4803 t.events = [];
4804 t = null;
4805 },
4806
4807 _add : function(o, n, f) {
4808 if (o.attachEvent)
4809 o.attachEvent('on' + n, f);
4810 else if (o.addEventListener)
4811 o.addEventListener(n, f, false);
4812 else
4813 o['on' + n] = f;
4814 },
4815
4816 _remove : function(o, n, f) {
4817 if (o) {
4818 try {
4819 if (o.detachEvent)
4820 o.detachEvent('on' + n, f);
4821 else if (o.removeEventListener)
4822 o.removeEventListener(n, f, false);
4823 else
4824 o['on' + n] = null;
4825 } catch (ex) {
4826 // Might fail with permission denined on IE so we just ignore that
4827 }
4828 }
4829 },
4830
4831 _pageInit : function(win) {
4832 var t = this;
4833
4834 // Keep it from running more than once
4835 if (t.domLoaded)
4836 return;
4837
4838 t.domLoaded = true;
4839
4840 each(t.inits, function(c) {
4841 c();
4842 });
4843
4844 t.inits = [];
4845 },
4846
4847 _wait : function(win) {
4848 var t = this, doc = win.document;
4849
4850 // No need since the document is already loaded
4851 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
4852 t.domLoaded = 1;
4853 return;
4854 }
4855
4856 // Use IE method
4857 if (doc.attachEvent) {
4858 doc.attachEvent("onreadystatechange", function() {
4859 if (doc.readyState === "complete") {
4860 doc.detachEvent("onreadystatechange", arguments.callee);
4861 t._pageInit(win);
4862 }
4863 });
4864
4865 if (doc.documentElement.doScroll && win == win.top) {
4866 (function() {
4867 if (t.domLoaded)
4868 return;
4869
4870 try {
4871 // If IE is used, use the trick by Diego Perini
4872 // http://javascript.nwbox.com/IEContentLoaded/
4873 doc.documentElement.doScroll("left");
4874 } catch (ex) {
4875 setTimeout(arguments.callee, 0);
4876 return;
4877 }
4878
4879 t._pageInit(win);
4880 })();
4881 }
4882 } else if (doc.addEventListener) {
4883 t._add(win, 'DOMContentLoaded', function() {
4884 t._pageInit(win);
4885 });
4886 }
4887
4888 t._add(win, 'load', function() {
4889 t._pageInit(win);
4890 });
4891 },
4892
4893 _stoppers : {
4894 preventDefault : function() {
4895 this.returnValue = false;
4896 },
4897
4898 stopPropagation : function() {
4899 this.cancelBubble = true;
4900 }
4901 }
4902 });
4903
4904 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
4905
4906 // Dispatch DOM content loaded event for IE and Safari
4907 Event._wait(window);
4908
4909 tinymce.addUnload(function() {
4910 Event.destroy();
4911 });
4912 })(tinymce);
4913
4914 (function(tinymce) {
4915 tinymce.dom.Element = function(id, settings) {
4916 var t = this, dom, el;
4917
4918 t.settings = settings = settings || {};
4919 t.id = id;
4920 t.dom = dom = settings.dom || tinymce.DOM;
4921
4922 // Only IE leaks DOM references, this is a lot faster
4923 if (!tinymce.isIE)
4924 el = dom.get(t.id);
4925
4926 tinymce.each(
4927 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
4928 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
4929 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
4930 'isHidden,setHTML,get').split(/,/)
4931 , function(k) {
4932 t[k] = function() {
4933 var a = [id], i;
4934
4935 for (i = 0; i < arguments.length; i++)
4936 a.push(arguments[i]);
4937
4938 a = dom[k].apply(dom, a);
4939 t.update(k);
4940
4941 return a;
4942 };
4943 });
4944
4945 tinymce.extend(t, {
4946 on : function(n, f, s) {
4947 return tinymce.dom.Event.add(t.id, n, f, s);
4948 },
4949
4950 getXY : function() {
4951 return {
4952 x : parseInt(t.getStyle('left')),
4953 y : parseInt(t.getStyle('top'))
4954 };
4955 },
4956
4957 getSize : function() {
4958 var n = dom.get(t.id);
4959
4960 return {
4961 w : parseInt(t.getStyle('width') || n.clientWidth),
4962 h : parseInt(t.getStyle('height') || n.clientHeight)
4963 };
4964 },
4965
4966 moveTo : function(x, y) {
4967 t.setStyles({left : x, top : y});
4968 },
4969
4970 moveBy : function(x, y) {
4971 var p = t.getXY();
4972
4973 t.moveTo(p.x + x, p.y + y);
4974 },
4975
4976 resizeTo : function(w, h) {
4977 t.setStyles({width : w, height : h});
4978 },
4979
4980 resizeBy : function(w, h) {
4981 var s = t.getSize();
4982
4983 t.resizeTo(s.w + w, s.h + h);
4984 },
4985
4986 update : function(k) {
4987 var b;
4988
4989 if (tinymce.isIE6 && settings.blocker) {
4990 k = k || '';
4991
4992 // Ignore getters
4993 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
4994 return;
4995
4996 // Remove blocker on remove
4997 if (k == 'remove') {
4998 dom.remove(t.blocker);
4999 return;
5000 }
5001
5002 if (!t.blocker) {
5003 t.blocker = dom.uniqueId();
5004 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
5005 dom.setStyle(b, 'opacity', 0);
5006 } else
5007 b = dom.get(t.blocker);
5008
5009 dom.setStyles(b, {
5010 left : t.getStyle('left', 1),
5011 top : t.getStyle('top', 1),
5012 width : t.getStyle('width', 1),
5013 height : t.getStyle('height', 1),
5014 display : t.getStyle('display', 1),
5015 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
5016 });
5017 }
5018 }
5019 });
5020 };
5021 })(tinymce);
5022
5023 (function(tinymce) {
5024 function trimNl(s) {
5025 return s.replace(/[\n\r]+/g, '');
5026 };
5027
5028 // Shorten names
5029 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
5030
5031 tinymce.create('tinymce.dom.Selection', {
5032 Selection : function(dom, win, serializer) {
5033 var t = this;
5034
5035 t.dom = dom;
5036 t.win = win;
5037 t.serializer = serializer;
5038
5039 // Add events
5040 each([
5041 'onBeforeSetContent',
5042 'onBeforeGetContent',
5043 'onSetContent',
5044 'onGetContent'
5045 ], function(e) {
5046 t[e] = new tinymce.util.Dispatcher(t);
5047 });
5048
5049 // No W3C Range support
5050 if (!t.win.getSelection)
5051 t.tridentSel = new tinymce.dom.TridentSelection(t);
5052
5053 // Prevent leaks
5054 tinymce.addUnload(t.destroy, t);
5055 },
5056
5057 getContent : function(s) {
5058 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
5059
5060 s = s || {};
5061 wb = wa = '';
5062 s.get = true;
5063 s.format = s.format || 'html';
5064 t.onBeforeGetContent.dispatch(t, s);
5065
5066 if (s.format == 'text')
5067 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
5068
5069 if (r.cloneContents) {
5070 n = r.cloneContents();
5071
5072 if (n)
5073 e.appendChild(n);
5074 } else if (is(r.item) || is(r.htmlText))
5075 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
5076 else
5077 e.innerHTML = r.toString();
5078
5079 // Keep whitespace before and after
5080 if (/^\s/.test(e.innerHTML))
5081 wb = ' ';
5082
5083 if (/\s+$/.test(e.innerHTML))
5084 wa = ' ';
5085
5086 s.getInner = true;
5087
5088 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
5089 t.onGetContent.dispatch(t, s);
5090
5091 return s.content;
5092 },
5093
5094 setContent : function(h, s) {
5095 var t = this, r = t.getRng(), c, d = t.win.document;
5096
5097 s = s || {format : 'html'};
5098 s.set = true;
5099 h = s.content = t.dom.processHTML(h);
5100
5101 // Dispatch before set content event
5102 t.onBeforeSetContent.dispatch(t, s);
5103 h = s.content;
5104
5105 if (r.insertNode) {
5106 // Make caret marker since insertNode places the caret in the beginning of text after insert
5107 h += '<span id="__caret">_</span>';
5108
5109 // Delete and insert new node
5110
5111 if (r.startContainer == d && r.endContainer == d) {
5112 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
5113 d.body.innerHTML = h;
5114 } else {
5115 r.deleteContents();
5116 if (d.body.childNodes.length == 0) {
5117 d.body.innerHTML = h;
5118 } else {
5119 r.insertNode(r.createContextualFragment(h));
5120 }
5121 }
5122
5123 // Move to caret marker
5124 c = t.dom.get('__caret');
5125 // Make sure we wrap it compleatly, Opera fails with a simple select call
5126 r = d.createRange();
5127 r.setStartBefore(c);
5128 r.setEndBefore(c);
5129 t.setRng(r);
5130
5131 // Remove the caret position
5132 t.dom.remove('__caret');
5133 } else {
5134 if (r.item) {
5135 // Delete content and get caret text selection
5136 d.execCommand('Delete', false, null);
5137 r = t.getRng();
5138 }
5139
5140 r.pasteHTML(h);
5141 }
5142
5143 // Dispatch set content event
5144 t.onSetContent.dispatch(t, s);
5145 },
5146
5147 getStart : function() {
5148 var rng = this.getRng(), startElement, parentElement, checkRng, node;
5149
5150 if (rng.duplicate || rng.item) {
5151 // Control selection, return first item
5152 if (rng.item)
5153 return rng.item(0);
5154
5155 // Get start element
5156 checkRng = rng.duplicate();
5157 checkRng.collapse(1);
5158 startElement = checkRng.parentElement();
5159
5160 // Check if range parent is inside the start element, then return the inner parent element
5161 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
5162 parentElement = node = rng.parentElement();
5163 while (node = node.parentNode) {
5164 if (node == startElement) {
5165 startElement = parentElement;
5166 break;
5167 }
5168 }
5169
5170 // If start element is body element try to move to the first child if it exists
5171 if (startElement && startElement.nodeName == 'BODY')
5172 return startElement.firstChild || startElement;
5173
5174 return startElement;
5175 } else {
5176 startElement = rng.startContainer;
5177
5178 if (startElement.nodeType == 1 && startElement.hasChildNodes())
5179 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
5180
5181 if (startElement && startElement.nodeType == 3)
5182 return startElement.parentNode;
5183
5184 return startElement;
5185 }
5186 },
5187
5188 getEnd : function() {
5189 var t = this, r = t.getRng(), e, eo;
5190
5191 if (r.duplicate || r.item) {
5192 if (r.item)
5193 return r.item(0);
5194
5195 r = r.duplicate();
5196 r.collapse(0);
5197 e = r.parentElement();
5198
5199 if (e && e.nodeName == 'BODY')
5200 return e.lastChild || e;
5201
5202 return e;
5203 } else {
5204 e = r.endContainer;
5205 eo = r.endOffset;
5206
5207 if (e.nodeType == 1 && e.hasChildNodes())
5208 e = e.childNodes[eo > 0 ? eo - 1 : eo];
5209
5210 if (e && e.nodeType == 3)
5211 return e.parentNode;
5212
5213 return e;
5214 }
5215 },
5216
5217 getBookmark : function(type, normalized) {
5218 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
5219
5220 function findIndex(name, element) {
5221 var index = 0;
5222
5223 each(dom.select(name), function(node, i) {
5224 if (node == element)
5225 index = i;
5226 });
5227
5228 return index;
5229 };
5230
5231 if (type == 2) {
5232 function getLocation() {
5233 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
5234
5235 function getPoint(rng, start) {
5236 var container = rng[start ? 'startContainer' : 'endContainer'],
5237 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
5238
5239 if (container.nodeType == 3) {
5240 if (normalized) {
5241 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
5242 offset += node.nodeValue.length;
5243 }
5244
5245 point.push(offset);
5246 } else {
5247 childNodes = container.childNodes;
5248
5249 if (offset >= childNodes.length && childNodes.length) {
5250 after = 1;
5251 offset = Math.max(0, childNodes.length - 1);
5252 }
5253
5254 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
5255 }
5256
5257 for (; container && container != root; container = container.parentNode)
5258 point.push(t.dom.nodeIndex(container, normalized));
5259
5260 return point;
5261 };
5262
5263 bookmark.start = getPoint(rng, true);
5264
5265 if (!t.isCollapsed())
5266 bookmark.end = getPoint(rng);
5267
5268 return bookmark;
5269 };
5270
5271 return getLocation();
5272 }
5273
5274 // Handle simple range
5275 if (type)
5276 return {rng : t.getRng()};
5277
5278 rng = t.getRng();
5279 id = dom.uniqueId();
5280 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
5281 styles = 'overflow:hidden;line-height:0px';
5282
5283 // Explorer method
5284 if (rng.duplicate || rng.item) {
5285 // Text selection
5286 if (!rng.item) {
5287 rng2 = rng.duplicate();
5288
5289 // Insert start marker
5290 rng.collapse();
5291 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
5292
5293 // Insert end marker
5294 if (!collapsed) {
5295 rng2.collapse(false);
5296 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
5297 }
5298 } else {
5299 // Control selection
5300 element = rng.item(0);
5301 name = element.nodeName;
5302
5303 return {name : name, index : findIndex(name, element)};
5304 }
5305 } else {
5306 element = t.getNode();
5307 name = element.nodeName;
5308 if (name == 'IMG')
5309 return {name : name, index : findIndex(name, element)};
5310
5311 // W3C method
5312 rng2 = rng.cloneRange();
5313
5314 // Insert end marker
5315 if (!collapsed) {
5316 rng2.collapse(false);
5317 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
5318 }
5319
5320 rng.collapse(true);
5321 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
5322 }
5323
5324 t.moveToBookmark({id : id, keep : 1});
5325
5326 return {id : id};
5327 },
5328
5329 moveToBookmark : function(bookmark) {
5330 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
5331
5332 // Clear selection cache
5333 if (t.tridentSel)
5334 t.tridentSel.destroy();
5335
5336 if (bookmark) {
5337 if (bookmark.start) {
5338 rng = dom.createRng();
5339 root = dom.getRoot();
5340
5341 function setEndPoint(start) {
5342 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
5343
5344 if (point) {
5345 // Find container node
5346 for (node = root, i = point.length - 1; i >= 1; i--) {
5347 children = node.childNodes;
5348
5349 if (children.length)
5350 node = children[point[i]];
5351 }
5352
5353 // Set offset within container node
5354 if (start)
5355 rng.setStart(node, point[0]);
5356 else
5357 rng.setEnd(node, point[0]);
5358 }
5359 };
5360
5361 setEndPoint(true);
5362 setEndPoint();
5363
5364 t.setRng(rng);
5365 } else if (bookmark.id) {
5366 function restoreEndPoint(suffix) {
5367 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
5368
5369 if (marker) {
5370 node = marker.parentNode;
5371
5372 if (suffix == 'start') {
5373 if (!keep) {
5374 idx = dom.nodeIndex(marker);
5375 } else {
5376 node = marker.firstChild;
5377 idx = 1;
5378 }
5379
5380 startContainer = endContainer = node;
5381 startOffset = endOffset = idx;
5382 } else {
5383 if (!keep) {
5384 idx = dom.nodeIndex(marker);
5385 } else {
5386 node = marker.firstChild;
5387 idx = 1;
5388 }
5389
5390 endContainer = node;
5391 endOffset = idx;
5392 }
5393
5394 if (!keep) {
5395 prev = marker.previousSibling;
5396 next = marker.nextSibling;
5397
5398 // Remove all marker text nodes
5399 each(tinymce.grep(marker.childNodes), function(node) {
5400 if (node.nodeType == 3)
5401 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
5402 });
5403
5404 // Remove marker but keep children if for example contents where inserted into the marker
5405 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
5406 while (marker = dom.get(bookmark.id + '_' + suffix))
5407 dom.remove(marker, 1);
5408
5409 // If siblings are text nodes then merge them
5410 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
5411 idx = prev.nodeValue.length;
5412 prev.appendData(next.nodeValue);
5413 dom.remove(next);
5414
5415 if (suffix == 'start') {
5416 startContainer = endContainer = prev;
5417 startOffset = endOffset = idx;
5418 } else {
5419 endContainer = prev;
5420 endOffset = idx;
5421 }
5422 }
5423 }
5424 }
5425 };
5426
5427 function addBogus(node) {
5428 // Adds a bogus BR element for empty block elements
5429 // on non IE browsers just to have a place to put the caret
5430 if (!isIE && dom.isBlock(node) && !node.innerHTML)
5431 node.innerHTML = '<br _mce_bogus="1" />';
5432
5433 return node;
5434 };
5435
5436 // Restore start/end points
5437 restoreEndPoint('start');
5438 restoreEndPoint('end');
5439
5440 if (startContainer) {
5441 rng = dom.createRng();
5442 rng.setStart(addBogus(startContainer), startOffset);
5443 rng.setEnd(addBogus(endContainer), endOffset);
5444 t.setRng(rng);
5445 }
5446 } else if (bookmark.name) {
5447 t.select(dom.select(bookmark.name)[bookmark.index]);
5448 } else if (bookmark.rng)
5449 t.setRng(bookmark.rng);
5450 }
5451 },
5452
5453 select : function(node, content) {
5454 var t = this, dom = t.dom, rng = dom.createRng(), idx;
5455
5456 idx = dom.nodeIndex(node);
5457 rng.setStart(node.parentNode, idx);
5458 rng.setEnd(node.parentNode, idx + 1);
5459
5460 // Find first/last text node or BR element
5461 if (content) {
5462 function setPoint(node, start) {
5463 var walker = new tinymce.dom.TreeWalker(node, node);
5464
5465 do {
5466 // Text node
5467 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
5468 if (start)
5469 rng.setStart(node, 0);
5470 else
5471 rng.setEnd(node, node.nodeValue.length);
5472
5473 return;
5474 }
5475
5476 // BR element
5477 if (node.nodeName == 'BR') {
5478 if (start)
5479 rng.setStartBefore(node);
5480 else
5481 rng.setEndBefore(node);
5482
5483 return;
5484 }
5485 } while (node = (start ? walker.next() : walker.prev()));
5486 };
5487
5488 setPoint(node, 1);
5489 setPoint(node);
5490 }
5491
5492 t.setRng(rng);
5493
5494 return node;
5495 },
5496
5497 isCollapsed : function() {
5498 var t = this, r = t.getRng(), s = t.getSel();
5499
5500 if (!r || r.item)
5501 return false;
5502
5503 if (r.compareEndPoints)
5504 return r.compareEndPoints('StartToEnd', r) === 0;
5505
5506 return !s || r.collapsed;
5507 },
5508
5509 collapse : function(b) {
5510 var t = this, r = t.getRng(), n;
5511
5512 // Control range on IE
5513 if (r.item) {
5514 n = r.item(0);
5515 r = this.win.document.body.createTextRange();
5516 r.moveToElementText(n);
5517 }
5518
5519 r.collapse(!!b);
5520 t.setRng(r);
5521 },
5522
5523 getSel : function() {
5524 var t = this, w = this.win;
5525
5526 return w.getSelection ? w.getSelection() : w.document.selection;
5527 },
5528
5529 getRng : function(w3c) {
5530 var t = this, s, r;
5531
5532 // Found tridentSel object then we need to use that one
5533 if (w3c && t.tridentSel)
5534 return t.tridentSel.getRangeAt(0);
5535
5536 try {
5537 if (s = t.getSel())
5538 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
5539 } catch (ex) {
5540 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
5541 }
5542
5543 // No range found then create an empty one
5544 // This can occur when the editor is placed in a hidden container element on Gecko
5545 // Or on IE when there was an exception
5546 if (!r)
5547 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
5548
5549 if (t.selectedRange && t.explicitRange) {
5550 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
5551 // Safari, Opera and Chrome only ever select text which causes the range to change.
5552 // This lets us use the originally set range if the selection hasn't been changed by the user.
5553 r = t.explicitRange;
5554 } else {
5555 t.selectedRange = null;
5556 t.explicitRange = null;
5557 }
5558 }
5559 return r;
5560 },
5561
5562 setRng : function(r) {
5563 var s, t = this;
5564
5565 if (!t.tridentSel) {
5566 s = t.getSel();
5567
5568 if (s) {
5569 t.explicitRange = r;
5570 s.removeAllRanges();
5571 s.addRange(r);
5572 t.selectedRange = s.getRangeAt(0);
5573 }
5574 } else {
5575 // Is W3C Range
5576 if (r.cloneRange) {
5577 t.tridentSel.addRange(r);
5578 return;
5579 }
5580
5581 // Is IE specific range
5582 try {
5583 r.select();
5584 } catch (ex) {
5585 // Needed for some odd IE bug #1843306
5586 }
5587 }
5588 },
5589
5590 setNode : function(n) {
5591 var t = this;
5592
5593 t.setContent(t.dom.getOuterHTML(n));
5594
5595 return n;
5596 },
5597
5598 getNode : function() {
5599 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
5600
5601 if (rng.setStart) {
5602 // Range maybe lost after the editor is made visible again
5603 if (!rng)
5604 return t.dom.getRoot();
5605
5606 elm = rng.commonAncestorContainer;
5607
5608 // Handle selection a image or other control like element such as anchors
5609 if (!rng.collapsed) {
5610 if (rng.startContainer == rng.endContainer) {
5611 if (rng.startOffset - rng.endOffset < 2) {
5612 if (rng.startContainer.hasChildNodes())
5613 elm = rng.startContainer.childNodes[rng.startOffset];
5614 }
5615 }
5616
5617 // If the anchor node is a element instead of a text node then return this element
5618 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
5619 return sel.anchorNode.childNodes[sel.anchorOffset];
5620 }
5621
5622 if (elm && elm.nodeType == 3)
5623 return elm.parentNode;
5624
5625 return elm;
5626 }
5627
5628 return rng.item ? rng.item(0) : rng.parentElement();
5629 },
5630
5631 getSelectedBlocks : function(st, en) {
5632 var t = this, dom = t.dom, sb, eb, n, bl = [];
5633
5634 sb = dom.getParent(st || t.getStart(), dom.isBlock);
5635 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
5636
5637 if (sb)
5638 bl.push(sb);
5639
5640 if (sb && eb && sb != eb) {
5641 n = sb;
5642
5643 while ((n = n.nextSibling) && n != eb) {
5644 if (dom.isBlock(n))
5645 bl.push(n);
5646 }
5647 }
5648
5649 if (eb && sb != eb)
5650 bl.push(eb);
5651
5652 return bl;
5653 },
5654
5655 destroy : function(s) {
5656 var t = this;
5657
5658 t.win = null;
5659
5660 if (t.tridentSel)
5661 t.tridentSel.destroy();
5662
5663 // Manual destroy then remove unload handler
5664 if (!s)
5665 tinymce.removeUnload(t.destroy);
5666 }
5667 });
5668 })(tinymce);
5669
5670 (function(tinymce) {
5671 tinymce.create('tinymce.dom.XMLWriter', {
5672 node : null,
5673
5674 XMLWriter : function(s) {
5675 // Get XML document
5676 function getXML() {
5677 var i = document.implementation;
5678
5679 if (!i || !i.createDocument) {
5680 // Try IE objects
5681 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
5682 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
5683 } else
5684 return i.createDocument('', '', null);
5685 };
5686
5687 this.doc = getXML();
5688
5689 // Since Opera and WebKit doesn't escape > into &gt; we need to do it our self to normalize the output for all browsers
5690 this.valid = tinymce.isOpera || tinymce.isWebKit;
5691
5692 this.reset();
5693 },
5694
5695 reset : function() {
5696 var t = this, d = t.doc;
5697
5698 if (d.firstChild)
5699 d.removeChild(d.firstChild);
5700
5701 t.node = d.appendChild(d.createElement("html"));
5702 },
5703
5704 writeStartElement : function(n) {
5705 var t = this;
5706
5707 t.node = t.node.appendChild(t.doc.createElement(n));
5708 },
5709
5710 writeAttribute : function(n, v) {
5711 if (this.valid)
5712 v = v.replace(/>/g, '%MCGT%');
5713
5714 this.node.setAttribute(n, v);
5715 },
5716
5717 writeEndElement : function() {
5718 this.node = this.node.parentNode;
5719 },
5720
5721 writeFullEndElement : function() {
5722 var t = this, n = t.node;
5723
5724 n.appendChild(t.doc.createTextNode(""));
5725 t.node = n.parentNode;
5726 },
5727
5728 writeText : function(v) {
5729 if (this.valid)
5730 v = v.replace(/>/g, '%MCGT%');
5731
5732 this.node.appendChild(this.doc.createTextNode(v));
5733 },
5734
5735 writeCDATA : function(v) {
5736 this.node.appendChild(this.doc.createCDATASection(v));
5737 },
5738
5739 writeComment : function(v) {
5740 // Fix for bug #2035694
5741 if (tinymce.isIE)
5742 v = v.replace(/^\-|\-$/g, ' ');
5743
5744 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
5745 },
5746
5747 getContent : function() {
5748 var h;
5749
5750 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
5751 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
5752 h = h.replace(/ ?\/>/g, ' />');
5753
5754 if (this.valid)
5755 h = h.replace(/\%MCGT%/g, '&gt;');
5756
5757 return h;
5758 }
5759 });
5760 })(tinymce);
5761
5762 (function(tinymce) {
5763 tinymce.create('tinymce.dom.StringWriter', {
5764 str : null,
5765 tags : null,
5766 count : 0,
5767 settings : null,
5768 indent : null,
5769
5770 StringWriter : function(s) {
5771 this.settings = tinymce.extend({
5772 indent_char : ' ',
5773 indentation : 0
5774 }, s);
5775
5776 this.reset();
5777 },
5778
5779 reset : function() {
5780 this.indent = '';
5781 this.str = "";
5782 this.tags = [];
5783 this.count = 0;
5784 },
5785
5786 writeStartElement : function(n) {
5787 this._writeAttributesEnd();
5788 this.writeRaw('<' + n);
5789 this.tags.push(n);
5790 this.inAttr = true;
5791 this.count++;
5792 this.elementCount = this.count;
5793 },
5794
5795 writeAttribute : function(n, v) {
5796 var t = this;
5797
5798 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
5799 },
5800
5801 writeEndElement : function() {
5802 var n;
5803
5804 if (this.tags.length > 0) {
5805 n = this.tags.pop();
5806
5807 if (this._writeAttributesEnd(1))
5808 this.writeRaw('</' + n + '>');
5809
5810 if (this.settings.indentation > 0)
5811 this.writeRaw('\n');
5812 }
5813 },
5814
5815 writeFullEndElement : function() {
5816 if (this.tags.length > 0) {
5817 this._writeAttributesEnd();
5818 this.writeRaw('</' + this.tags.pop() + '>');
5819
5820 if (this.settings.indentation > 0)
5821 this.writeRaw('\n');
5822 }
5823 },
5824
5825 writeText : function(v) {
5826 this._writeAttributesEnd();
5827 this.writeRaw(this.encode(v));
5828 this.count++;
5829 },
5830
5831 writeCDATA : function(v) {
5832 this._writeAttributesEnd();
5833 this.writeRaw('<![CDATA[' + v + ']]>');
5834 this.count++;
5835 },
5836
5837 writeComment : function(v) {
5838 this._writeAttributesEnd();
5839 this.writeRaw('<!-- ' + v + '-->');
5840 this.count++;
5841 },
5842
5843 writeRaw : function(v) {
5844 this.str += v;
5845 },
5846
5847 encode : function(s) {
5848 return s.replace(/[<>&"]/g, function(v) {
5849 switch (v) {
5850 case '<':
5851 return '&lt;';
5852
5853 case '>':
5854 return '&gt;';
5855
5856 case '&':
5857 return '&amp;';
5858
5859 case '"':
5860 return '&quot;';
5861 }
5862
5863 return v;
5864 });
5865 },
5866
5867 getContent : function() {
5868 return this.str;
5869 },
5870
5871 _writeAttributesEnd : function(s) {
5872 if (!this.inAttr)
5873 return;
5874
5875 this.inAttr = false;
5876
5877 if (s && this.elementCount == this.count) {
5878 this.writeRaw(' />');
5879 return false;
5880 }
5881
5882 this.writeRaw('>');
5883
5884 return true;
5885 }
5886 });
5887 })(tinymce);
5888
5889 (function(tinymce) {
5890 // Shorten names
5891 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
5892
5893 function wildcardToRE(s) {
5894 return s.replace(/([?+*])/g, '.$1');
5895 };
5896
5897 tinymce.create('tinymce.dom.Serializer', {
5898 Serializer : function(s) {
5899 var t = this;
5900
5901 t.key = 0;
5902 t.onPreProcess = new Dispatcher(t);
5903 t.onPostProcess = new Dispatcher(t);
5904
5905 try {
5906 t.writer = new tinymce.dom.XMLWriter();
5907 } catch (ex) {
5908 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
5909 t.writer = new tinymce.dom.StringWriter();
5910 }
5911
5912 // Default settings
5913 t.settings = s = extend({
5914 dom : tinymce.DOM,
5915 valid_nodes : 0,
5916 node_filter : 0,
5917 attr_filter : 0,
5918 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
5919 closed : /^(br|hr|input|meta|img|link|param|area)$/,
5920 entity_encoding : 'named',
5921 entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro',
5922 valid_elements : '*[*]',
5923 extended_valid_elements : 0,
5924 invalid_elements : 0,
5925 fix_table_elements : 1,
5926 fix_list_elements : true,
5927 fix_content_duplication : true,
5928 convert_fonts_to_spans : false,
5929 font_size_classes : 0,
5930 apply_source_formatting : 0,
5931 indent_mode : 'simple',
5932 indent_char : '\t',
5933 indent_levels : 1,
5934 remove_linebreaks : 1,
5935 remove_redundant_brs : 1,
5936 element_format : 'xhtml'
5937 }, s);
5938
5939 t.dom = s.dom;
5940 t.schema = s.schema;
5941
5942 // Use raw entities if no entities are defined
5943 if (s.entity_encoding == 'named' && !s.entities)
5944 s.entity_encoding = 'raw';
5945
5946 if (s.remove_redundant_brs) {
5947 t.onPostProcess.add(function(se, o) {
5948 // Remove single BR at end of block elements since they get rendered
5949 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
5950 // Check if it's a single element
5951 if (/^<br \/>\s*<\//.test(a))
5952 return '</' + c + '>';
5953
5954 return a;
5955 });
5956 });
5957 }
5958
5959 // Remove XHTML element endings i.e. produce crap :) XHTML is better
5960 if (s.element_format == 'html') {
5961 t.onPostProcess.add(function(se, o) {
5962 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
5963 });
5964 }
5965
5966 if (s.fix_list_elements) {
5967 t.onPreProcess.add(function(se, o) {
5968 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
5969
5970 function prevNode(e, n) {
5971 var a = n.split(','), i;
5972
5973 while ((e = e.previousSibling) != null) {
5974 for (i=0; i<a.length; i++) {
5975 if (e.nodeName == a[i])
5976 return e;
5977 }
5978 }
5979
5980 return null;
5981 };
5982
5983 for (x=0; x<a.length; x++) {
5984 nl = t.dom.select(a[x], o.node);
5985
5986 for (i=0; i<nl.length; i++) {
5987 n = nl[i];
5988 p = n.parentNode;
5989
5990 if (r.test(p.nodeName)) {
5991 np = prevNode(n, 'LI');
5992
5993 if (!np) {
5994 np = t.dom.create('li');
5995 np.innerHTML = '&nbsp;';
5996 np.appendChild(n);
5997 p.insertBefore(np, p.firstChild);
5998 } else
5999 np.appendChild(n);
6000 }
6001 }
6002 }
6003 });
6004 }
6005
6006 if (s.fix_table_elements) {
6007 t.onPreProcess.add(function(se, o) {
6008 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
6009 // so Opera users with an older version will have to live with less compaible output not much we can do here
6010 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
6011 each(t.dom.select('p table', o.node).reverse(), function(n) {
6012 var parent = t.dom.getParent(n.parentNode, 'table,p');
6013
6014 if (parent.nodeName != 'TABLE') {
6015 try {
6016 t.dom.split(parent, n);
6017 } catch (ex) {
6018 // IE can sometimes fire an unknown runtime error so we just ignore it
6019 }
6020 }
6021 });
6022 }
6023 });
6024 }
6025 },
6026
6027 setEntities : function(s) {
6028 var t = this, a, i, l = {}, v;
6029
6030 // No need to setup more than once
6031 if (t.entityLookup)
6032 return;
6033
6034 // Build regex and lookup array
6035 a = s.split(',');
6036 for (i = 0; i < a.length; i += 2) {
6037 v = a[i];
6038
6039 // Don't add default &amp; &quot; etc.
6040 if (v == 34 || v == 38 || v == 60 || v == 62)
6041 continue;
6042
6043 l[String.fromCharCode(a[i])] = a[i + 1];
6044
6045 v = parseInt(a[i]).toString(16);
6046 }
6047
6048 t.entityLookup = l;
6049 },
6050
6051 setRules : function(s) {
6052 var t = this;
6053
6054 t._setup();
6055 t.rules = {};
6056 t.wildRules = [];
6057 t.validElements = {};
6058
6059 return t.addRules(s);
6060 },
6061
6062 addRules : function(s) {
6063 var t = this, dr;
6064
6065 if (!s)
6066 return;
6067
6068 t._setup();
6069
6070 each(s.split(','), function(s) {
6071 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
6072
6073 // Extend with default rules
6074 if (dr)
6075 at = tinymce.extend([], dr.attribs);
6076
6077 // Parse attributes
6078 if (p.length > 1) {
6079 each(p[1].split('|'), function(s) {
6080 var ar = {}, i;
6081
6082 at = at || [];
6083
6084 // Parse attribute rule
6085 s = s.replace(/::/g, '~');
6086 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
6087 s[2] = s[2].replace(/~/g, ':');
6088
6089 // Add required attributes
6090 if (s[1] == '!') {
6091 ra = ra || [];
6092 ra.push(s[2]);
6093 }
6094
6095 // Remove inherited attributes
6096 if (s[1] == '-') {
6097 for (i = 0; i <at.length; i++) {
6098 if (at[i].name == s[2]) {
6099 at.splice(i, 1);
6100 return;
6101 }
6102 }
6103 }
6104
6105 switch (s[3]) {
6106 // Add default attrib values
6107 case '=':
6108 ar.defaultVal = s[4] || '';
6109 break;
6110
6111 // Add forced attrib values
6112 case ':':
6113 ar.forcedVal = s[4];
6114 break;
6115
6116 // Add validation values
6117 case '<':
6118 ar.validVals = s[4].split('?');
6119 break;
6120 }
6121
6122 if (/[*.?]/.test(s[2])) {
6123 wat = wat || [];
6124 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
6125 wat.push(ar);
6126 } else {
6127 ar.name = s[2];
6128 at.push(ar);
6129 }
6130
6131 va.push(s[2]);
6132 });
6133 }
6134
6135 // Handle element names
6136 each(tn, function(s, i) {
6137 var pr = s.charAt(0), x = 1, ru = {};
6138
6139 // Extend with default rule data
6140 if (dr) {
6141 if (dr.noEmpty)
6142 ru.noEmpty = dr.noEmpty;
6143
6144 if (dr.fullEnd)
6145 ru.fullEnd = dr.fullEnd;
6146
6147 if (dr.padd)
6148 ru.padd = dr.padd;
6149 }
6150
6151 // Handle prefixes
6152 switch (pr) {
6153 case '-':
6154 ru.noEmpty = true;
6155 break;
6156
6157 case '+':
6158 ru.fullEnd = true;
6159 break;
6160
6161 case '#':
6162 ru.padd = true;
6163 break;
6164
6165 default:
6166 x = 0;
6167 }
6168
6169 tn[i] = s = s.substring(x);
6170 t.validElements[s] = 1;
6171
6172 // Add element name or element regex
6173 if (/[*.?]/.test(tn[0])) {
6174 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
6175 t.wildRules = t.wildRules || {};
6176 t.wildRules.push(ru);
6177 } else {
6178 ru.name = tn[0];
6179
6180 // Store away default rule
6181 if (tn[0] == '@')
6182 dr = ru;
6183
6184 t.rules[s] = ru;
6185 }
6186
6187 ru.attribs = at;
6188
6189 if (ra)
6190 ru.requiredAttribs = ra;
6191
6192 if (wat) {
6193 // Build valid attributes regexp
6194 s = '';
6195 each(va, function(v) {
6196 if (s)
6197 s += '|';
6198
6199 s += '(' + wildcardToRE(v) + ')';
6200 });
6201 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
6202 ru.wildAttribs = wat;
6203 }
6204 });
6205 });
6206
6207 // Build valid elements regexp
6208 s = '';
6209 each(t.validElements, function(v, k) {
6210 if (s)
6211 s += '|';
6212
6213 if (k != '@')
6214 s += k;
6215 });
6216 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
6217
6218 //console.debug(t.validElementsRE.toString());
6219 //console.dir(t.rules);
6220 //console.dir(t.wildRules);
6221 },
6222
6223 findRule : function(n) {
6224 var t = this, rl = t.rules, i, r;
6225
6226 t._setup();
6227
6228 // Exact match
6229 r = rl[n];
6230 if (r)
6231 return r;
6232
6233 // Try wildcards
6234 rl = t.wildRules;
6235 for (i = 0; i < rl.length; i++) {
6236 if (rl[i].nameRE.test(n))
6237 return rl[i];
6238 }
6239
6240 return null;
6241 },
6242
6243 findAttribRule : function(ru, n) {
6244 var i, wa = ru.wildAttribs;
6245
6246 for (i = 0; i < wa.length; i++) {
6247 if (wa[i].nameRE.test(n))
6248 return wa[i];
6249 }
6250
6251 return null;
6252 },
6253
6254 serialize : function(n, o) {
6255 var h, t = this, doc, oldDoc, impl, selected;
6256
6257 t._setup();
6258 o = o || {};
6259 o.format = o.format || 'html';
6260 t.processObj = o;
6261
6262 // IE looses the selected attribute on option elements so we need to store it
6263 // See: http://support.microsoft.com/kb/829907
6264 if (isIE) {
6265 selected = [];
6266 each(n.getElementsByTagName('option'), function(n) {
6267 var v = t.dom.getAttrib(n, 'selected');
6268
6269 selected.push(v ? v : null);
6270 });
6271 }
6272
6273 n = n.cloneNode(true);
6274
6275 // IE looses the selected attribute on option elements so we need to restore it
6276 if (isIE) {
6277 each(n.getElementsByTagName('option'), function(n, i) {
6278 t.dom.setAttrib(n, 'selected', selected[i]);
6279 });
6280 }
6281
6282 // Nodes needs to be attached to something in WebKit/Opera
6283 // Older builds of Opera crashes if you attach the node to an document created dynamically
6284 // and since we can't feature detect a crash we need to sniff the acutal build number
6285 // This fix will make DOM ranges and make Sizzle happy!
6286 impl = n.ownerDocument.implementation;
6287 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
6288 // Create an empty HTML document
6289 doc = impl.createHTMLDocument("");
6290
6291 // Add the element or it's children if it's a body element to the new document
6292 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
6293 doc.body.appendChild(doc.importNode(node, true));
6294 });
6295
6296 // Grab first child or body element for serialization
6297 if (n.nodeName != 'BODY')
6298 n = doc.body.firstChild;
6299 else
6300 n = doc.body;
6301
6302 // set the new document in DOMUtils so createElement etc works
6303 oldDoc = t.dom.doc;
6304 t.dom.doc = doc;
6305 }
6306
6307 t.key = '' + (parseInt(t.key) + 1);
6308
6309 // Pre process
6310 if (!o.no_events) {
6311 o.node = n;
6312 t.onPreProcess.dispatch(t, o);
6313 }
6314
6315 // Serialize HTML DOM into a string
6316 t.writer.reset();
6317 t._info = o;
6318 t._serializeNode(n, o.getInner);
6319
6320 // Post process
6321 o.content = t.writer.getContent();
6322
6323 // Restore the old document if it was changed
6324 if (oldDoc)
6325 t.dom.doc = oldDoc;
6326
6327 if (!o.no_events)
6328 t.onPostProcess.dispatch(t, o);
6329
6330 t._postProcess(o);
6331 o.node = null;
6332
6333 return tinymce.trim(o.content);
6334 },
6335
6336 // Internal functions
6337
6338 _postProcess : function(o) {
6339 var t = this, s = t.settings, h = o.content, sc = [], p;
6340
6341 if (o.format == 'html') {
6342 // Protect some elements
6343 p = t._protect({
6344 content : h,
6345 patterns : [
6346 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
6347 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
6348 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
6349 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
6350 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
6351 ]
6352 });
6353
6354 h = p.content;
6355
6356 // Entity encode
6357 if (s.entity_encoding !== 'raw')
6358 h = t._encode(h);
6359
6360 // Use BR instead of &nbsp; padded P elements inside editor and use <p>&nbsp;</p> outside editor
6361 /* if (o.set)
6362 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
6363 else
6364 h = h.replace(/<p>\s+(&nbsp;|&#160;|\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
6365
6366 // Since Gecko and Safari keeps whitespace in the DOM we need to
6367 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
6368 // This process is only done when getting contents out from the editor.
6369 if (!o.set) {
6370 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
6371 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1>&#160;</p>' : '<p$1>&nbsp;</p>');
6372
6373 if (s.remove_linebreaks) {
6374 h = h.replace(/\r?\n|\r/g, ' ');
6375 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
6376 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
6377 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start
6378 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start
6379 h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>'); // Trim block end
6380 }
6381
6382 // Simple indentation
6383 if (s.apply_source_formatting && s.indent_mode == 'simple') {
6384 // Add line breaks before and after block elements
6385 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
6386 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
6387 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
6388 h = h.replace(/\n\n/g, '\n');
6389 }
6390 }
6391
6392 h = t._unprotect(h, p);
6393
6394 // Restore CDATA sections
6395 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
6396
6397 // Restore the \u00a0 character if raw mode is enabled
6398 if (s.entity_encoding == 'raw')
6399 h = h.replace(/<p>&nbsp;<\/p>|<p([^>]+)>&nbsp;<\/p>/g, '<p$1>\u00a0</p>');
6400
6401 // Restore noscript elements
6402 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
6403 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
6404 });
6405 }
6406
6407 o.content = h;
6408 },
6409
6410 _serializeNode : function(n, inner) {
6411 var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type, scopeName;
6412
6413 if (!s.node_filter || s.node_filter(n)) {
6414 switch (n.nodeType) {
6415 case 1: // Element
6416 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
6417 return;
6418
6419 iv = keep = false;
6420 hc = n.hasChildNodes();
6421 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
6422
6423 // Get internal type
6424 type = n.getAttribute('_mce_type');
6425 if (type) {
6426 if (!t._info.cleanup) {
6427 iv = true;
6428 return;
6429 } else
6430 keep = 1;
6431 }
6432
6433 // Add correct prefix on IE
6434 if (isIE) {
6435 scopeName = n.scopeName;
6436 if (scopeName && scopeName !== 'HTML' && scopeName !== 'html')
6437 nn = scopeName + ':' + nn;
6438 }
6439
6440 // Remove mce prefix on IE needed for the abbr element
6441 if (nn.indexOf('mce:') === 0)
6442 nn = nn.substring(4);
6443
6444 // Check if valid
6445 if (!keep) {
6446 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
6447 iv = true;
6448 break;
6449 }
6450 }
6451
6452 if (isIE) {
6453 // Fix IE content duplication (DOM can have multiple copies of the same node)
6454 if (s.fix_content_duplication) {
6455 if (n._mce_serialized == t.key)
6456 return;
6457
6458 n._mce_serialized = t.key;
6459 }
6460
6461 // IE sometimes adds a / infront of the node name
6462 if (nn.charAt(0) == '/')
6463 nn = nn.substring(1);
6464 } else if (isGecko) {
6465 // Ignore br elements
6466 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
6467 return;
6468 }
6469
6470 // Check if valid child
6471 if (s.validate_children) {
6472 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
6473 iv = true;
6474 break;
6475 }
6476
6477 t.elementName = nn;
6478 }
6479
6480 ru = t.findRule(nn);
6481
6482 // No valid rule for this element could be found then skip it
6483 if (!ru) {
6484 iv = true;
6485 break;
6486 }
6487
6488 nn = ru.name || nn;
6489 closed = s.closed.test(nn);
6490
6491 // Skip empty nodes or empty node name in IE
6492 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
6493 iv = true;
6494 break;
6495 }
6496
6497 // Check required
6498 if (ru.requiredAttribs) {
6499 a = ru.requiredAttribs;
6500
6501 for (i = a.length - 1; i >= 0; i--) {
6502 if (this.dom.getAttrib(n, a[i]) !== '')
6503 break;
6504 }
6505
6506 // None of the required was there
6507 if (i == -1) {
6508 iv = true;
6509 break;
6510 }
6511 }
6512
6513 w.writeStartElement(nn);
6514
6515 // Add ordered attributes
6516 if (ru.attribs) {
6517 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
6518 a = at[i];
6519 v = t._getAttrib(n, a);
6520
6521 if (v !== null)
6522 w.writeAttribute(a.name, v);
6523 }
6524 }
6525
6526 // Add wild attributes
6527 if (ru.validAttribsRE) {
6528 at = t.dom.getAttribs(n);
6529 for (i=at.length-1; i>-1; i--) {
6530 no = at[i];
6531
6532 if (no.specified) {
6533 a = no.nodeName.toLowerCase();
6534
6535 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
6536 continue;
6537
6538 ar = t.findAttribRule(ru, a);
6539 v = t._getAttrib(n, ar, a);
6540
6541 if (v !== null)
6542 w.writeAttribute(a, v);
6543 }
6544 }
6545 }
6546
6547 // Keep type attribute
6548 if (type && keep)
6549 w.writeAttribute('_mce_type', type);
6550
6551 // Write text from script
6552 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
6553 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
6554 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
6555 hc = false;
6556 break;
6557 }
6558
6559 // Padd empty nodes with a &nbsp;
6560 if (ru.padd) {
6561 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
6562 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
6563 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
6564 w.writeText('\u00a0');
6565 } else if (!hc)
6566 w.writeText('\u00a0'); // No children then padd it
6567 }
6568
6569 break;
6570
6571 case 3: // Text
6572 // Check if valid child
6573 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
6574 return;
6575
6576 return w.writeText(n.nodeValue);
6577
6578 case 4: // CDATA
6579 return w.writeCDATA(n.nodeValue);
6580
6581 case 8: // Comment
6582 return w.writeComment(n.nodeValue);
6583 }
6584 } else if (n.nodeType == 1)
6585 hc = n.hasChildNodes();
6586
6587 if (hc && !closed) {
6588 cn = n.firstChild;
6589
6590 while (cn) {
6591 t._serializeNode(cn);
6592 t.elementName = nn;
6593 cn = cn.nextSibling;
6594 }
6595 }
6596
6597 // Write element end
6598 if (!iv) {
6599 if (!closed)
6600 w.writeFullEndElement();
6601 else
6602 w.writeEndElement();
6603 }
6604 },
6605
6606 _protect : function(o) {
6607 var t = this;
6608
6609 o.items = o.items || [];
6610
6611 function enc(s) {
6612 return s.replace(/[\r\n\\]/g, function(c) {
6613 if (c === '\n')
6614 return '\\n';
6615 else if (c === '\\')
6616 return '\\\\';
6617
6618 return '\\r';
6619 });
6620 };
6621
6622 function dec(s) {
6623 return s.replace(/\\[\\rn]/g, function(c) {
6624 if (c === '\\n')
6625 return '\n';
6626 else if (c === '\\\\')
6627 return '\\';
6628
6629 return '\r';
6630 });
6631 };
6632
6633 each(o.patterns, function(p) {
6634 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
6635 b = dec(b);
6636
6637 if (p.encode)
6638 b = t._encode(b);
6639
6640 o.items.push(b);
6641 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
6642 }));
6643 });
6644
6645 return o;
6646 },
6647
6648 _unprotect : function(h, o) {
6649 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
6650 return o.items[parseInt(b)];
6651 });
6652
6653 o.items = [];
6654
6655 return h;
6656 },
6657
6658 _encode : function(h) {
6659 var t = this, s = t.settings, l;
6660
6661 // Entity encode
6662 if (s.entity_encoding !== 'raw') {
6663 if (s.entity_encoding.indexOf('named') != -1) {
6664 t.setEntities(s.entities);
6665 l = t.entityLookup;
6666
6667 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
6668 var v;
6669
6670 if (v = l[a])
6671 a = '&' + v + ';';
6672
6673 return a;
6674 });
6675 }
6676
6677 if (s.entity_encoding.indexOf('numeric') != -1) {
6678 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
6679 return '&#' + a.charCodeAt(0) + ';';
6680 });
6681 }
6682 }
6683
6684 return h;
6685 },
6686
6687 _setup : function() {
6688 var t = this, s = this.settings;
6689
6690 if (t.done)
6691 return;
6692
6693 t.done = 1;
6694
6695 t.setRules(s.valid_elements);
6696 t.addRules(s.extended_valid_elements);
6697
6698 if (s.invalid_elements)
6699 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
6700
6701 if (s.attrib_value_filter)
6702 t.attribValueFilter = s.attribValueFilter;
6703 },
6704
6705 _getAttrib : function(n, a, na) {
6706 var i, v;
6707
6708 na = na || a.name;
6709
6710 if (a.forcedVal && (v = a.forcedVal)) {
6711 if (v === '{$uid}')
6712 return this.dom.uniqueId();
6713
6714 return v;
6715 }
6716
6717 v = this.dom.getAttrib(n, na);
6718
6719 switch (na) {
6720 case 'rowspan':
6721 case 'colspan':
6722 // Whats the point? Remove usless attribute value
6723 if (v == '1')
6724 v = '';
6725
6726 break;
6727 }
6728
6729 if (this.attribValueFilter)
6730 v = this.attribValueFilter(na, v, n);
6731
6732 if (a.validVals) {
6733 for (i = a.validVals.length - 1; i >= 0; i--) {
6734 if (v == a.validVals[i])
6735 break;
6736 }
6737
6738 if (i == -1)
6739 return null;
6740 }
6741
6742 if (v === '' && typeof(a.defaultVal) != 'undefined') {
6743 v = a.defaultVal;
6744
6745 if (v === '{$uid}')
6746 return this.dom.uniqueId();
6747
6748 return v;
6749 } else {
6750 // Remove internal mceItemXX classes when content is extracted from editor
6751 if (na == 'class' && this.processObj.get)
6752 v = v.replace(/\s?mceItem\w+\s?/g, '');
6753 }
6754
6755 if (v === '')
6756 return null;
6757
6758
6759 return v;
6760 }
6761 });
6762 })(tinymce);
6763
6764 (function(tinymce) {
6765 tinymce.dom.ScriptLoader = function(settings) {
6766 var QUEUED = 0,
6767 LOADING = 1,
6768 LOADED = 2,
6769 states = {},
6770 queue = [],
6771 scriptLoadedCallbacks = {},
6772 queueLoadedCallbacks = [],
6773 loading = 0,
6774 undefined;
6775
6776 function loadScript(url, callback) {
6777 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
6778
6779 // Execute callback when script is loaded
6780 function done() {
6781 dom.remove(id);
6782
6783 if (elm)
6784 elm.onreadystatechange = elm.onload = elm = null;
6785
6786 callback();
6787 };
6788
6789 id = dom.uniqueId();
6790
6791 if (tinymce.isIE6) {
6792 uri = new tinymce.util.URI(url);
6793 loc = location;
6794
6795 // If script is from same domain and we
6796 // use IE 6 then use XHR since it's more reliable
6797 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
6798 tinymce.util.XHR.send({
6799 url : tinymce._addVer(uri.getURI()),
6800 success : function(content) {
6801 // Create new temp script element
6802 var script = dom.create('script', {
6803 type : 'text/javascript'
6804 });
6805
6806 // Evaluate script in global scope
6807 script.text = content;
6808 document.getElementsByTagName('head')[0].appendChild(script);
6809 dom.remove(script);
6810
6811 done();
6812 }
6813 });
6814
6815 return;
6816 }
6817 }
6818
6819 // Create new script element
6820 elm = dom.create('script', {
6821 id : id,
6822 type : 'text/javascript',
6823 src : tinymce._addVer(url)
6824 });
6825
6826 // Add onload and readystate listeners
6827 elm.onload = done;
6828 elm.onreadystatechange = function() {
6829 var state = elm.readyState;
6830
6831 // Loaded state is passed on IE 6 however there
6832 // are known issues with this method but we can't use
6833 // XHR in a cross domain loading
6834 if (state == 'complete' || state == 'loaded')
6835 done();
6836 };
6837
6838 // Most browsers support this feature so we report errors
6839 // for those at least to help users track their missing plugins etc
6840 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
6841 /*elm.onerror = function() {
6842 alert('Failed to load: ' + url);
6843 };*/
6844
6845 // Add script to document
6846 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
6847 };
6848
6849 this.isDone = function(url) {
6850 return states[url] == LOADED;
6851 };
6852
6853 this.markDone = function(url) {
6854 states[url] = LOADED;
6855 };
6856
6857 this.add = this.load = function(url, callback, scope) {
6858 var item, state = states[url];
6859
6860 // Add url to load queue
6861 if (state == undefined) {
6862 queue.push(url);
6863 states[url] = QUEUED;
6864 }
6865
6866 if (callback) {
6867 // Store away callback for later execution
6868 if (!scriptLoadedCallbacks[url])
6869 scriptLoadedCallbacks[url] = [];
6870
6871 scriptLoadedCallbacks[url].push({
6872 func : callback,
6873 scope : scope || this
6874 });
6875 }
6876 };
6877
6878 this.loadQueue = function(callback, scope) {
6879 this.loadScripts(queue, callback, scope);
6880 };
6881
6882 this.loadScripts = function(scripts, callback, scope) {
6883 var loadScripts;
6884
6885 function execScriptLoadedCallbacks(url) {
6886 // Execute URL callback functions
6887 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
6888 callback.func.call(callback.scope);
6889 });
6890
6891 scriptLoadedCallbacks[url] = undefined;
6892 };
6893
6894 queueLoadedCallbacks.push({
6895 func : callback,
6896 scope : scope || this
6897 });
6898
6899 loadScripts = function() {
6900 var loadingScripts = tinymce.grep(scripts);
6901
6902 // Current scripts has been handled
6903 scripts.length = 0;
6904
6905 // Load scripts that needs to be loaded
6906 tinymce.each(loadingScripts, function(url) {
6907 // Script is already loaded then execute script callbacks directly
6908 if (states[url] == LOADED) {
6909 execScriptLoadedCallbacks(url);
6910 return;
6911 }
6912
6913 // Is script not loading then start loading it
6914 if (states[url] != LOADING) {
6915 states[url] = LOADING;
6916 loading++;
6917
6918 loadScript(url, function() {
6919 states[url] = LOADED;
6920 loading--;
6921
6922 execScriptLoadedCallbacks(url);
6923
6924 // Load more scripts if they where added by the recently loaded script
6925 loadScripts();
6926 });
6927 }
6928 });
6929
6930 // No scripts are currently loading then execute all pending queue loaded callbacks
6931 if (!loading) {
6932 tinymce.each(queueLoadedCallbacks, function(callback) {
6933 callback.func.call(callback.scope);
6934 });
6935
6936 queueLoadedCallbacks.length = 0;
6937 }
6938 };
6939
6940 loadScripts();
6941 };
6942 };
6943
6944 // Global script loader
6945 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
6946 })(tinymce);
6947
6948 tinymce.dom.TreeWalker = function(start_node, root_node) {
6949 var node = start_node;
6950
6951 function findSibling(node, start_name, sibling_name, shallow) {
6952 var sibling, parent;
6953
6954 if (node) {
6955 // Walk into nodes if it has a start
6956 if (!shallow && node[start_name])
6957 return node[start_name];
6958
6959 // Return the sibling if it has one
6960 if (node != root_node) {
6961 sibling = node[sibling_name];
6962 if (sibling)
6963 return sibling;
6964
6965 // Walk up the parents to look for siblings
6966 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
6967 sibling = parent[sibling_name];
6968 if (sibling)
6969 return sibling;
6970 }
6971 }
6972 }
6973 };
6974
6975 this.current = function() {
6976 return node;
6977 };
6978
6979 this.next = function(shallow) {
6980 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
6981 };
6982
6983 this.prev = function(shallow) {
6984 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
6985 };
6986 };
6987
6988 (function() {
6989 var transitional = {};
6990
6991 function unpack(lookup, data) {
6992 var key;
6993
6994 function replace(value) {
6995 return value.replace(/[A-Z]+/g, function(key) {
6996 return replace(lookup[key]);
6997 });
6998 };
6999
7000 // Unpack lookup
7001 for (key in lookup) {
7002 if (lookup.hasOwnProperty(key))
7003 lookup[key] = replace(lookup[key]);
7004 }
7005
7006 // Unpack and parse data into object map
7007 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
7008 var i, map = {};
7009
7010 children = children.split(/\|/);
7011
7012 for (i = children.length - 1; i >= 0; i--)
7013 map[children[i]] = 1;
7014
7015 transitional[name] = map;
7016 });
7017 };
7018
7019 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
7020 // we will later include the attributes here and use it as a default for valid elements but it
7021 // requires us to rewrite the serializer engine
7022 unpack({
7023 Z : '#|H|K|N|O|P',
7024 Y : '#|X|form|R|Q',
7025 X : 'p|T|div|U|W|isindex|fieldset|table',
7026 W : 'pre|hr|blockquote|address|center|noframes',
7027 U : 'ul|ol|dl|menu|dir',
7028 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
7029 T : 'h1|h2|h3|h4|h5|h6',
7030 ZB : '#|X|S|Q',
7031 S : 'R|P',
7032 ZA : '#|a|G|J|M|O|P',
7033 R : '#|a|H|K|N|O',
7034 Q : 'noscript|P',
7035 P : 'ins|del|script',
7036 O : 'input|select|textarea|label|button',
7037 N : 'M|L',
7038 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
7039 L : 'sub|sup',
7040 K : 'J|I',
7041 J : 'tt|i|b|u|s|strike',
7042 I : 'big|small|font|basefont',
7043 H : 'G|F',
7044 G : 'br|span|bdo',
7045 F : 'object|applet|img|map|iframe'
7046 }, 'script[]' +
7047 'style[]' +
7048 'object[#|param|X|form|a|H|K|N|O|Q]' +
7049 'param[]' +
7050 'p[S]' +
7051 'a[Z]' +
7052 'br[]' +
7053 'span[S]' +
7054 'bdo[S]' +
7055 'applet[#|param|X|form|a|H|K|N|O|Q]' +
7056 'h1[S]' +
7057 'img[]' +
7058 'map[X|form|Q|area]' +
7059 'h2[S]' +
7060 'iframe[#|X|form|a|H|K|N|O|Q]' +
7061 'h3[S]' +
7062 'tt[S]' +
7063 'i[S]' +
7064 'b[S]' +
7065 'u[S]' +
7066 's[S]' +
7067 'strike[S]' +
7068 'big[S]' +
7069 'small[S]' +
7070 'font[S]' +
7071 'basefont[]' +
7072 'em[S]' +
7073 'strong[S]' +
7074 'dfn[S]' +
7075 'code[S]' +
7076 'q[S]' +
7077 'samp[S]' +
7078 'kbd[S]' +
7079 'var[S]' +
7080 'cite[S]' +
7081 'abbr[S]' +
7082 'acronym[S]' +
7083 'sub[S]' +
7084 'sup[S]' +
7085 'input[]' +
7086 'select[optgroup|option]' +
7087 'optgroup[option]' +
7088 'option[]' +
7089 'textarea[]' +
7090 'label[S]' +
7091 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
7092 'h4[S]' +
7093 'ins[#|X|form|a|H|K|N|O|Q]' +
7094 'h5[S]' +
7095 'del[#|X|form|a|H|K|N|O|Q]' +
7096 'h6[S]' +
7097 'div[#|X|form|a|H|K|N|O|Q]' +
7098 'ul[li]' +
7099 'li[#|X|form|a|H|K|N|O|Q]' +
7100 'ol[li]' +
7101 'dl[dt|dd]' +
7102 'dt[S]' +
7103 'dd[#|X|form|a|H|K|N|O|Q]' +
7104 'menu[li]' +
7105 'dir[li]' +
7106 'pre[ZA]' +
7107 'hr[]' +
7108 'blockquote[#|X|form|a|H|K|N|O|Q]' +
7109 'address[S|p]' +
7110 'center[#|X|form|a|H|K|N|O|Q]' +
7111 'noframes[#|X|form|a|H|K|N|O|Q]' +
7112 'isindex[]' +
7113 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
7114 'legend[S]' +
7115 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
7116 'caption[S]' +
7117 'col[]' +
7118 'colgroup[col]' +
7119 'thead[tr]' +
7120 'tr[th|td]' +
7121 'th[#|X|form|a|H|K|N|O|Q]' +
7122 'form[#|X|a|H|K|N|O|Q]' +
7123 'noscript[#|X|form|a|H|K|N|O|Q]' +
7124 'td[#|X|form|a|H|K|N|O|Q]' +
7125 'tfoot[tr]' +
7126 'tbody[tr]' +
7127 'area[]' +
7128 'base[]' +
7129 'body[#|X|form|a|H|K|N|O|Q]'
7130 );
7131
7132 tinymce.dom.Schema = function() {
7133 var t = this, elements = transitional;
7134
7135 t.isValid = function(name, child_name) {
7136 var element = elements[name];
7137
7138 return !!(element && (!child_name || element[child_name]));
7139 };
7140 };
7141 })();
7142 (function(tinymce) {
7143 tinymce.dom.RangeUtils = function(dom) {
7144 var INVISIBLE_CHAR = '\uFEFF';
7145
7146 this.walk = function(rng, callback) {
7147 var startContainer = rng.startContainer,
7148 startOffset = rng.startOffset,
7149 endContainer = rng.endContainer,
7150 endOffset = rng.endOffset,
7151 ancestor, startPoint,
7152 endPoint, node, parent, siblings, nodes;
7153
7154 // Handle table cell selection the table plugin enables
7155 // you to fake select table cells and perform formatting actions on them
7156 nodes = dom.select('td.mceSelected,th.mceSelected');
7157 if (nodes.length > 0) {
7158 tinymce.each(nodes, function(node) {
7159 callback([node]);
7160 });
7161
7162 return;
7163 }
7164
7165 function collectSiblings(node, name, end_node) {
7166 var siblings = [];
7167
7168 for (; node && node != end_node; node = node[name])
7169 siblings.push(node);
7170
7171 return siblings;
7172 };
7173
7174 function findEndPoint(node, root) {
7175 do {
7176 if (node.parentNode == root)
7177 return node;
7178
7179 node = node.parentNode;
7180 } while(node);
7181 };
7182
7183 function walkBoundary(start_node, end_node, next) {
7184 var siblingName = next ? 'nextSibling' : 'previousSibling';
7185
7186 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
7187 parent = node.parentNode;
7188 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
7189
7190 if (siblings.length) {
7191 if (!next)
7192 siblings.reverse();
7193
7194 callback(siblings);
7195 }
7196 }
7197 };
7198
7199 // If index based start position then resolve it
7200 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
7201 startContainer = startContainer.childNodes[startOffset];
7202
7203 // If index based end position then resolve it
7204 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
7205 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
7206
7207 // Find common ancestor and end points
7208 ancestor = dom.findCommonAncestor(startContainer, endContainer);
7209
7210 // Same container
7211 if (startContainer == endContainer)
7212 return callback([startContainer]);
7213
7214 // Process left side
7215 for (node = startContainer; node; node = node.parentNode) {
7216 if (node == endContainer)
7217 return walkBoundary(startContainer, ancestor, true);
7218
7219 if (node == ancestor)
7220 break;
7221 }
7222
7223 // Process right side
7224 for (node = endContainer; node; node = node.parentNode) {
7225 if (node == startContainer)
7226 return walkBoundary(endContainer, ancestor);
7227
7228 if (node == ancestor)
7229 break;
7230 }
7231
7232 // Find start/end point
7233 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
7234 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
7235
7236 // Walk left leaf
7237 walkBoundary(startContainer, startPoint, true);
7238
7239 // Walk the middle from start to end point
7240 siblings = collectSiblings(
7241 startPoint == startContainer ? startPoint : startPoint.nextSibling,
7242 'nextSibling',
7243 endPoint == endContainer ? endPoint.nextSibling : endPoint
7244 );
7245
7246 if (siblings.length)
7247 callback(siblings);
7248
7249 // Walk right leaf
7250 walkBoundary(endContainer, endPoint);
7251 };
7252
7253 /* this.split = function(rng) {
7254 var startContainer = rng.startContainer,
7255 startOffset = rng.startOffset,
7256 endContainer = rng.endContainer,
7257 endOffset = rng.endOffset;
7258
7259 function splitText(node, offset) {
7260 if (offset == node.nodeValue.length)
7261 node.appendData(INVISIBLE_CHAR);
7262
7263 node = node.splitText(offset);
7264
7265 if (node.nodeValue === INVISIBLE_CHAR)
7266 node.nodeValue = '';
7267
7268 return node;
7269 };
7270
7271 // Handle single text node
7272 if (startContainer == endContainer) {
7273 if (startContainer.nodeType == 3) {
7274 if (startOffset != 0)
7275 startContainer = endContainer = splitText(startContainer, startOffset);
7276
7277 if (endOffset - startOffset != startContainer.nodeValue.length)
7278 splitText(startContainer, endOffset - startOffset);
7279 }
7280 } else {
7281 // Split startContainer text node if needed
7282 if (startContainer.nodeType == 3 && startOffset != 0) {
7283 startContainer = splitText(startContainer, startOffset);
7284 startOffset = 0;
7285 }
7286
7287 // Split endContainer text node if needed
7288 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
7289 endContainer = splitText(endContainer, endOffset).previousSibling;
7290 endOffset = endContainer.nodeValue.length;
7291 }
7292 }
7293
7294 return {
7295 startContainer : startContainer,
7296 startOffset : startOffset,
7297 endContainer : endContainer,
7298 endOffset : endOffset
7299 };
7300 };
7301 */
7302 };
7303
7304 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
7305 if (rng1 && rng2) {
7306 // Compare native IE ranges
7307 if (rng1.item || rng1.duplicate) {
7308 // Both are control ranges and the selected element matches
7309 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
7310 return true;
7311
7312 // Both are text ranges and the range matches
7313 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
7314 return true;
7315 } else {
7316 // Compare w3c ranges
7317 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
7318 }
7319 }
7320
7321 return false;
7322 };
7323 })(tinymce);
7324
7325 (function(tinymce) {
7326 // Shorten class names
7327 var DOM = tinymce.DOM, is = tinymce.is;
7328
7329 tinymce.create('tinymce.ui.Control', {
7330 Control : function(id, s) {
7331 this.id = id;
7332 this.settings = s = s || {};
7333 this.rendered = false;
7334 this.onRender = new tinymce.util.Dispatcher(this);
7335 this.classPrefix = '';
7336 this.scope = s.scope || this;
7337 this.disabled = 0;
7338 this.active = 0;
7339 },
7340
7341 setDisabled : function(s) {
7342 var e;
7343
7344 if (s != this.disabled) {
7345 e = DOM.get(this.id);
7346
7347 // Add accessibility title for unavailable actions
7348 if (e && this.settings.unavailable_prefix) {
7349 if (s) {
7350 this.prevTitle = e.title;
7351 e.title = this.settings.unavailable_prefix + ": " + e.title;
7352 } else
7353 e.title = this.prevTitle;
7354 }
7355
7356 this.setState('Disabled', s);
7357 this.setState('Enabled', !s);
7358 this.disabled = s;
7359 }
7360 },
7361
7362 isDisabled : function() {
7363 return this.disabled;
7364 },
7365
7366 setActive : function(s) {
7367 if (s != this.active) {
7368 this.setState('Active', s);
7369 this.active = s;
7370 }
7371 },
7372
7373 isActive : function() {
7374 return this.active;
7375 },
7376
7377 setState : function(c, s) {
7378 var n = DOM.get(this.id);
7379
7380 c = this.classPrefix + c;
7381
7382 if (s)
7383 DOM.addClass(n, c);
7384 else
7385 DOM.removeClass(n, c);
7386 },
7387
7388 isRendered : function() {
7389 return this.rendered;
7390 },
7391
7392 renderHTML : function() {
7393 },
7394
7395 renderTo : function(n) {
7396 DOM.setHTML(n, this.renderHTML());
7397 },
7398
7399 postRender : function() {
7400 var t = this, b;
7401
7402 // Set pending states
7403 if (is(t.disabled)) {
7404 b = t.disabled;
7405 t.disabled = -1;
7406 t.setDisabled(b);
7407 }
7408
7409 if (is(t.active)) {
7410 b = t.active;
7411 t.active = -1;
7412 t.setActive(b);
7413 }
7414 },
7415
7416 remove : function() {
7417 DOM.remove(this.id);
7418 this.destroy();
7419 },
7420
7421 destroy : function() {
7422 tinymce.dom.Event.clear(this.id);
7423 }
7424 });
7425 })(tinymce);
7426 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
7427 Container : function(id, s) {
7428 this.parent(id, s);
7429
7430 this.controls = [];
7431
7432 this.lookup = {};
7433 },
7434
7435 add : function(c) {
7436 this.lookup[c.id] = c;
7437 this.controls.push(c);
7438
7439 return c;
7440 },
7441
7442 get : function(n) {
7443 return this.lookup[n];
7444 }
7445 });
7446
7447
7448 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
7449 Separator : function(id, s) {
7450 this.parent(id, s);
7451 this.classPrefix = 'mceSeparator';
7452 },
7453
7454 renderHTML : function() {
7455 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
7456 }
7457 });
7458
7459 (function(tinymce) {
7460 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
7461
7462 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
7463 MenuItem : function(id, s) {
7464 this.parent(id, s);
7465 this.classPrefix = 'mceMenuItem';
7466 },
7467
7468 setSelected : function(s) {
7469 this.setState('Selected', s);
7470 this.selected = s;
7471 },
7472
7473 isSelected : function() {
7474 return this.selected;
7475 },
7476
7477 postRender : function() {
7478 var t = this;
7479
7480 t.parent();
7481
7482 // Set pending state
7483 if (is(t.selected))
7484 t.setSelected(t.selected);
7485 }
7486 });
7487 })(tinymce);
7488
7489 (function(tinymce) {
7490 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
7491
7492 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
7493 Menu : function(id, s) {
7494 var t = this;
7495
7496 t.parent(id, s);
7497 t.items = {};
7498 t.collapsed = false;
7499 t.menuCount = 0;
7500 t.onAddItem = new tinymce.util.Dispatcher(this);
7501 },
7502
7503 expand : function(d) {
7504 var t = this;
7505
7506 if (d) {
7507 walk(t, function(o) {
7508 if (o.expand)
7509 o.expand();
7510 }, 'items', t);
7511 }
7512
7513 t.collapsed = false;
7514 },
7515
7516 collapse : function(d) {
7517 var t = this;
7518
7519 if (d) {
7520 walk(t, function(o) {
7521 if (o.collapse)
7522 o.collapse();
7523 }, 'items', t);
7524 }
7525
7526 t.collapsed = true;
7527 },
7528
7529 isCollapsed : function() {
7530 return this.collapsed;
7531 },
7532
7533 add : function(o) {
7534 if (!o.settings)
7535 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
7536
7537 this.onAddItem.dispatch(this, o);
7538
7539 return this.items[o.id] = o;
7540 },
7541
7542 addSeparator : function() {
7543 return this.add({separator : true});
7544 },
7545
7546 addMenu : function(o) {
7547 if (!o.collapse)
7548 o = this.createMenu(o);
7549
7550 this.menuCount++;
7551
7552 return this.add(o);
7553 },
7554
7555 hasMenus : function() {
7556 return this.menuCount !== 0;
7557 },
7558
7559 remove : function(o) {
7560 delete this.items[o.id];
7561 },
7562
7563 removeAll : function() {
7564 var t = this;
7565
7566 walk(t, function(o) {
7567 if (o.removeAll)
7568 o.removeAll();
7569 else
7570 o.remove();
7571
7572 o.destroy();
7573 }, 'items', t);
7574
7575 t.items = {};
7576 },
7577
7578 createMenu : function(o) {
7579 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
7580
7581 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
7582
7583 return m;
7584 }
7585 });
7586 })(tinymce);
7587 (function(tinymce) {
7588 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
7589
7590 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
7591 DropMenu : function(id, s) {
7592 s = s || {};
7593 s.container = s.container || DOM.doc.body;
7594 s.offset_x = s.offset_x || 0;
7595 s.offset_y = s.offset_y || 0;
7596 s.vp_offset_x = s.vp_offset_x || 0;
7597 s.vp_offset_y = s.vp_offset_y || 0;
7598
7599 if (is(s.icons) && !s.icons)
7600 s['class'] += ' mceNoIcons';
7601
7602 this.parent(id, s);
7603 this.onShowMenu = new tinymce.util.Dispatcher(this);
7604 this.onHideMenu = new tinymce.util.Dispatcher(this);
7605 this.classPrefix = 'mceMenu';
7606 },
7607
7608 createMenu : function(s) {
7609 var t = this, cs = t.settings, m;
7610
7611 s.container = s.container || cs.container;
7612 s.parent = t;
7613 s.constrain = s.constrain || cs.constrain;
7614 s['class'] = s['class'] || cs['class'];
7615 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
7616 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
7617 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
7618
7619 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
7620
7621 return m;
7622 },
7623
7624 update : function() {
7625 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
7626
7627 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
7628 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
7629
7630 if (!DOM.boxModel)
7631 t.element.setStyles({width : tw + 2, height : th + 2});
7632 else
7633 t.element.setStyles({width : tw, height : th});
7634
7635 if (s.max_width)
7636 DOM.setStyle(co, 'width', tw);
7637
7638 if (s.max_height) {
7639 DOM.setStyle(co, 'height', th);
7640
7641 if (tb.clientHeight < s.max_height)
7642 DOM.setStyle(co, 'overflow', 'hidden');
7643 }
7644 },
7645
7646 showMenu : function(x, y, px) {
7647 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
7648
7649 t.collapse(1);
7650
7651 if (t.isMenuVisible)
7652 return;
7653
7654 if (!t.rendered) {
7655 co = DOM.add(t.settings.container, t.renderNode());
7656
7657 each(t.items, function(o) {
7658 o.postRender();
7659 });
7660
7661 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
7662 } else
7663 co = DOM.get('menu_' + t.id);
7664
7665 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
7666 if (!tinymce.isOpera)
7667 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
7668
7669 DOM.show(co);
7670 t.update();
7671
7672 x += s.offset_x || 0;
7673 y += s.offset_y || 0;
7674 vp.w -= 4;
7675 vp.h -= 4;
7676
7677 // Move inside viewport if not submenu
7678 if (s.constrain) {
7679 w = co.clientWidth - ot;
7680 h = co.clientHeight - ot;
7681 mx = vp.x + vp.w;
7682 my = vp.y + vp.h;
7683
7684 if ((x + s.vp_offset_x + w) > mx)
7685 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
7686
7687 if ((y + s.vp_offset_y + h) > my)
7688 y = Math.max(0, (my - s.vp_offset_y) - h);
7689 }
7690
7691 DOM.setStyles(co, {left : x , top : y});
7692 t.element.update();
7693
7694 t.isMenuVisible = 1;
7695 t.mouseClickFunc = Event.add(co, 'click', function(e) {
7696 var m;
7697
7698 e = e.target;
7699
7700 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
7701 m = t.items[e.id];
7702
7703 if (m.isDisabled())
7704 return;
7705
7706 dm = t;
7707
7708 while (dm) {
7709 if (dm.hideMenu)
7710 dm.hideMenu();
7711
7712 dm = dm.settings.parent;
7713 }
7714
7715 if (m.settings.onclick)
7716 m.settings.onclick(e);
7717
7718 return Event.cancel(e); // Cancel to fix onbeforeunload problem
7719 }
7720 });
7721
7722 if (t.hasMenus()) {
7723 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
7724 var m, r, mi;
7725
7726 e = e.target;
7727 if (e && (e = DOM.getParent(e, 'tr'))) {
7728 m = t.items[e.id];
7729
7730 if (t.lastMenu)
7731 t.lastMenu.collapse(1);
7732
7733 if (m.isDisabled())
7734 return;
7735
7736 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
7737 //p = DOM.getPos(s.container);
7738 r = DOM.getRect(e);
7739 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
7740 t.lastMenu = m;
7741 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
7742 }
7743 }
7744 });
7745 }
7746
7747 t.onShowMenu.dispatch(t);
7748
7749 if (s.keyboard_focus) {
7750 Event.add(co, 'keydown', t._keyHandler, t);
7751 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
7752 t._focusIdx = 0;
7753 }
7754 },
7755
7756 hideMenu : function(c) {
7757 var t = this, co = DOM.get('menu_' + t.id), e;
7758
7759 if (!t.isMenuVisible)
7760 return;
7761
7762 Event.remove(co, 'mouseover', t.mouseOverFunc);
7763 Event.remove(co, 'click', t.mouseClickFunc);
7764 Event.remove(co, 'keydown', t._keyHandler);
7765 DOM.hide(co);
7766 t.isMenuVisible = 0;
7767
7768 if (!c)
7769 t.collapse(1);
7770
7771 if (t.element)
7772 t.element.hide();
7773
7774 if (e = DOM.get(t.id))
7775 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
7776
7777 t.onHideMenu.dispatch(t);
7778 },
7779
7780 add : function(o) {
7781 var t = this, co;
7782
7783 o = t.parent(o);
7784
7785 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
7786 t._add(DOM.select('tbody', co)[0], o);
7787
7788 return o;
7789 },
7790
7791 collapse : function(d) {
7792 this.parent(d);
7793 this.hideMenu(1);
7794 },
7795
7796 remove : function(o) {
7797 DOM.remove(o.id);
7798 this.destroy();
7799
7800 return this.parent(o);
7801 },
7802
7803 destroy : function() {
7804 var t = this, co = DOM.get('menu_' + t.id);
7805
7806 Event.remove(co, 'mouseover', t.mouseOverFunc);
7807 Event.remove(co, 'click', t.mouseClickFunc);
7808
7809 if (t.element)
7810 t.element.remove();
7811
7812 DOM.remove(co);
7813 },
7814
7815 renderNode : function() {
7816 var t = this, s = t.settings, n, tb, co, w;
7817
7818 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
7819 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
7820 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
7821
7822 if (s.menu_line)
7823 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
7824
7825 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
7826 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
7827 tb = DOM.add(n, 'tbody');
7828
7829 each(t.items, function(o) {
7830 t._add(tb, o);
7831 });
7832
7833 t.rendered = true;
7834
7835 return w;
7836 },
7837
7838 // Internal functions
7839
7840 _keyHandler : function(e) {
7841 var t = this, kc = e.keyCode;
7842
7843 function focus(d) {
7844 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
7845
7846 if (e) {
7847 t._focusIdx = i;
7848 e.focus();
7849 }
7850 };
7851
7852 switch (kc) {
7853 case 38:
7854 focus(-1); // Select first link
7855 return;
7856
7857 case 40:
7858 focus(1);
7859 return;
7860
7861 case 13:
7862 return;
7863
7864 case 27:
7865 return this.hideMenu();
7866 }
7867 },
7868
7869 _add : function(tb, o) {
7870 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
7871
7872 if (s.separator) {
7873 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
7874 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
7875
7876 if (n = ro.previousSibling)
7877 DOM.addClass(n, 'mceLast');
7878
7879 return;
7880 }
7881
7882 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
7883 n = it = DOM.add(n, 'td');
7884 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
7885
7886 DOM.addClass(it, s['class']);
7887 // n = DOM.add(n, 'span', {'class' : 'item'});
7888
7889 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
7890
7891 if (s.icon_src)
7892 DOM.add(ic, 'img', {src : s.icon_src});
7893
7894 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
7895
7896 if (o.settings.style)
7897 DOM.setAttrib(n, 'style', o.settings.style);
7898
7899 if (tb.childNodes.length == 1)
7900 DOM.addClass(ro, 'mceFirst');
7901
7902 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
7903 DOM.addClass(ro, 'mceFirst');
7904
7905 if (o.collapse)
7906 DOM.addClass(ro, cp + 'ItemSub');
7907
7908 if (n = ro.previousSibling)
7909 DOM.removeClass(n, 'mceLast');
7910
7911 DOM.addClass(ro, 'mceLast');
7912 }
7913 });
7914 })(tinymce);
7915 (function(tinymce) {
7916 var DOM = tinymce.DOM;
7917
7918 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
7919 Button : function(id, s) {
7920 this.parent(id, s);
7921 this.classPrefix = 'mceButton';
7922 },
7923
7924 renderHTML : function() {
7925 var cp = this.classPrefix, s = this.settings, h, l;
7926
7927 l = DOM.encode(s.label || '');
7928 h = '<a id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" title="' + DOM.encode(s.title) + '">';
7929
7930 if (s.image)
7931 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
7932 else
7933 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
7934
7935 return h;
7936 },
7937
7938 postRender : function() {
7939 var t = this, s = t.settings;
7940
7941 tinymce.dom.Event.add(t.id, 'click', function(e) {
7942 if (!t.isDisabled())
7943 return s.onclick.call(s.scope, e);
7944 });
7945 }
7946 });
7947 })(tinymce);
7948
7949 (function(tinymce) {
7950 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
7951
7952 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
7953 ListBox : function(id, s) {
7954 var t = this;
7955
7956 t.parent(id, s);
7957
7958 t.items = [];
7959
7960 t.onChange = new Dispatcher(t);
7961
7962 t.onPostRender = new Dispatcher(t);
7963
7964 t.onAdd = new Dispatcher(t);
7965
7966 t.onRenderMenu = new tinymce.util.Dispatcher(this);
7967
7968 t.classPrefix = 'mceListBox';
7969 },
7970
7971 select : function(va) {
7972 var t = this, fv, f;
7973
7974 if (va == undefined)
7975 return t.selectByIndex(-1);
7976
7977 // Is string or number make function selector
7978 if (va && va.call)
7979 f = va;
7980 else {
7981 f = function(v) {
7982 return v == va;
7983 };
7984 }
7985
7986 // Do we need to do something?
7987 if (va != t.selectedValue) {
7988 // Find item
7989 each(t.items, function(o, i) {
7990 if (f(o.value)) {
7991 fv = 1;
7992 t.selectByIndex(i);
7993 return false;
7994 }
7995 });
7996
7997 if (!fv)
7998 t.selectByIndex(-1);
7999 }
8000 },
8001
8002 selectByIndex : function(idx) {
8003 var t = this, e, o;
8004
8005 if (idx != t.selectedIndex) {
8006 e = DOM.get(t.id + '_text');
8007 o = t.items[idx];
8008
8009 if (o) {
8010 t.selectedValue = o.value;
8011 t.selectedIndex = idx;
8012 DOM.setHTML(e, DOM.encode(o.title));
8013 DOM.removeClass(e, 'mceTitle');
8014 } else {
8015 DOM.setHTML(e, DOM.encode(t.settings.title));
8016 DOM.addClass(e, 'mceTitle');
8017 t.selectedValue = t.selectedIndex = null;
8018 }
8019
8020 e = 0;
8021 }
8022 },
8023
8024 add : function(n, v, o) {
8025 var t = this;
8026
8027 o = o || {};
8028 o = tinymce.extend(o, {
8029 title : n,
8030 value : v
8031 });
8032
8033 t.items.push(o);
8034 t.onAdd.dispatch(t, o);
8035 },
8036
8037 getLength : function() {
8038 return this.items.length;
8039 },
8040
8041 renderHTML : function() {
8042 var h = '', t = this, s = t.settings, cp = t.classPrefix;
8043
8044 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
8045 h += '<td>' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
8046 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span></span>') + '</td>';
8047 h += '</tr></tbody></table>';
8048
8049 return h;
8050 },
8051
8052 showMenu : function() {
8053 var t = this, p1, p2, e = DOM.get(this.id), m;
8054
8055 if (t.isDisabled() || t.items.length == 0)
8056 return;
8057
8058 if (t.menu && t.menu.isMenuVisible)
8059 return t.hideMenu();
8060
8061 if (!t.isMenuRendered) {
8062 t.renderMenu();
8063 t.isMenuRendered = true;
8064 }
8065
8066 p1 = DOM.getPos(this.settings.menu_container);
8067 p2 = DOM.getPos(e);
8068
8069 m = t.menu;
8070 m.settings.offset_x = p2.x;
8071 m.settings.offset_y = p2.y;
8072 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
8073
8074 // Select in menu
8075 if (t.oldID)
8076 m.items[t.oldID].setSelected(0);
8077
8078 each(t.items, function(o) {
8079 if (o.value === t.selectedValue) {
8080 m.items[o.id].setSelected(1);
8081 t.oldID = o.id;
8082 }
8083 });
8084
8085 m.showMenu(0, e.clientHeight);
8086
8087 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
8088 DOM.addClass(t.id, t.classPrefix + 'Selected');
8089
8090 //DOM.get(t.id + '_text').focus();
8091 },
8092
8093 hideMenu : function(e) {
8094 var t = this;
8095
8096 if (t.menu && t.menu.isMenuVisible) {
8097 // Prevent double toogles by canceling the mouse click event to the button
8098 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
8099 return;
8100
8101 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
8102 DOM.removeClass(t.id, t.classPrefix + 'Selected');
8103 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
8104 t.menu.hideMenu();
8105 }
8106 }
8107 },
8108
8109 renderMenu : function() {
8110 var t = this, m;
8111
8112 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
8113 menu_line : 1,
8114 'class' : t.classPrefix + 'Menu mceNoIcons',
8115 max_width : 150,
8116 max_height : 150
8117 });
8118
8119 m.onHideMenu.add(t.hideMenu, t);
8120
8121 m.add({
8122 title : t.settings.title,
8123 'class' : 'mceMenuItemTitle',
8124 onclick : function() {
8125 if (t.settings.onselect('') !== false)
8126 t.select(''); // Must be runned after
8127 }
8128 });
8129
8130 each(t.items, function(o) {
8131 // No value then treat it as a title
8132 if (o.value === undefined) {
8133 m.add({
8134 title : o.title,
8135 'class' : 'mceMenuItemTitle',
8136 onclick : function() {
8137 if (t.settings.onselect('') !== false)
8138 t.select(''); // Must be runned after
8139 }
8140 });
8141 } else {
8142 o.id = DOM.uniqueId();
8143 o.onclick = function() {
8144 if (t.settings.onselect(o.value) !== false)
8145 t.select(o.value); // Must be runned after
8146 };
8147
8148 m.add(o);
8149 }
8150 });
8151
8152 t.onRenderMenu.dispatch(t, m);
8153 t.menu = m;
8154 },
8155
8156 postRender : function() {
8157 var t = this, cp = t.classPrefix;
8158
8159 Event.add(t.id, 'click', t.showMenu, t);
8160 Event.add(t.id + '_text', 'focus', function() {
8161 if (!t._focused) {
8162 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
8163 var idx = -1, v, kc = e.keyCode;
8164
8165 // Find current index
8166 each(t.items, function(v, i) {
8167 if (t.selectedValue == v.value)
8168 idx = i;
8169 });
8170
8171 // Move up/down
8172 if (kc == 38)
8173 v = t.items[idx - 1];
8174 else if (kc == 40)
8175 v = t.items[idx + 1];
8176 else if (kc == 13) {
8177 // Fake select on enter
8178 v = t.selectedValue;
8179 t.selectedValue = null; // Needs to be null to fake change
8180 t.settings.onselect(v);
8181 return Event.cancel(e);
8182 }
8183
8184 if (v) {
8185 t.hideMenu();
8186 t.select(v.value);
8187 }
8188 });
8189 }
8190
8191 t._focused = 1;
8192 });
8193 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
8194
8195 // Old IE doesn't have hover on all elements
8196 if (tinymce.isIE6 || !DOM.boxModel) {
8197 Event.add(t.id, 'mouseover', function() {
8198 if (!DOM.hasClass(t.id, cp + 'Disabled'))
8199 DOM.addClass(t.id, cp + 'Hover');
8200 });
8201
8202 Event.add(t.id, 'mouseout', function() {
8203 if (!DOM.hasClass(t.id, cp + 'Disabled'))
8204 DOM.removeClass(t.id, cp + 'Hover');
8205 });
8206 }
8207
8208 t.onPostRender.dispatch(t, DOM.get(t.id));
8209 },
8210
8211 destroy : function() {
8212 this.parent();
8213
8214 Event.clear(this.id + '_text');
8215 Event.clear(this.id + '_open');
8216 }
8217 });
8218 })(tinymce);
8219 (function(tinymce) {
8220 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
8221
8222 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
8223 NativeListBox : function(id, s) {
8224 this.parent(id, s);
8225 this.classPrefix = 'mceNativeListBox';
8226 },
8227
8228 setDisabled : function(s) {
8229 DOM.get(this.id).disabled = s;
8230 },
8231
8232 isDisabled : function() {
8233 return DOM.get(this.id).disabled;
8234 },
8235
8236 select : function(va) {
8237 var t = this, fv, f;
8238
8239 if (va == undefined)
8240 return t.selectByIndex(-1);
8241
8242 // Is string or number make function selector
8243 if (va && va.call)
8244 f = va;
8245 else {
8246 f = function(v) {
8247 return v == va;
8248 };
8249 }
8250
8251 // Do we need to do something?
8252 if (va != t.selectedValue) {
8253 // Find item
8254 each(t.items, function(o, i) {
8255 if (f(o.value)) {
8256 fv = 1;
8257 t.selectByIndex(i);
8258 return false;
8259 }
8260 });
8261
8262 if (!fv)
8263 t.selectByIndex(-1);
8264 }
8265 },
8266
8267 selectByIndex : function(idx) {
8268 DOM.get(this.id).selectedIndex = idx + 1;
8269 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
8270 },
8271
8272 add : function(n, v, a) {
8273 var o, t = this;
8274
8275 a = a || {};
8276 a.value = v;
8277
8278 if (t.isRendered())
8279 DOM.add(DOM.get(this.id), 'option', a, n);
8280
8281 o = {
8282 title : n,
8283 value : v,
8284 attribs : a
8285 };
8286
8287 t.items.push(o);
8288 t.onAdd.dispatch(t, o);
8289 },
8290
8291 getLength : function() {
8292 return this.items.length;
8293 },
8294
8295 renderHTML : function() {
8296 var h, t = this;
8297
8298 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
8299
8300 each(t.items, function(it) {
8301 h += DOM.createHTML('option', {value : it.value}, it.title);
8302 });
8303
8304 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
8305
8306 return h;
8307 },
8308
8309 postRender : function() {
8310 var t = this, ch;
8311
8312 t.rendered = true;
8313
8314 function onChange(e) {
8315 var v = t.items[e.target.selectedIndex - 1];
8316
8317 if (v && (v = v.value)) {
8318 t.onChange.dispatch(t, v);
8319
8320 if (t.settings.onselect)
8321 t.settings.onselect(v);
8322 }
8323 };
8324
8325 Event.add(t.id, 'change', onChange);
8326
8327 // Accessibility keyhandler
8328 Event.add(t.id, 'keydown', function(e) {
8329 var bf;
8330
8331 Event.remove(t.id, 'change', ch);
8332
8333 bf = Event.add(t.id, 'blur', function() {
8334 Event.add(t.id, 'change', onChange);
8335 Event.remove(t.id, 'blur', bf);
8336 });
8337
8338 if (e.keyCode == 13 || e.keyCode == 32) {
8339 onChange(e);
8340 return Event.cancel(e);
8341 }
8342 });
8343
8344 t.onPostRender.dispatch(t, DOM.get(t.id));
8345 }
8346 });
8347 })(tinymce);
8348 (function(tinymce) {
8349 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
8350
8351 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
8352 MenuButton : function(id, s) {
8353 this.parent(id, s);
8354
8355 this.onRenderMenu = new tinymce.util.Dispatcher(this);
8356
8357 s.menu_container = s.menu_container || DOM.doc.body;
8358 },
8359
8360 showMenu : function() {
8361 var t = this, p1, p2, e = DOM.get(t.id), m;
8362
8363 if (t.isDisabled())
8364 return;
8365
8366 if (!t.isMenuRendered) {
8367 t.renderMenu();
8368 t.isMenuRendered = true;
8369 }
8370
8371 if (t.isMenuVisible)
8372 return t.hideMenu();
8373
8374 p1 = DOM.getPos(t.settings.menu_container);
8375 p2 = DOM.getPos(e);
8376
8377 m = t.menu;
8378 m.settings.offset_x = p2.x;
8379 m.settings.offset_y = p2.y;
8380 m.settings.vp_offset_x = p2.x;
8381 m.settings.vp_offset_y = p2.y;
8382 m.settings.keyboard_focus = t._focused;
8383 m.showMenu(0, e.clientHeight);
8384
8385 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
8386 t.setState('Selected', 1);
8387
8388 t.isMenuVisible = 1;
8389 },
8390
8391 renderMenu : function() {
8392 var t = this, m;
8393
8394 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
8395 menu_line : 1,
8396 'class' : this.classPrefix + 'Menu',
8397 icons : t.settings.icons
8398 });
8399
8400 m.onHideMenu.add(t.hideMenu, t);
8401
8402 t.onRenderMenu.dispatch(t, m);
8403 t.menu = m;
8404 },
8405
8406 hideMenu : function(e) {
8407 var t = this;
8408
8409 // Prevent double toogles by canceling the mouse click event to the button
8410 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
8411 return;
8412
8413 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
8414 t.setState('Selected', 0);
8415 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
8416 if (t.menu)
8417 t.menu.hideMenu();
8418 }
8419
8420 t.isMenuVisible = 0;
8421 },
8422
8423 postRender : function() {
8424 var t = this, s = t.settings;
8425
8426 Event.add(t.id, 'click', function() {
8427 if (!t.isDisabled()) {
8428 if (s.onclick)
8429 s.onclick(t.value);
8430
8431 t.showMenu();
8432 }
8433 });
8434 }
8435 });
8436 })(tinymce);
8437
8438 (function(tinymce) {
8439 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
8440
8441 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
8442 SplitButton : function(id, s) {
8443 this.parent(id, s);
8444 this.classPrefix = 'mceSplitButton';
8445 },
8446
8447 renderHTML : function() {
8448 var h, t = this, s = t.settings, h1;
8449
8450 h = '<tbody><tr>';
8451
8452 if (s.image)
8453 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
8454 else
8455 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
8456
8457 h += '<td>' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
8458
8459 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
8460 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
8461
8462 h += '</tr></tbody>';
8463
8464 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
8465 },
8466
8467 postRender : function() {
8468 var t = this, s = t.settings;
8469
8470 if (s.onclick) {
8471 Event.add(t.id + '_action', 'click', function() {
8472 if (!t.isDisabled())
8473 s.onclick(t.value);
8474 });
8475 }
8476
8477 Event.add(t.id + '_open', 'click', t.showMenu, t);
8478 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
8479 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
8480
8481 // Old IE doesn't have hover on all elements
8482 if (tinymce.isIE6 || !DOM.boxModel) {
8483 Event.add(t.id, 'mouseover', function() {
8484 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
8485 DOM.addClass(t.id, 'mceSplitButtonHover');
8486 });
8487
8488 Event.add(t.id, 'mouseout', function() {
8489 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
8490 DOM.removeClass(t.id, 'mceSplitButtonHover');
8491 });
8492 }
8493 },
8494
8495 destroy : function() {
8496 this.parent();
8497
8498 Event.clear(this.id + '_action');
8499 Event.clear(this.id + '_open');
8500 }
8501 });
8502 })(tinymce);
8503
8504 (function(tinymce) {
8505 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
8506
8507 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
8508 ColorSplitButton : function(id, s) {
8509 var t = this;
8510
8511 t.parent(id, s);
8512
8513 t.settings = s = tinymce.extend({
8514 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',
8515 grid_width : 8,
8516 default_color : '#888888'
8517 }, t.settings);
8518
8519 t.onShowMenu = new tinymce.util.Dispatcher(t);
8520
8521 t.onHideMenu = new tinymce.util.Dispatcher(t);
8522
8523 t.value = s.default_color;
8524 },
8525
8526 showMenu : function() {
8527 var t = this, r, p, e, p2;
8528
8529 if (t.isDisabled())
8530 return;
8531
8532 if (!t.isMenuRendered) {
8533 t.renderMenu();
8534 t.isMenuRendered = true;
8535 }
8536
8537 if (t.isMenuVisible)
8538 return t.hideMenu();
8539
8540 e = DOM.get(t.id);
8541 DOM.show(t.id + '_menu');
8542 DOM.addClass(e, 'mceSplitButtonSelected');
8543 p2 = DOM.getPos(e);
8544 DOM.setStyles(t.id + '_menu', {
8545 left : p2.x,
8546 top : p2.y + e.clientHeight,
8547 zIndex : 200000
8548 });
8549 e = 0;
8550
8551 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
8552 t.onShowMenu.dispatch(t);
8553
8554 if (t._focused) {
8555 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
8556 if (e.keyCode == 27)
8557 t.hideMenu();
8558 });
8559
8560 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
8561 }
8562
8563 t.isMenuVisible = 1;
8564 },
8565
8566 hideMenu : function(e) {
8567 var t = this;
8568
8569 // Prevent double toogles by canceling the mouse click event to the button
8570 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
8571 return;
8572
8573 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
8574 DOM.removeClass(t.id, 'mceSplitButtonSelected');
8575 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
8576 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
8577 DOM.hide(t.id + '_menu');
8578 }
8579
8580 t.onHideMenu.dispatch(t);
8581
8582 t.isMenuVisible = 0;
8583 },
8584
8585 renderMenu : function() {
8586 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
8587
8588 w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
8589 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
8590 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
8591
8592 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
8593 tb = DOM.add(n, 'tbody');
8594
8595 // Generate color grid
8596 i = 0;
8597 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
8598 c = c.replace(/^#/, '');
8599
8600 if (!i--) {
8601 tr = DOM.add(tb, 'tr');
8602 i = s.grid_width - 1;
8603 }
8604
8605 n = DOM.add(tr, 'td');
8606
8607 n = DOM.add(n, 'a', {
8608 href : 'javascript:;',
8609 style : {
8610 backgroundColor : '#' + c
8611 },
8612 _mce_color : '#' + c
8613 });
8614 });
8615
8616 if (s.more_colors_func) {
8617 n = DOM.add(tb, 'tr');
8618 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
8619 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
8620
8621 Event.add(n, 'click', function(e) {
8622 s.more_colors_func.call(s.more_colors_scope || this);
8623 return Event.cancel(e); // Cancel to fix onbeforeunload problem
8624 });
8625 }
8626
8627 DOM.addClass(m, 'mceColorSplitMenu');
8628
8629 Event.add(t.id + '_menu', 'click', function(e) {
8630 var c;
8631
8632 e = e.target;
8633
8634 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
8635 t.setColor(c);
8636
8637 return Event.cancel(e); // Prevent IE auto save warning
8638 });
8639
8640 return w;
8641 },
8642
8643 setColor : function(c) {
8644 var t = this;
8645
8646 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
8647
8648 t.value = c;
8649 t.hideMenu();
8650 t.settings.onselect(c);
8651 },
8652
8653 postRender : function() {
8654 var t = this, id = t.id;
8655
8656 t.parent();
8657 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
8658 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
8659 },
8660
8661 destroy : function() {
8662 this.parent();
8663
8664 Event.clear(this.id + '_menu');
8665 Event.clear(this.id + '_more');
8666 DOM.remove(this.id + '_menu');
8667 }
8668 });
8669 })(tinymce);
8670
8671 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
8672 renderHTML : function() {
8673 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
8674
8675 cl = t.controls;
8676 for (i=0; i<cl.length; i++) {
8677 // Get current control, prev control, next control and if the control is a list box or not
8678 co = cl[i];
8679 pr = cl[i - 1];
8680 nx = cl[i + 1];
8681
8682 // Add toolbar start
8683 if (i === 0) {
8684 c = 'mceToolbarStart';
8685
8686 if (co.Button)
8687 c += ' mceToolbarStartButton';
8688 else if (co.SplitButton)
8689 c += ' mceToolbarStartSplitButton';
8690 else if (co.ListBox)
8691 c += ' mceToolbarStartListBox';
8692
8693 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
8694 }
8695
8696 // Add toolbar end before list box and after the previous button
8697 // This is to fix the o2k7 editor skins
8698 if (pr && co.ListBox) {
8699 if (pr.Button || pr.SplitButton)
8700 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
8701 }
8702
8703 // Render control HTML
8704
8705 // IE 8 quick fix, needed to propertly generate a hit area for anchors
8706 if (dom.stdMode)
8707 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
8708 else
8709 h += '<td>' + co.renderHTML() + '</td>';
8710
8711 // Add toolbar start after list box and before the next button
8712 // This is to fix the o2k7 editor skins
8713 if (nx && co.ListBox) {
8714 if (nx.Button || nx.SplitButton)
8715 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
8716 }
8717 }
8718
8719 c = 'mceToolbarEnd';
8720
8721 if (co.Button)
8722 c += ' mceToolbarEndButton';
8723 else if (co.SplitButton)
8724 c += ' mceToolbarEndSplitButton';
8725 else if (co.ListBox)
8726 c += ' mceToolbarEndListBox';
8727
8728 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
8729
8730 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
8731 }
8732 });
8733
8734 (function(tinymce) {
8735 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
8736
8737 tinymce.create('tinymce.AddOnManager', {
8738 AddOnManager : function() {
8739 var self = this;
8740
8741 self.items = [];
8742 self.urls = {};
8743 self.lookup = {};
8744 self.onAdd = new Dispatcher(self);
8745 },
8746
8747 get : function(n) {
8748 return this.lookup[n];
8749 },
8750
8751 requireLangPack : function(n) {
8752 var s = tinymce.settings;
8753
8754 if (s && s.language)
8755 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
8756 },
8757
8758 add : function(id, o) {
8759 this.items.push(o);
8760 this.lookup[id] = o;
8761 this.onAdd.dispatch(this, id, o);
8762
8763 return o;
8764 },
8765
8766 load : function(n, u, cb, s) {
8767 var t = this;
8768
8769 if (t.urls[n])
8770 return;
8771
8772 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
8773 u = tinymce.baseURL + '/' + u;
8774
8775 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
8776
8777 if (!t.lookup[n])
8778 tinymce.ScriptLoader.add(u, cb, s);
8779 }
8780 });
8781
8782 // Create plugin and theme managers
8783 tinymce.PluginManager = new tinymce.AddOnManager();
8784 tinymce.ThemeManager = new tinymce.AddOnManager();
8785 }(tinymce));
8786
8787 (function(tinymce) {
8788 // Shorten names
8789 var each = tinymce.each, extend = tinymce.extend,
8790 DOM = tinymce.DOM, Event = tinymce.dom.Event,
8791 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
8792 explode = tinymce.explode,
8793 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
8794
8795 // Setup some URLs where the editor API is located and where the document is
8796 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
8797 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
8798 tinymce.documentBaseURL += '/';
8799
8800 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
8801
8802 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
8803
8804 // Add before unload listener
8805 // This was required since IE was leaking memory if you added and removed beforeunload listeners
8806 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
8807 tinymce.onBeforeUnload = new Dispatcher(tinymce);
8808
8809 // Must be on window or IE will leak if the editor is placed in frame or iframe
8810 Event.add(window, 'beforeunload', function(e) {
8811 tinymce.onBeforeUnload.dispatch(tinymce, e);
8812 });
8813
8814 tinymce.onAddEditor = new Dispatcher(tinymce);
8815
8816 tinymce.onRemoveEditor = new Dispatcher(tinymce);
8817
8818 tinymce.EditorManager = extend(tinymce, {
8819 editors : [],
8820
8821 i18n : {},
8822
8823 activeEditor : null,
8824
8825 init : function(s) {
8826 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
8827
8828 function execCallback(se, n, s) {
8829 var f = se[n];
8830
8831 if (!f)
8832 return;
8833
8834 if (tinymce.is(f, 'string')) {
8835 s = f.replace(/\.\w+$/, '');
8836 s = s ? tinymce.resolve(s) : 0;
8837 f = tinymce.resolve(f);
8838 }
8839
8840 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
8841 };
8842
8843 s = extend({
8844 theme : "simple",
8845 language : "en"
8846 }, s);
8847
8848 t.settings = s;
8849
8850 // Legacy call
8851 Event.add(document, 'init', function() {
8852 var l, co;
8853
8854 execCallback(s, 'onpageload');
8855
8856 switch (s.mode) {
8857 case "exact":
8858 l = s.elements || '';
8859
8860 if(l.length > 0) {
8861 each(explode(l), function(v) {
8862 if (DOM.get(v)) {
8863 ed = new tinymce.Editor(v, s);
8864 el.push(ed);
8865 ed.render(1);
8866 } else {
8867 each(document.forms, function(f) {
8868 each(f.elements, function(e) {
8869 if (e.name === v) {
8870 v = 'mce_editor_' + instanceCounter++;
8871 DOM.setAttrib(e, 'id', v);
8872
8873 ed = new tinymce.Editor(v, s);
8874 el.push(ed);
8875 ed.render(1);
8876 }
8877 });
8878 });
8879 }
8880 });
8881 }
8882 break;
8883
8884 case "textareas":
8885 case "specific_textareas":
8886 function hasClass(n, c) {
8887 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
8888 };
8889
8890 each(DOM.select('textarea'), function(v) {
8891 if (s.editor_deselector && hasClass(v, s.editor_deselector))
8892 return;
8893
8894 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
8895 // Can we use the name
8896 e = DOM.get(v.name);
8897 if (!v.id && !e)
8898 v.id = v.name;
8899
8900 // Generate unique name if missing or already exists
8901 if (!v.id || t.get(v.id))
8902 v.id = DOM.uniqueId();
8903
8904 ed = new tinymce.Editor(v.id, s);
8905 el.push(ed);
8906 ed.render(1);
8907 }
8908 });
8909 break;
8910 }
8911
8912 // Call onInit when all editors are initialized
8913 if (s.oninit) {
8914 l = co = 0;
8915
8916 each(el, function(ed) {
8917 co++;
8918
8919 if (!ed.initialized) {
8920 // Wait for it
8921 ed.onInit.add(function() {
8922 l++;
8923
8924 // All done
8925 if (l == co)
8926 execCallback(s, 'oninit');
8927 });
8928 } else
8929 l++;
8930
8931 // All done
8932 if (l == co)
8933 execCallback(s, 'oninit');
8934 });
8935 }
8936 });
8937 },
8938
8939 get : function(id) {
8940 if (id === undefined)
8941 return this.editors;
8942
8943 return this.editors[id];
8944 },
8945
8946 getInstanceById : function(id) {
8947 return this.get(id);
8948 },
8949
8950 add : function(editor) {
8951 var self = this, editors = self.editors;
8952
8953 // Add named and index editor instance
8954 editors[editor.id] = editor;
8955 editors.push(editor);
8956
8957 self._setActive(editor);
8958 self.onAddEditor.dispatch(self, editor);
8959
8960
8961 return editor;
8962 },
8963
8964 remove : function(editor) {
8965 var t = this, i, editors = t.editors;
8966
8967 // Not in the collection
8968 if (!editors[editor.id])
8969 return null;
8970
8971 delete editors[editor.id];
8972
8973 for (i = 0; i < editors.length; i++) {
8974 if (editors[i] == editor) {
8975 editors.splice(i, 1);
8976 break;
8977 }
8978 }
8979
8980 // Select another editor since the active one was removed
8981 if (t.activeEditor == editor)
8982 t._setActive(editors[0]);
8983
8984 editor.destroy();
8985 t.onRemoveEditor.dispatch(t, editor);
8986
8987 return editor;
8988 },
8989
8990 execCommand : function(c, u, v) {
8991 var t = this, ed = t.get(v), w;
8992
8993 // Manager commands
8994 switch (c) {
8995 case "mceFocus":
8996 ed.focus();
8997 return true;
8998
8999 case "mceAddEditor":
9000 case "mceAddControl":
9001 if (!t.get(v))
9002 new tinymce.Editor(v, t.settings).render();
9003
9004 return true;
9005
9006 case "mceAddFrameControl":
9007 w = v.window;
9008
9009 // Add tinyMCE global instance and tinymce namespace to specified window
9010 w.tinyMCE = tinyMCE;
9011 w.tinymce = tinymce;
9012
9013 tinymce.DOM.doc = w.document;
9014 tinymce.DOM.win = w;
9015
9016 ed = new tinymce.Editor(v.element_id, v);
9017 ed.render();
9018
9019 // Fix IE memory leaks
9020 if (tinymce.isIE) {
9021 function clr() {
9022 ed.destroy();
9023 w.detachEvent('onunload', clr);
9024 w = w.tinyMCE = w.tinymce = null; // IE leak
9025 };
9026
9027 w.attachEvent('onunload', clr);
9028 }
9029
9030 v.page_window = null;
9031
9032 return true;
9033
9034 case "mceRemoveEditor":
9035 case "mceRemoveControl":
9036 if (ed)
9037 ed.remove();
9038
9039 return true;
9040
9041 case 'mceToggleEditor':
9042 if (!ed) {
9043 t.execCommand('mceAddControl', 0, v);
9044 return true;
9045 }
9046
9047 if (ed.isHidden())
9048 ed.show();
9049 else
9050 ed.hide();
9051
9052 return true;
9053 }
9054
9055 // Run command on active editor
9056 if (t.activeEditor)
9057 return t.activeEditor.execCommand(c, u, v);
9058
9059 return false;
9060 },
9061
9062 execInstanceCommand : function(id, c, u, v) {
9063 var ed = this.get(id);
9064
9065 if (ed)
9066 return ed.execCommand(c, u, v);
9067
9068 return false;
9069 },
9070
9071 triggerSave : function() {
9072 each(this.editors, function(e) {
9073 e.save();
9074 });
9075 },
9076
9077 addI18n : function(p, o) {
9078 var lo, i18n = this.i18n;
9079
9080 if (!tinymce.is(p, 'string')) {
9081 each(p, function(o, lc) {
9082 each(o, function(o, g) {
9083 each(o, function(o, k) {
9084 if (g === 'common')
9085 i18n[lc + '.' + k] = o;
9086 else
9087 i18n[lc + '.' + g + '.' + k] = o;
9088 });
9089 });
9090 });
9091 } else {
9092 each(o, function(o, k) {
9093 i18n[p + '.' + k] = o;
9094 });
9095 }
9096 },
9097
9098 // Private methods
9099
9100 _setActive : function(editor) {
9101 this.selectedInstance = this.activeEditor = editor;
9102 }
9103 });
9104 })(tinymce);
9105
9106 (function(tinymce) {
9107 // Shorten these names
9108 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
9109 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
9110 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
9111 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
9112 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
9113
9114 tinymce.create('tinymce.Editor', {
9115 Editor : function(id, s) {
9116 var t = this;
9117
9118 t.id = t.editorId = id;
9119
9120 t.execCommands = {};
9121 t.queryStateCommands = {};
9122 t.queryValueCommands = {};
9123
9124 t.isNotDirty = false;
9125
9126 t.plugins = {};
9127
9128 // Add events to the editor
9129 each([
9130 'onPreInit',
9131
9132 'onBeforeRenderUI',
9133
9134 'onPostRender',
9135
9136 'onInit',
9137
9138 'onRemove',
9139
9140 'onActivate',
9141
9142 'onDeactivate',
9143
9144 'onClick',
9145
9146 'onEvent',
9147
9148 'onMouseUp',
9149
9150 'onMouseDown',
9151
9152 'onDblClick',
9153
9154 'onKeyDown',
9155
9156 'onKeyUp',
9157
9158 'onKeyPress',
9159
9160 'onContextMenu',
9161
9162 'onSubmit',
9163
9164 'onReset',
9165
9166 'onPaste',
9167
9168 'onPreProcess',
9169
9170 'onPostProcess',
9171
9172 'onBeforeSetContent',
9173
9174 'onBeforeGetContent',
9175
9176 'onSetContent',
9177
9178 'onGetContent',
9179
9180 'onLoadContent',
9181
9182 'onSaveContent',
9183
9184 'onNodeChange',
9185
9186 'onChange',
9187
9188 'onBeforeExecCommand',
9189
9190 'onExecCommand',
9191
9192 'onUndo',
9193
9194 'onRedo',
9195
9196 'onVisualAid',
9197
9198 'onSetProgressState'
9199 ], function(e) {
9200 t[e] = new Dispatcher(t);
9201 });
9202
9203 t.settings = s = extend({
9204 id : id,
9205 language : 'en',
9206 docs_language : 'en',
9207 theme : 'simple',
9208 skin : 'default',
9209 delta_width : 0,
9210 delta_height : 0,
9211 popup_css : '',
9212 plugins : '',
9213 document_base_url : tinymce.documentBaseURL,
9214 add_form_submit_trigger : 1,
9215 submit_patch : 1,
9216 add_unload_trigger : 1,
9217 convert_urls : 1,
9218 relative_urls : 1,
9219 remove_script_host : 1,
9220 table_inline_editing : 0,
9221 object_resizing : 1,
9222 cleanup : 1,
9223 accessibility_focus : 1,
9224 custom_shortcuts : 1,
9225 custom_undo_redo_keyboard_shortcuts : 1,
9226 custom_undo_redo_restore_selection : 1,
9227 custom_undo_redo : 1,
9228 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
9229 visual_table_class : 'mceItemTable',
9230 visual : 1,
9231 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
9232 apply_source_formatting : 1,
9233 directionality : 'ltr',
9234 forced_root_block : 'p',
9235 valid_elements : '@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur],strong/b,em/i,strike,u,#p,-ol[type|compact],-ul[type|compact],-li,br,img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],-sub,-sup,-blockquote[cite],-table[border|cellspacing|cellpadding|width|frame|rules|height|align|summary|bgcolor|background|bordercolor],-tr[rowspan|width|height|align|valign|bgcolor|background|bordercolor],tbody,thead,tfoot,#td[colspan|rowspan|width|height|align|valign|bgcolor|background|bordercolor|scope],#th[colspan|rowspan|width|height|align|valign|scope],caption,-div,-span,-code,-pre,address,-h1,-h2,-h3,-h4,-h5,-h6,hr[size|noshade],-font[face|size|color],dd,dl,dt,cite,abbr,acronym,del[datetime|cite],ins[datetime|cite],object[classid|width|height|codebase|*],param[name|value],embed[type|width|height|src|*],script[src|type],map[name],area[shape|coords|href|alt|target],bdo,button,col[align|char|charoff|span|valign|width],colgroup[align|char|charoff|span|valign|width],dfn,fieldset,form[action|accept|accept-charset|enctype|method],input[accept|alt|checked|disabled|maxlength|name|readonly|size|src|type|value|tabindex|accesskey],kbd,label[for],legend,noscript,optgroup[label|disabled],option[disabled|label|selected|value],q[cite],samp,select[disabled|multiple|name|size],small,textarea[cols|rows|disabled|name|readonly],tt,var,big',
9236 hidden_input : 1,
9237 padd_empty_editor : 1,
9238 render_ui : 1,
9239 init_theme : 1,
9240 force_p_newlines : 1,
9241 indentation : '30px',
9242 keep_styles : 1,
9243 fix_table_elements : 1,
9244 inline_styles : 1,
9245 convert_fonts_to_spans : true
9246 }, s);
9247
9248 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
9249 base_uri : tinyMCE.baseURI
9250 });
9251
9252 t.baseURI = tinymce.baseURI;
9253
9254 // Call setup
9255 t.execCallback('setup', t);
9256 },
9257
9258 render : function(nst) {
9259 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
9260
9261 // Page is not loaded yet, wait for it
9262 if (!Event.domLoaded) {
9263 Event.add(document, 'init', function() {
9264 t.render();
9265 });
9266 return;
9267 }
9268
9269 tinyMCE.settings = s;
9270
9271 // Element not found, then skip initialization
9272 if (!t.getElement())
9273 return;
9274
9275 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
9276 // browser says it has contentEditable support but there is no visible caret
9277 // We will remove this check ones Apple implements full contentEditable support
9278 if (tinymce.isIDevice)
9279 return;
9280
9281 // Add hidden input for non input elements inside form elements
9282 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
9283 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
9284
9285 if (tinymce.WindowManager)
9286 t.windowManager = new tinymce.WindowManager(t);
9287
9288 if (s.encoding == 'xml') {
9289 t.onGetContent.add(function(ed, o) {
9290 if (o.save)
9291 o.content = DOM.encode(o.content);
9292 });
9293 }
9294
9295 if (s.add_form_submit_trigger) {
9296 t.onSubmit.addToTop(function() {
9297 if (t.initialized) {
9298 t.save();
9299 t.isNotDirty = 1;
9300 }
9301 });
9302 }
9303
9304 if (s.add_unload_trigger) {
9305 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
9306 if (t.initialized && !t.destroyed && !t.isHidden())
9307 t.save({format : 'raw', no_events : true});
9308 });
9309 }
9310
9311 tinymce.addUnload(t.destroy, t);
9312
9313 if (s.submit_patch) {
9314 t.onBeforeRenderUI.add(function() {
9315 var n = t.getElement().form;
9316
9317 if (!n)
9318 return;
9319
9320 // Already patched
9321 if (n._mceOldSubmit)
9322 return;
9323
9324 // Check page uses id="submit" or name="submit" for it's submit button
9325 if (!n.submit.nodeType && !n.submit.length) {
9326 t.formElement = n;
9327 n._mceOldSubmit = n.submit;
9328 n.submit = function() {
9329 // Save all instances
9330 tinymce.triggerSave();
9331 t.isNotDirty = 1;
9332
9333 return t.formElement._mceOldSubmit(t.formElement);
9334 };
9335 }
9336
9337 n = null;
9338 });
9339 }
9340
9341 // Load scripts
9342 function loadScripts() {
9343 if (s.language)
9344 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
9345
9346 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
9347 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
9348
9349 each(explode(s.plugins), function(p) {
9350 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
9351 // Skip safari plugin, since it is removed as of 3.3b1
9352 if (p == 'safari')
9353 return;
9354
9355 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
9356 }
9357 });
9358
9359 // Init when que is loaded
9360 sl.loadQueue(function() {
9361 if (!t.removed)
9362 t.init();
9363 });
9364 };
9365
9366 loadScripts();
9367 },
9368
9369 init : function() {
9370 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
9371
9372 tinymce.add(t);
9373
9374 if (s.theme) {
9375 s.theme = s.theme.replace(/-/, '');
9376 o = ThemeManager.get(s.theme);
9377 t.theme = new o();
9378
9379 if (t.theme.init && s.init_theme)
9380 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
9381 }
9382
9383 // Create all plugins
9384 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
9385 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
9386
9387 if (c) {
9388 po = new c(t, u);
9389
9390 t.plugins[p] = po;
9391
9392 if (po.init)
9393 po.init(t, u);
9394 }
9395 });
9396
9397 // Setup popup CSS path(s)
9398 if (s.popup_css !== false) {
9399 if (s.popup_css)
9400 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
9401 else
9402 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
9403 }
9404
9405 if (s.popup_css_add)
9406 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
9407
9408 t.controlManager = new tinymce.ControlManager(t);
9409
9410 if (s.custom_undo_redo) {
9411 // Add initial undo level
9412 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
9413 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
9414 if (!t.undoManager.hasUndo())
9415 t.undoManager.add();
9416 }
9417 });
9418
9419 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
9420 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
9421 t.undoManager.add();
9422 });
9423 }
9424
9425 t.onExecCommand.add(function(ed, c) {
9426 // Don't refresh the select lists until caret move
9427 if (!/^(FontName|FontSize)$/.test(c))
9428 t.nodeChanged();
9429 });
9430
9431 // Remove ghost selections on images and tables in Gecko
9432 if (isGecko) {
9433 function repaint(a, o) {
9434 if (!o || !o.initial)
9435 t.execCommand('mceRepaint');
9436 };
9437
9438 t.onUndo.add(repaint);
9439 t.onRedo.add(repaint);
9440 t.onSetContent.add(repaint);
9441 }
9442
9443 // Enables users to override the control factory
9444 t.onBeforeRenderUI.dispatch(t, t.controlManager);
9445
9446 // Measure box
9447 if (s.render_ui) {
9448 w = s.width || e.style.width || e.offsetWidth;
9449 h = s.height || e.style.height || e.offsetHeight;
9450 t.orgDisplay = e.style.display;
9451 re = /^[0-9\.]+(|px)$/i;
9452
9453 if (re.test('' + w))
9454 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
9455
9456 if (re.test('' + h))
9457 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
9458
9459 // Render UI
9460 o = t.theme.renderUI({
9461 targetNode : e,
9462 width : w,
9463 height : h,
9464 deltaWidth : s.delta_width,
9465 deltaHeight : s.delta_height
9466 });
9467
9468 t.editorContainer = o.editorContainer;
9469 }
9470
9471
9472 // User specified a document.domain value
9473 if (document.domain && location.hostname != document.domain)
9474 tinymce.relaxedDomain = document.domain;
9475
9476 // Resize editor
9477 DOM.setStyles(o.sizeContainer || o.editorContainer, {
9478 width : w,
9479 height : h
9480 });
9481
9482 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
9483 if (h < 100)
9484 h = 100;
9485
9486 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
9487
9488 // We only need to override paths if we have to
9489 // IE has a bug where it remove site absolute urls to relative ones if this is specified
9490 if (s.document_base_url != tinymce.documentBaseURL)
9491 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
9492
9493 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
9494
9495 if (tinymce.relaxedDomain)
9496 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
9497
9498 bi = s.body_id || 'tinymce';
9499 if (bi.indexOf('=') != -1) {
9500 bi = t.getParam('body_id', '', 'hash');
9501 bi = bi[t.id] || bi;
9502 }
9503
9504 bc = s.body_class || '';
9505 if (bc.indexOf('=') != -1) {
9506 bc = t.getParam('body_class', '', 'hash');
9507 bc = bc[t.id] || '';
9508 }
9509
9510 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
9511
9512 // Domain relaxing enabled, then set document domain
9513 if (tinymce.relaxedDomain) {
9514 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
9515 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
9516 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();})()';
9517 else if (tinymce.isOpera)
9518 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
9519 }
9520
9521 // Create iframe
9522 n = DOM.add(o.iframeContainer, 'iframe', {
9523 id : t.id + "_ifr",
9524 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
9525 frameBorder : '0',
9526 style : {
9527 width : '100%',
9528 height : h
9529 }
9530 });
9531
9532 t.contentAreaContainer = o.iframeContainer;
9533 DOM.get(o.editorContainer).style.display = t.orgDisplay;
9534 DOM.get(t.id).style.display = 'none';
9535
9536 if (!isIE || !tinymce.relaxedDomain)
9537 t.setupIframe();
9538
9539 e = n = o = null; // Cleanup
9540 },
9541
9542 setupIframe : function() {
9543 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
9544
9545 // Setup iframe body
9546 if (!isIE || !tinymce.relaxedDomain) {
9547 d.open();
9548 d.write(t.iframeHTML);
9549 d.close();
9550 }
9551
9552 // Design mode needs to be added here Ctrl+A will fail otherwise
9553 if (!isIE) {
9554 try {
9555 if (!s.readonly)
9556 d.designMode = 'On';
9557 } catch (ex) {
9558 // Will fail on Gecko if the editor is placed in an hidden container element
9559 // The design mode will be set ones the editor is focused
9560 }
9561 }
9562
9563 // IE needs to use contentEditable or it will display non secure items for HTTPS
9564 if (isIE) {
9565 // It will not steal focus if we hide it while setting contentEditable
9566 b = t.getBody();
9567 DOM.hide(b);
9568
9569 if (!s.readonly)
9570 b.contentEditable = true;
9571
9572 DOM.show(b);
9573 }
9574
9575 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
9576 keep_values : true,
9577 url_converter : t.convertURL,
9578 url_converter_scope : t,
9579 hex_colors : s.force_hex_style_colors,
9580 class_filter : s.class_filter,
9581 update_styles : 1,
9582 fix_ie_paragraphs : 1,
9583 valid_styles : s.valid_styles
9584 });
9585
9586 t.schema = new tinymce.dom.Schema();
9587
9588 t.serializer = new tinymce.dom.Serializer(extend(s, {
9589 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
9590 dom : t.dom,
9591 schema : t.schema
9592 }));
9593
9594 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
9595
9596 t.formatter = new tinymce.Formatter(this);
9597
9598 // Register default formats
9599 t.formatter.register({
9600 alignleft : [
9601 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
9602 {selector : 'img,table', styles : {'float' : 'left'}}
9603 ],
9604
9605 aligncenter : [
9606 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
9607 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
9608 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
9609 ],
9610
9611 alignright : [
9612 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
9613 {selector : 'img,table', styles : {'float' : 'right'}}
9614 ],
9615
9616 alignfull : [
9617 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
9618 ],
9619
9620 bold : [
9621 {inline : 'strong'},
9622 {inline : 'span', styles : {fontWeight : 'bold'}},
9623 {inline : 'b'}
9624 ],
9625
9626 italic : [
9627 {inline : 'em'},
9628 {inline : 'span', styles : {fontStyle : 'italic'}},
9629 {inline : 'i'}
9630 ],
9631
9632 underline : [
9633 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
9634 {inline : 'u'}
9635 ],
9636
9637 strikethrough : [
9638 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
9639 {inline : 'u'}
9640 ],
9641
9642 forecolor : {inline : 'span', styles : {color : '%value'}},
9643 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
9644 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
9645 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
9646 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
9647 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
9648
9649 removeformat : [
9650 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
9651 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
9652 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
9653 ]
9654 });
9655
9656 // Register default block formats
9657 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
9658 t.formatter.register(name, {block : name, remove : 'all'});
9659 });
9660
9661 // Register user defined formats
9662 t.formatter.register(t.settings.formats);
9663
9664 t.undoManager = new tinymce.UndoManager(t);
9665
9666 // Pass through
9667 t.undoManager.onAdd.add(function(um, l) {
9668 if (!l.initial)
9669 return t.onChange.dispatch(t, l, um);
9670 });
9671
9672 t.undoManager.onUndo.add(function(um, l) {
9673 return t.onUndo.dispatch(t, l, um);
9674 });
9675
9676 t.undoManager.onRedo.add(function(um, l) {
9677 return t.onRedo.dispatch(t, l, um);
9678 });
9679
9680 t.forceBlocks = new tinymce.ForceBlocks(t, {
9681 forced_root_block : s.forced_root_block
9682 });
9683
9684 t.editorCommands = new tinymce.EditorCommands(t);
9685
9686 // Pass through
9687 t.serializer.onPreProcess.add(function(se, o) {
9688 return t.onPreProcess.dispatch(t, o, se);
9689 });
9690
9691 t.serializer.onPostProcess.add(function(se, o) {
9692 return t.onPostProcess.dispatch(t, o, se);
9693 });
9694
9695 t.onPreInit.dispatch(t);
9696
9697 if (!s.gecko_spellcheck)
9698 t.getBody().spellcheck = 0;
9699
9700 if (!s.readonly)
9701 t._addEvents();
9702
9703 t.controlManager.onPostRender.dispatch(t, t.controlManager);
9704 t.onPostRender.dispatch(t);
9705
9706 if (s.directionality)
9707 t.getBody().dir = s.directionality;
9708
9709 if (s.nowrap)
9710 t.getBody().style.whiteSpace = "nowrap";
9711
9712 if (s.custom_elements) {
9713 function handleCustom(ed, o) {
9714 each(explode(s.custom_elements), function(v) {
9715 var n;
9716
9717 if (v.indexOf('~') === 0) {
9718 v = v.substring(1);
9719 n = 'span';
9720 } else
9721 n = 'div';
9722
9723 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
9724 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
9725 });
9726 };
9727
9728 t.onBeforeSetContent.add(handleCustom);
9729 t.onPostProcess.add(function(ed, o) {
9730 if (o.set)
9731 handleCustom(ed, o);
9732 });
9733 }
9734
9735 if (s.handle_node_change_callback) {
9736 t.onNodeChange.add(function(ed, cm, n) {
9737 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
9738 });
9739 }
9740
9741 if (s.save_callback) {
9742 t.onSaveContent.add(function(ed, o) {
9743 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
9744
9745 if (h)
9746 o.content = h;
9747 });
9748 }
9749
9750 if (s.onchange_callback) {
9751 t.onChange.add(function(ed, l) {
9752 t.execCallback('onchange_callback', t, l);
9753 });
9754 }
9755
9756 if (s.convert_newlines_to_brs) {
9757 t.onBeforeSetContent.add(function(ed, o) {
9758 if (o.initial)
9759 o.content = o.content.replace(/\r?\n/g, '<br />');
9760 });
9761 }
9762
9763 if (s.fix_nesting && isIE) {
9764 t.onBeforeSetContent.add(function(ed, o) {
9765 o.content = t._fixNesting(o.content);
9766 });
9767 }
9768
9769 if (s.preformatted) {
9770 t.onPostProcess.add(function(ed, o) {
9771 o.content = o.content.replace(/^\s*<pre.*?>/, '');
9772 o.content = o.content.replace(/<\/pre>\s*$/, '');
9773
9774 if (o.set)
9775 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
9776 });
9777 }
9778
9779 if (s.verify_css_classes) {
9780 t.serializer.attribValueFilter = function(n, v) {
9781 var s, cl;
9782
9783 if (n == 'class') {
9784 // Build regexp for classes
9785 if (!t.classesRE) {
9786 cl = t.dom.getClasses();
9787
9788 if (cl.length > 0) {
9789 s = '';
9790
9791 each (cl, function(o) {
9792 s += (s ? '|' : '') + o['class'];
9793 });
9794
9795 t.classesRE = new RegExp('(' + s + ')', 'gi');
9796 }
9797 }
9798
9799 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
9800 }
9801
9802 return v;
9803 };
9804 }
9805
9806 if (s.cleanup_callback) {
9807 t.onBeforeSetContent.add(function(ed, o) {
9808 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
9809 });
9810
9811 t.onPreProcess.add(function(ed, o) {
9812 if (o.set)
9813 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
9814
9815 if (o.get)
9816 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
9817 });
9818
9819 t.onPostProcess.add(function(ed, o) {
9820 if (o.set)
9821 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
9822
9823 if (o.get)
9824 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
9825 });
9826 }
9827
9828 if (s.save_callback) {
9829 t.onGetContent.add(function(ed, o) {
9830 if (o.save)
9831 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
9832 });
9833 }
9834
9835 if (s.handle_event_callback) {
9836 t.onEvent.add(function(ed, e, o) {
9837 if (t.execCallback('handle_event_callback', e, ed, o) === false)
9838 Event.cancel(e);
9839 });
9840 }
9841
9842 // Add visual aids when new contents is added
9843 t.onSetContent.add(function() {
9844 t.addVisual(t.getBody());
9845 });
9846
9847 // Remove empty contents
9848 if (s.padd_empty_editor) {
9849 t.onPostProcess.add(function(ed, o) {
9850 o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
9851 });
9852 }
9853
9854 if (isGecko) {
9855 // Fix gecko link bug, when a link is placed at the end of block elements there is
9856 // no way to move the caret behind the link. This fix adds a bogus br element after the link
9857 function fixLinks(ed, o) {
9858 each(ed.dom.select('a'), function(n) {
9859 var pn = n.parentNode;
9860
9861 if (ed.dom.isBlock(pn) && pn.lastChild === n)
9862 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
9863 });
9864 };
9865
9866 t.onExecCommand.add(function(ed, cmd) {
9867 if (cmd === 'CreateLink')
9868 fixLinks(ed);
9869 });
9870
9871 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
9872
9873 if (!s.readonly) {
9874 try {
9875 // Design mode must be set here once again to fix a bug where
9876 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
9877 d.designMode = 'Off';
9878 d.designMode = 'On';
9879 } catch (ex) {
9880 // Will fail on Gecko if the editor is placed in an hidden container element
9881 // The design mode will be set ones the editor is focused
9882 }
9883 }
9884 }
9885
9886 // A small timeout was needed since firefox will remove. Bug: #1838304
9887 setTimeout(function () {
9888 if (t.removed)
9889 return;
9890
9891 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
9892 t.startContent = t.getContent({format : 'raw'});
9893 t.initialized = true;
9894
9895 t.onInit.dispatch(t);
9896 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
9897 t.execCallback('init_instance_callback', t);
9898 t.focus(true);
9899 t.nodeChanged({initial : 1});
9900
9901 // Load specified content CSS last
9902 if (s.content_css) {
9903 tinymce.each(explode(s.content_css), function(u) {
9904 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
9905 });
9906 }
9907
9908 // Handle auto focus
9909 if (s.auto_focus) {
9910 setTimeout(function () {
9911 var ed = tinymce.get(s.auto_focus);
9912
9913 ed.selection.select(ed.getBody(), 1);
9914 ed.selection.collapse(1);
9915 ed.getWin().focus();
9916 }, 100);
9917 }
9918 }, 1);
9919
9920 e = null;
9921 },
9922
9923
9924 focus : function(sf) {
9925 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
9926
9927 if (!sf) {
9928 // Get selected control element
9929 ieRng = t.selection.getRng();
9930 if (ieRng.item) {
9931 controlElm = ieRng.item(0);
9932 }
9933
9934 // Is not content editable
9935 if (!ce)
9936 t.getWin().focus();
9937
9938 // Restore selected control element
9939 // This is needed when for example an image is selected within a
9940 // layer a call to focus will then remove the control selection
9941 if (controlElm && controlElm.ownerDocument == doc) {
9942 ieRng = doc.body.createControlRange();
9943 ieRng.addElement(controlElm);
9944 ieRng.select();
9945 }
9946
9947 }
9948
9949 if (tinymce.activeEditor != t) {
9950 if ((oed = tinymce.activeEditor) != null)
9951 oed.onDeactivate.dispatch(oed, t);
9952
9953 t.onActivate.dispatch(t, oed);
9954 }
9955
9956 tinymce._setActive(t);
9957 },
9958
9959 execCallback : function(n) {
9960 var t = this, f = t.settings[n], s;
9961
9962 if (!f)
9963 return;
9964
9965 // Look through lookup
9966 if (t.callbackLookup && (s = t.callbackLookup[n])) {
9967 f = s.func;
9968 s = s.scope;
9969 }
9970
9971 if (is(f, 'string')) {
9972 s = f.replace(/\.\w+$/, '');
9973 s = s ? tinymce.resolve(s) : 0;
9974 f = tinymce.resolve(f);
9975 t.callbackLookup = t.callbackLookup || {};
9976 t.callbackLookup[n] = {func : f, scope : s};
9977 }
9978
9979 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
9980 },
9981
9982 translate : function(s) {
9983 var c = this.settings.language || 'en', i18n = tinymce.i18n;
9984
9985 if (!s)
9986 return '';
9987
9988 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
9989 return i18n[c + '.' + b] || '{#' + b + '}';
9990 });
9991 },
9992
9993 getLang : function(n, dv) {
9994 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
9995 },
9996
9997 getParam : function(n, dv, ty) {
9998 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
9999
10000 if (ty === 'hash') {
10001 o = {};
10002
10003 if (is(v, 'string')) {
10004 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
10005 v = v.split('=');
10006
10007 if (v.length > 1)
10008 o[tr(v[0])] = tr(v[1]);
10009 else
10010 o[tr(v[0])] = tr(v);
10011 });
10012 } else
10013 o = v;
10014
10015 return o;
10016 }
10017
10018 return v;
10019 },
10020
10021 nodeChanged : function(o) {
10022 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
10023
10024 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
10025 if (t.initialized) {
10026 o = o || {};
10027 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
10028
10029 // Get parents and add them to object
10030 o.parents = [];
10031 t.dom.getParent(n, function(node) {
10032 if (node.nodeName == 'BODY')
10033 return true;
10034
10035 o.parents.push(node);
10036 });
10037
10038 t.onNodeChange.dispatch(
10039 t,
10040 o ? o.controlManager || t.controlManager : t.controlManager,
10041 n,
10042 s.isCollapsed(),
10043 o
10044 );
10045 }
10046 },
10047
10048 addButton : function(n, s) {
10049 var t = this;
10050
10051 t.buttons = t.buttons || {};
10052 t.buttons[n] = s;
10053 },
10054
10055 addCommand : function(n, f, s) {
10056 this.execCommands[n] = {func : f, scope : s || this};
10057 },
10058
10059 addQueryStateHandler : function(n, f, s) {
10060 this.queryStateCommands[n] = {func : f, scope : s || this};
10061 },
10062
10063 addQueryValueHandler : function(n, f, s) {
10064 this.queryValueCommands[n] = {func : f, scope : s || this};
10065 },
10066
10067 addShortcut : function(pa, desc, cmd_func, sc) {
10068 var t = this, c;
10069
10070 if (!t.settings.custom_shortcuts)
10071 return false;
10072
10073 t.shortcuts = t.shortcuts || {};
10074
10075 if (is(cmd_func, 'string')) {
10076 c = cmd_func;
10077
10078 cmd_func = function() {
10079 t.execCommand(c, false, null);
10080 };
10081 }
10082
10083 if (is(cmd_func, 'object')) {
10084 c = cmd_func;
10085
10086 cmd_func = function() {
10087 t.execCommand(c[0], c[1], c[2]);
10088 };
10089 }
10090
10091 each(explode(pa), function(pa) {
10092 var o = {
10093 func : cmd_func,
10094 scope : sc || this,
10095 desc : desc,
10096 alt : false,
10097 ctrl : false,
10098 shift : false
10099 };
10100
10101 each(explode(pa, '+'), function(v) {
10102 switch (v) {
10103 case 'alt':
10104 case 'ctrl':
10105 case 'shift':
10106 o[v] = true;
10107 break;
10108
10109 default:
10110 o.charCode = v.charCodeAt(0);
10111 o.keyCode = v.toUpperCase().charCodeAt(0);
10112 }
10113 });
10114
10115 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
10116 });
10117
10118 return true;
10119 },
10120
10121 execCommand : function(cmd, ui, val, a) {
10122 var t = this, s = 0, o, st;
10123
10124 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
10125 t.focus();
10126
10127 o = {};
10128 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
10129 if (o.terminate)
10130 return false;
10131
10132 // Command callback
10133 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
10134 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10135 return true;
10136 }
10137
10138 // Registred commands
10139 if (o = t.execCommands[cmd]) {
10140 st = o.func.call(o.scope, ui, val);
10141
10142 // Fall through on true
10143 if (st !== true) {
10144 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10145 return st;
10146 }
10147 }
10148
10149 // Plugin commands
10150 each(t.plugins, function(p) {
10151 if (p.execCommand && p.execCommand(cmd, ui, val)) {
10152 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10153 s = 1;
10154 return false;
10155 }
10156 });
10157
10158 if (s)
10159 return true;
10160
10161 // Theme commands
10162 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
10163 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10164 return true;
10165 }
10166
10167 // Execute global commands
10168 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
10169 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10170 return true;
10171 }
10172
10173 // Editor commands
10174 if (t.editorCommands.execCommand(cmd, ui, val)) {
10175 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10176 return true;
10177 }
10178
10179 // Browser commands
10180 t.getDoc().execCommand(cmd, ui, val);
10181 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10182 },
10183
10184 queryCommandState : function(cmd) {
10185 var t = this, o, s;
10186
10187 // Is hidden then return undefined
10188 if (t._isHidden())
10189 return;
10190
10191 // Registred commands
10192 if (o = t.queryStateCommands[cmd]) {
10193 s = o.func.call(o.scope);
10194
10195 // Fall though on true
10196 if (s !== true)
10197 return s;
10198 }
10199
10200 // Registred commands
10201 o = t.editorCommands.queryCommandState(cmd);
10202 if (o !== -1)
10203 return o;
10204
10205 // Browser commands
10206 try {
10207 return this.getDoc().queryCommandState(cmd);
10208 } catch (ex) {
10209 // Fails sometimes see bug: 1896577
10210 }
10211 },
10212
10213 queryCommandValue : function(c) {
10214 var t = this, o, s;
10215
10216 // Is hidden then return undefined
10217 if (t._isHidden())
10218 return;
10219
10220 // Registred commands
10221 if (o = t.queryValueCommands[c]) {
10222 s = o.func.call(o.scope);
10223
10224 // Fall though on true
10225 if (s !== true)
10226 return s;
10227 }
10228
10229 // Registred commands
10230 o = t.editorCommands.queryCommandValue(c);
10231 if (is(o))
10232 return o;
10233
10234 // Browser commands
10235 try {
10236 return this.getDoc().queryCommandValue(c);
10237 } catch (ex) {
10238 // Fails sometimes see bug: 1896577
10239 }
10240 },
10241
10242 show : function() {
10243 var t = this;
10244
10245 DOM.show(t.getContainer());
10246 DOM.hide(t.id);
10247 t.load();
10248 },
10249
10250 hide : function() {
10251 var t = this, d = t.getDoc();
10252
10253 // Fixed bug where IE has a blinking cursor left from the editor
10254 if (isIE && d)
10255 d.execCommand('SelectAll');
10256
10257 // We must save before we hide so Safari doesn't crash
10258 t.save();
10259 DOM.hide(t.getContainer());
10260 DOM.setStyle(t.id, 'display', t.orgDisplay);
10261 },
10262
10263 isHidden : function() {
10264 return !DOM.isHidden(this.id);
10265 },
10266
10267 setProgressState : function(b, ti, o) {
10268 this.onSetProgressState.dispatch(this, b, ti, o);
10269
10270 return b;
10271 },
10272
10273 load : function(o) {
10274 var t = this, e = t.getElement(), h;
10275
10276 if (e) {
10277 o = o || {};
10278 o.load = true;
10279
10280 // Double encode existing entities in the value
10281 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
10282 o.element = e;
10283
10284 if (!o.no_events)
10285 t.onLoadContent.dispatch(t, o);
10286
10287 o.element = e = null;
10288
10289 return h;
10290 }
10291 },
10292
10293 save : function(o) {
10294 var t = this, e = t.getElement(), h, f;
10295
10296 if (!e || !t.initialized)
10297 return;
10298
10299 o = o || {};
10300 o.save = true;
10301
10302 // Add undo level will trigger onchange event
10303 if (!o.no_events) {
10304 t.undoManager.typing = 0;
10305 t.undoManager.add();
10306 }
10307
10308 o.element = e;
10309 h = o.content = t.getContent(o);
10310
10311 if (!o.no_events)
10312 t.onSaveContent.dispatch(t, o);
10313
10314 h = o.content;
10315
10316 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
10317 e.innerHTML = h;
10318
10319 // Update hidden form element
10320 if (f = DOM.getParent(t.id, 'form')) {
10321 each(f.elements, function(e) {
10322 if (e.name == t.id) {
10323 e.value = h;
10324 return false;
10325 }
10326 });
10327 }
10328 } else
10329 e.value = h;
10330
10331 o.element = e = null;
10332
10333 return h;
10334 },
10335
10336 setContent : function(h, o) {
10337 var t = this;
10338
10339 o = o || {};
10340 o.format = o.format || 'html';
10341 o.set = true;
10342 o.content = h;
10343
10344 if (!o.no_events)
10345 t.onBeforeSetContent.dispatch(t, o);
10346
10347 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
10348 // It will also be impossible to place the caret in the editor unless there is a BR element present
10349 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
10350 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
10351 o.format = 'raw';
10352 }
10353
10354 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
10355
10356 if (o.format != 'raw' && t.settings.cleanup) {
10357 o.getInner = true;
10358 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
10359 }
10360
10361 if (!o.no_events)
10362 t.onSetContent.dispatch(t, o);
10363
10364 return o.content;
10365 },
10366
10367 getContent : function(o) {
10368 var t = this, h;
10369
10370 o = o || {};
10371 o.format = o.format || 'html';
10372 o.get = true;
10373
10374 if (!o.no_events)
10375 t.onBeforeGetContent.dispatch(t, o);
10376
10377 if (o.format != 'raw' && t.settings.cleanup) {
10378 o.getInner = true;
10379 h = t.serializer.serialize(t.getBody(), o);
10380 } else
10381 h = t.getBody().innerHTML;
10382
10383 h = h.replace(/^\s*|\s*$/g, '');
10384 o.content = h;
10385
10386 if (!o.no_events)
10387 t.onGetContent.dispatch(t, o);
10388
10389 return o.content;
10390 },
10391
10392 isDirty : function() {
10393 var t = this;
10394
10395 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
10396 },
10397
10398 getContainer : function() {
10399 var t = this;
10400
10401 if (!t.container)
10402 t.container = DOM.get(t.editorContainer || t.id + '_parent');
10403
10404 return t.container;
10405 },
10406
10407 getContentAreaContainer : function() {
10408 return this.contentAreaContainer;
10409 },
10410
10411 getElement : function() {
10412 return DOM.get(this.settings.content_element || this.id);
10413 },
10414
10415 getWin : function() {
10416 var t = this, e;
10417
10418 if (!t.contentWindow) {
10419 e = DOM.get(t.id + "_ifr");
10420
10421 if (e)
10422 t.contentWindow = e.contentWindow;
10423 }
10424
10425 return t.contentWindow;
10426 },
10427
10428 getDoc : function() {
10429 var t = this, w;
10430
10431 if (!t.contentDocument) {
10432 w = t.getWin();
10433
10434 if (w)
10435 t.contentDocument = w.document;
10436 }
10437
10438 return t.contentDocument;
10439 },
10440
10441 getBody : function() {
10442 return this.bodyElement || this.getDoc().body;
10443 },
10444
10445 convertURL : function(u, n, e) {
10446 var t = this, s = t.settings;
10447
10448 // Use callback instead
10449 if (s.urlconverter_callback)
10450 return t.execCallback('urlconverter_callback', u, e, true, n);
10451
10452 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
10453 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
10454 return u;
10455
10456 // Convert to relative
10457 if (s.relative_urls)
10458 return t.documentBaseURI.toRelative(u);
10459
10460 // Convert to absolute
10461 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
10462
10463 return u;
10464 },
10465
10466 addVisual : function(e) {
10467 var t = this, s = t.settings;
10468
10469 e = e || t.getBody();
10470
10471 if (!is(t.hasVisual))
10472 t.hasVisual = s.visual;
10473
10474 each(t.dom.select('table,a', e), function(e) {
10475 var v;
10476
10477 switch (e.nodeName) {
10478 case 'TABLE':
10479 v = t.dom.getAttrib(e, 'border');
10480
10481 if (!v || v == '0') {
10482 if (t.hasVisual)
10483 t.dom.addClass(e, s.visual_table_class);
10484 else
10485 t.dom.removeClass(e, s.visual_table_class);
10486 }
10487
10488 return;
10489
10490 case 'A':
10491 v = t.dom.getAttrib(e, 'name');
10492
10493 if (v) {
10494 if (t.hasVisual)
10495 t.dom.addClass(e, 'mceItemAnchor');
10496 else
10497 t.dom.removeClass(e, 'mceItemAnchor');
10498 }
10499
10500 return;
10501 }
10502 });
10503
10504 t.onVisualAid.dispatch(t, e, t.hasVisual);
10505 },
10506
10507 remove : function() {
10508 var t = this, e = t.getContainer();
10509
10510 t.removed = 1; // Cancels post remove event execution
10511 t.hide();
10512
10513 t.execCallback('remove_instance_callback', t);
10514 t.onRemove.dispatch(t);
10515
10516 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
10517 t.onExecCommand.listeners = [];
10518
10519 tinymce.remove(t);
10520 DOM.remove(e);
10521 },
10522
10523 destroy : function(s) {
10524 var t = this;
10525
10526 // One time is enough
10527 if (t.destroyed)
10528 return;
10529
10530 if (!s) {
10531 tinymce.removeUnload(t.destroy);
10532 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
10533
10534 // Manual destroy
10535 if (t.theme && t.theme.destroy)
10536 t.theme.destroy();
10537
10538 // Destroy controls, selection and dom
10539 t.controlManager.destroy();
10540 t.selection.destroy();
10541 t.dom.destroy();
10542
10543 // Remove all events
10544
10545 // Don't clear the window or document if content editable
10546 // is enabled since other instances might still be present
10547 if (!t.settings.content_editable) {
10548 Event.clear(t.getWin());
10549 Event.clear(t.getDoc());
10550 }
10551
10552 Event.clear(t.getBody());
10553 Event.clear(t.formElement);
10554 }
10555
10556 if (t.formElement) {
10557 t.formElement.submit = t.formElement._mceOldSubmit;
10558 t.formElement._mceOldSubmit = null;
10559 }
10560
10561 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
10562
10563 if (t.selection)
10564 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
10565
10566 t.destroyed = 1;
10567 },
10568
10569 // Internal functions
10570
10571 _addEvents : function() {
10572 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
10573 var t = this, i, s = t.settings, lo = {
10574 mouseup : 'onMouseUp',
10575 mousedown : 'onMouseDown',
10576 click : 'onClick',
10577 keyup : 'onKeyUp',
10578 keydown : 'onKeyDown',
10579 keypress : 'onKeyPress',
10580 submit : 'onSubmit',
10581 reset : 'onReset',
10582 contextmenu : 'onContextMenu',
10583 dblclick : 'onDblClick',
10584 paste : 'onPaste' // Doesn't work in all browsers yet
10585 };
10586
10587 function eventHandler(e, o) {
10588 var ty = e.type;
10589
10590 // Don't fire events when it's removed
10591 if (t.removed)
10592 return;
10593
10594 // Generic event handler
10595 if (t.onEvent.dispatch(t, e, o) !== false) {
10596 // Specific event handler
10597 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
10598 }
10599 };
10600
10601 // Add DOM events
10602 each(lo, function(v, k) {
10603 switch (k) {
10604 case 'contextmenu':
10605 if (tinymce.isOpera) {
10606 // Fake contextmenu on Opera
10607 t.dom.bind(t.getBody(), 'mousedown', function(e) {
10608 if (e.ctrlKey) {
10609 e.fakeType = 'contextmenu';
10610 eventHandler(e);
10611 }
10612 });
10613 } else
10614 t.dom.bind(t.getBody(), k, eventHandler);
10615 break;
10616
10617 case 'paste':
10618 t.dom.bind(t.getBody(), k, function(e) {
10619 eventHandler(e);
10620 });
10621 break;
10622
10623 case 'submit':
10624 case 'reset':
10625 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
10626 break;
10627
10628 default:
10629 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
10630 }
10631 });
10632
10633 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
10634 t.focus(true);
10635 });
10636
10637
10638 // Fixes bug where a specified document_base_uri could result in broken images
10639 // This will also fix drag drop of images in Gecko
10640 if (tinymce.isGecko) {
10641 // Convert all images to absolute URLs
10642 /* t.onSetContent.add(function(ed, o) {
10643 each(ed.dom.select('img'), function(e) {
10644 var v;
10645
10646 if (v = e.getAttribute('_mce_src'))
10647 e.src = t.documentBaseURI.toAbsolute(v);
10648 })
10649 });*/
10650
10651 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
10652 var v;
10653
10654 e = e.target;
10655
10656 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
10657 e.src = t.documentBaseURI.toAbsolute(v);
10658 });
10659 }
10660
10661 // Set various midas options in Gecko
10662 if (isGecko) {
10663 function setOpts() {
10664 var t = this, d = t.getDoc(), s = t.settings;
10665
10666 if (isGecko && !s.readonly) {
10667 if (t._isHidden()) {
10668 try {
10669 if (!s.content_editable)
10670 d.designMode = 'On';
10671 } catch (ex) {
10672 // Fails if it's hidden
10673 }
10674 }
10675
10676 try {
10677 // Try new Gecko method
10678 d.execCommand("styleWithCSS", 0, false);
10679 } catch (ex) {
10680 // Use old method
10681 if (!t._isHidden())
10682 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
10683 }
10684
10685 if (!s.table_inline_editing)
10686 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
10687
10688 if (!s.object_resizing)
10689 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
10690 }
10691 };
10692
10693 t.onBeforeExecCommand.add(setOpts);
10694 t.onMouseDown.add(setOpts);
10695 }
10696
10697 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
10698 // WebKit can't even do simple things like selecting an image
10699 // This also fixes so it's possible to select mceItemAnchors
10700 if (tinymce.isWebKit) {
10701 t.onClick.add(function(ed, e) {
10702 e = e.target;
10703
10704 // Needs tobe the setBaseAndExtend or it will fail to select floated images
10705 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
10706 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
10707 });
10708 }
10709
10710 // Add node change handlers
10711 t.onMouseUp.add(t.nodeChanged);
10712 //t.onClick.add(t.nodeChanged);
10713 t.onKeyUp.add(function(ed, e) {
10714 var c = e.keyCode;
10715
10716 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)
10717 t.nodeChanged();
10718 });
10719
10720 // Add reset handler
10721 t.onReset.add(function() {
10722 t.setContent(t.startContent, {format : 'raw'});
10723 });
10724
10725 // Add shortcuts
10726 if (s.custom_shortcuts) {
10727 if (s.custom_undo_redo_keyboard_shortcuts) {
10728 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
10729 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
10730 }
10731
10732 // Add default shortcuts for gecko
10733 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
10734 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
10735 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
10736
10737 // BlockFormat shortcuts keys
10738 for (i=1; i<=6; i++)
10739 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
10740
10741 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
10742 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
10743 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
10744
10745 function find(e) {
10746 var v = null;
10747
10748 if (!e.altKey && !e.ctrlKey && !e.metaKey)
10749 return v;
10750
10751 each(t.shortcuts, function(o) {
10752 if (tinymce.isMac && o.ctrl != e.metaKey)
10753 return;
10754 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
10755 return;
10756
10757 if (o.alt != e.altKey)
10758 return;
10759
10760 if (o.shift != e.shiftKey)
10761 return;
10762
10763 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
10764 v = o;
10765 return false;
10766 }
10767 });
10768
10769 return v;
10770 };
10771
10772 t.onKeyUp.add(function(ed, e) {
10773 var o = find(e);
10774
10775 if (o)
10776 return Event.cancel(e);
10777 });
10778
10779 t.onKeyPress.add(function(ed, e) {
10780 var o = find(e);
10781
10782 if (o)
10783 return Event.cancel(e);
10784 });
10785
10786 t.onKeyDown.add(function(ed, e) {
10787 var o = find(e);
10788
10789 if (o) {
10790 o.func.call(o.scope);
10791 return Event.cancel(e);
10792 }
10793 });
10794 }
10795
10796 if (tinymce.isIE) {
10797 // Fix so resize will only update the width and height attributes not the styles of an image
10798 // It will also block mceItemNoResize items
10799 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
10800 var re = t.resizeInfo, cb;
10801
10802 e = e.target;
10803
10804 // Don't do this action for non image elements
10805 if (e.nodeName !== 'IMG')
10806 return;
10807
10808 if (re)
10809 t.dom.unbind(re.node, re.ev, re.cb);
10810
10811 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
10812 ev = 'resizeend';
10813 cb = t.dom.bind(e, ev, function(e) {
10814 var v;
10815
10816 e = e.target;
10817
10818 if (v = t.dom.getStyle(e, 'width')) {
10819 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
10820 t.dom.setStyle(e, 'width', '');
10821 }
10822
10823 if (v = t.dom.getStyle(e, 'height')) {
10824 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
10825 t.dom.setStyle(e, 'height', '');
10826 }
10827 });
10828 } else {
10829 ev = 'resizestart';
10830 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
10831 }
10832
10833 re = t.resizeInfo = {
10834 node : e,
10835 ev : ev,
10836 cb : cb
10837 };
10838 });
10839
10840 t.onKeyDown.add(function(ed, e) {
10841 switch (e.keyCode) {
10842 case 8:
10843 // Fix IE control + backspace browser bug
10844 if (t.selection.getRng().item) {
10845 ed.dom.remove(t.selection.getRng().item(0));
10846 return Event.cancel(e);
10847 }
10848 }
10849 });
10850
10851 /*if (t.dom.boxModel) {
10852 t.getBody().style.height = '100%';
10853
10854 Event.add(t.getWin(), 'resize', function(e) {
10855 var docElm = t.getDoc().documentElement;
10856
10857 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
10858 });
10859 }*/
10860 }
10861
10862 if (tinymce.isOpera) {
10863 t.onClick.add(function(ed, e) {
10864 Event.prevent(e);
10865 });
10866 }
10867
10868 // Add custom undo/redo handlers
10869 if (s.custom_undo_redo) {
10870 function addUndo() {
10871 t.undoManager.typing = 0;
10872 t.undoManager.add();
10873 };
10874
10875 t.dom.bind(t.getDoc(), 'focusout', function(e) {
10876 if (!t.removed && t.undoManager.typing)
10877 addUndo();
10878 });
10879
10880 t.onKeyUp.add(function(ed, e) {
10881 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
10882 addUndo();
10883 });
10884
10885 t.onKeyDown.add(function(ed, e) {
10886 var rng, parent, bookmark;
10887
10888 // IE has a really odd bug where the DOM might include an node that doesn't have
10889 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
10890 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
10891 // after you delete contents from it. See: #3008923
10892 if (isIE && e.keyCode == 46) {
10893 rng = t.selection.getRng();
10894
10895 if (rng.parentElement) {
10896 parent = rng.parentElement();
10897
10898 // Select next word when ctrl key is used in combo with delete
10899 if (e.ctrlKey) {
10900 rng.moveEnd('word', 1);
10901 rng.select();
10902 }
10903
10904 // Delete contents
10905 t.selection.getSel().clear();
10906
10907 // Check if we are within the same parent
10908 if (rng.parentElement() == parent) {
10909 bookmark = t.selection.getBookmark();
10910
10911 try {
10912 // Update the HTML and hopefully it will remove the artifacts
10913 parent.innerHTML = parent.innerHTML;
10914 } catch (ex) {
10915 // And since it's IE it can sometimes produce an unknown runtime error
10916 }
10917
10918 // Restore the caret position
10919 t.selection.moveToBookmark(bookmark);
10920 }
10921
10922 // Block the default delete behavior since it might be broken
10923 e.preventDefault();
10924 return;
10925 }
10926 }
10927
10928 // Is caracter positon keys
10929 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
10930 if (t.undoManager.typing)
10931 addUndo();
10932
10933 return;
10934 }
10935
10936 if (!t.undoManager.typing) {
10937 t.undoManager.add();
10938 t.undoManager.typing = 1;
10939 }
10940 });
10941
10942 t.onMouseDown.add(function() {
10943 if (t.undoManager.typing)
10944 addUndo();
10945 });
10946 }
10947 },
10948
10949 _isHidden : function() {
10950 var s;
10951
10952 if (!isGecko)
10953 return 0;
10954
10955 // Weird, wheres that cursor selection?
10956 s = this.selection.getSel();
10957 return (!s || !s.rangeCount || s.rangeCount == 0);
10958 },
10959
10960 // Fix for bug #1867292
10961 _fixNesting : function(s) {
10962 var d = [], i;
10963
10964 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
10965 var e;
10966
10967 // Handle end element
10968 if (b === '/') {
10969 if (!d.length)
10970 return '';
10971
10972 if (c !== d[d.length - 1].tag) {
10973 for (i=d.length - 1; i>=0; i--) {
10974 if (d[i].tag === c) {
10975 d[i].close = 1;
10976 break;
10977 }
10978 }
10979
10980 return '';
10981 } else {
10982 d.pop();
10983
10984 if (d.length && d[d.length - 1].close) {
10985 a = a + '</' + d[d.length - 1].tag + '>';
10986 d.pop();
10987 }
10988 }
10989 } else {
10990 // Ignore these
10991 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
10992 return a;
10993
10994 // Ignore closed ones
10995 if (/\/>$/.test(a))
10996 return a;
10997
10998 d.push({tag : c}); // Push start element
10999 }
11000
11001 return a;
11002 });
11003
11004 // End all open tags
11005 for (i=d.length - 1; i>=0; i--)
11006 s += '</' + d[i].tag + '>';
11007
11008 return s;
11009 }
11010 });
11011 })(tinymce);
11012
11013 (function(tinymce) {
11014 // Added for compression purposes
11015 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
11016
11017 tinymce.EditorCommands = function(editor) {
11018 var dom = editor.dom,
11019 selection = editor.selection,
11020 commands = {state: {}, exec : {}, value : {}},
11021 settings = editor.settings,
11022 bookmark;
11023
11024 function execCommand(command, ui, value) {
11025 var func;
11026
11027 command = command.toLowerCase();
11028 if (func = commands.exec[command]) {
11029 func(command, ui, value);
11030 return TRUE;
11031 }
11032
11033 return FALSE;
11034 };
11035
11036 function queryCommandState(command) {
11037 var func;
11038
11039 command = command.toLowerCase();
11040 if (func = commands.state[command])
11041 return func(command);
11042
11043 return -1;
11044 };
11045
11046 function queryCommandValue(command) {
11047 var func;
11048
11049 command = command.toLowerCase();
11050 if (func = commands.value[command])
11051 return func(command);
11052
11053 return FALSE;
11054 };
11055
11056 function addCommands(command_list, type) {
11057 type = type || 'exec';
11058
11059 each(command_list, function(callback, command) {
11060 each(command.toLowerCase().split(','), function(command) {
11061 commands[type][command] = callback;
11062 });
11063 });
11064 };
11065
11066 // Expose public methods
11067 tinymce.extend(this, {
11068 execCommand : execCommand,
11069 queryCommandState : queryCommandState,
11070 queryCommandValue : queryCommandValue,
11071 addCommands : addCommands
11072 });
11073
11074 // Private methods
11075
11076 function execNativeCommand(command, ui, value) {
11077 if (ui === undefined)
11078 ui = FALSE;
11079
11080 if (value === undefined)
11081 value = null;
11082
11083 return editor.getDoc().execCommand(command, ui, value);
11084 };
11085
11086 function isFormatMatch(name) {
11087 return editor.formatter.match(name);
11088 };
11089
11090 function toggleFormat(name, value) {
11091 editor.formatter.toggle(name, value ? {value : value} : undefined);
11092 };
11093
11094 function storeSelection(type) {
11095 bookmark = selection.getBookmark(type);
11096 };
11097
11098 function restoreSelection() {
11099 selection.moveToBookmark(bookmark);
11100 };
11101
11102 // Add execCommand overrides
11103 addCommands({
11104 // Ignore these, added for compatibility
11105 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
11106
11107 // Add undo manager logic
11108 'mceEndUndoLevel,mceAddUndoLevel' : function() {
11109 editor.undoManager.add();
11110 },
11111
11112 'Cut,Copy,Paste' : function(command) {
11113 var doc = editor.getDoc(), failed;
11114
11115 // Try executing the native command
11116 try {
11117 execNativeCommand(command);
11118 } catch (ex) {
11119 // Command failed
11120 failed = TRUE;
11121 }
11122
11123 // Present alert message about clipboard access not being available
11124 if (failed || !doc.queryCommandSupported(command)) {
11125 if (tinymce.isGecko) {
11126 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
11127 if (state)
11128 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
11129 });
11130 } else
11131 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
11132 }
11133 },
11134
11135 // Override unlink command
11136 unlink : function(command) {
11137 if (selection.isCollapsed())
11138 selection.select(selection.getNode());
11139
11140 execNativeCommand(command);
11141 selection.collapse(FALSE);
11142 },
11143
11144 // Override justify commands to use the text formatter engine
11145 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
11146 var align = command.substring(7);
11147
11148 // Remove all other alignments first
11149 each('left,center,right,full'.split(','), function(name) {
11150 if (align != name)
11151 editor.formatter.remove('align' + name);
11152 });
11153
11154 toggleFormat('align' + align);
11155 },
11156
11157 // Override list commands to fix WebKit bug
11158 'InsertUnorderedList,InsertOrderedList' : function(command) {
11159 var listElm, listParent;
11160
11161 execNativeCommand(command);
11162
11163 // WebKit produces lists within block elements so we need to split them
11164 // we will replace the native list creation logic to custom logic later on
11165 // TODO: Remove this when the list creation logic is removed
11166 listElm = dom.getParent(selection.getNode(), 'ol,ul');
11167 if (listElm) {
11168 listParent = listElm.parentNode;
11169
11170 // If list is within a text block then split that block
11171 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
11172 storeSelection();
11173 dom.split(listParent, listElm);
11174 restoreSelection();
11175 }
11176 }
11177 },
11178
11179 // Override commands to use the text formatter engine
11180 'Bold,Italic,Underline,Strikethrough' : function(command) {
11181 toggleFormat(command);
11182 },
11183
11184 // Override commands to use the text formatter engine
11185 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
11186 toggleFormat(command, value);
11187 },
11188
11189 FontSize : function(command, ui, value) {
11190 var fontClasses, fontSizes;
11191
11192 // Convert font size 1-7 to styles
11193 if (value >= 1 && value <= 7) {
11194 fontSizes = tinymce.explode(settings.font_size_style_values);
11195 fontClasses = tinymce.explode(settings.font_size_classes);
11196
11197 if (fontClasses)
11198 value = fontClasses[value - 1] || value;
11199 else
11200 value = fontSizes[value - 1] || value;
11201 }
11202
11203 toggleFormat(command, value);
11204 },
11205
11206 RemoveFormat : function(command) {
11207 editor.formatter.remove(command);
11208 },
11209
11210 mceBlockQuote : function(command) {
11211 toggleFormat('blockquote');
11212 },
11213
11214 FormatBlock : function(command, ui, value) {
11215 return toggleFormat(value || 'p');
11216 },
11217
11218 mceCleanup : function() {
11219 var bookmark = selection.getBookmark();
11220
11221 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
11222
11223 selection.moveToBookmark(bookmark);
11224 },
11225
11226 mceRemoveNode : function(command, ui, value) {
11227 var node = value || selection.getNode();
11228
11229 // Make sure that the body node isn't removed
11230 if (node != editor.getBody()) {
11231 storeSelection();
11232 editor.dom.remove(node, TRUE);
11233 restoreSelection();
11234 }
11235 },
11236
11237 mceSelectNodeDepth : function(command, ui, value) {
11238 var counter = 0;
11239
11240 dom.getParent(selection.getNode(), function(node) {
11241 if (node.nodeType == 1 && counter++ == value) {
11242 selection.select(node);
11243 return FALSE;
11244 }
11245 }, editor.getBody());
11246 },
11247
11248 mceSelectNode : function(command, ui, value) {
11249 selection.select(value);
11250 },
11251
11252 mceInsertContent : function(command, ui, value) {
11253 selection.setContent(value);
11254 },
11255
11256 mceInsertRawHTML : function(command, ui, value) {
11257 selection.setContent('tiny_mce_marker');
11258 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
11259 },
11260
11261 mceSetContent : function(command, ui, value) {
11262 editor.setContent(value);
11263 },
11264
11265 'Indent,Outdent' : function(command) {
11266 var intentValue, indentUnit, value;
11267
11268 // Setup indent level
11269 intentValue = settings.indentation;
11270 indentUnit = /[a-z%]+$/i.exec(intentValue);
11271 intentValue = parseInt(intentValue);
11272
11273 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
11274 each(selection.getSelectedBlocks(), function(element) {
11275 if (command == 'outdent') {
11276 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
11277 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
11278 } else
11279 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
11280 });
11281 } else
11282 execNativeCommand(command);
11283 },
11284
11285 mceRepaint : function() {
11286 var bookmark;
11287
11288 if (tinymce.isGecko) {
11289 try {
11290 storeSelection(TRUE);
11291
11292 if (selection.getSel())
11293 selection.getSel().selectAllChildren(editor.getBody());
11294
11295 selection.collapse(TRUE);
11296 restoreSelection();
11297 } catch (ex) {
11298 // Ignore
11299 }
11300 }
11301 },
11302
11303 mceToggleFormat : function(command, ui, value) {
11304 editor.formatter.toggle(value);
11305 },
11306
11307 InsertHorizontalRule : function() {
11308 selection.setContent('<hr />');
11309 },
11310
11311 mceToggleVisualAid : function() {
11312 editor.hasVisual = !editor.hasVisual;
11313 editor.addVisual();
11314 },
11315
11316 mceReplaceContent : function(command, ui, value) {
11317 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
11318 },
11319
11320 mceInsertLink : function(command, ui, value) {
11321 var link = dom.getParent(selection.getNode(), 'a');
11322
11323 if (tinymce.is(value, 'string'))
11324 value = {href : value};
11325
11326 if (!link) {
11327 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
11328 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
11329 dom.setAttribs(link, value);
11330 });
11331 } else {
11332 if (value.href)
11333 dom.setAttribs(link, value);
11334 else
11335 editor.dom.remove(link, TRUE);
11336 }
11337 },
11338
11339 selectAll : function() {
11340 var root = dom.getRoot(), rng = dom.createRng();
11341
11342 rng.setStart(root, 0);
11343 rng.setEnd(root, root.childNodes.length);
11344
11345 editor.selection.setRng(rng);
11346 }
11347 });
11348
11349 // Add queryCommandState overrides
11350 addCommands({
11351 // Override justify commands
11352 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
11353 return isFormatMatch('align' + command.substring(7));
11354 },
11355
11356 'Bold,Italic,Underline,Strikethrough' : function(command) {
11357 return isFormatMatch(command);
11358 },
11359
11360 mceBlockQuote : function() {
11361 return isFormatMatch('blockquote');
11362 },
11363
11364 Outdent : function() {
11365 var node;
11366
11367 if (settings.inline_styles) {
11368 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
11369 return TRUE;
11370
11371 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
11372 return TRUE;
11373 }
11374
11375 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
11376 },
11377
11378 'InsertUnorderedList,InsertOrderedList' : function(command) {
11379 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
11380 }
11381 }, 'state');
11382
11383 // Add queryCommandValue overrides
11384 addCommands({
11385 'FontSize,FontName' : function(command) {
11386 var value = 0, parent;
11387
11388 if (parent = dom.getParent(selection.getNode(), 'span')) {
11389 if (command == 'fontsize')
11390 value = parent.style.fontSize;
11391 else
11392 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
11393 }
11394
11395 return value;
11396 }
11397 }, 'value');
11398
11399 // Add undo manager logic
11400 if (settings.custom_undo_redo) {
11401 addCommands({
11402 Undo : function() {
11403 editor.undoManager.undo();
11404 },
11405
11406 Redo : function() {
11407 editor.undoManager.redo();
11408 }
11409 });
11410 }
11411 };
11412 })(tinymce);
11413 (function(tinymce) {
11414 var Dispatcher = tinymce.util.Dispatcher;
11415
11416 tinymce.UndoManager = function(editor) {
11417 var self, index = 0, data = [];
11418
11419 function getContent() {
11420 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
11421 };
11422
11423 return self = {
11424 typing : 0,
11425
11426 onAdd : new Dispatcher(self),
11427 onUndo : new Dispatcher(self),
11428 onRedo : new Dispatcher(self),
11429
11430 add : function(level) {
11431 var i, settings = editor.settings, lastLevel;
11432
11433 level = level || {};
11434 level.content = getContent();
11435
11436 // Add undo level if needed
11437 lastLevel = data[index];
11438 if (lastLevel && lastLevel.content == level.content) {
11439 if (index > 0 || data.length == 1)
11440 return null;
11441 }
11442
11443 // Time to compress
11444 if (settings.custom_undo_redo_levels) {
11445 if (data.length > settings.custom_undo_redo_levels) {
11446 for (i = 0; i < data.length - 1; i++)
11447 data[i] = data[i + 1];
11448
11449 data.length--;
11450 index = data.length;
11451 }
11452 }
11453
11454 // Get a non intrusive normalized bookmark
11455 level.bookmark = editor.selection.getBookmark(2, true);
11456
11457 // Crop array if needed
11458 if (index < data.length - 1) {
11459 // Treat first level as initial
11460 if (index == 0)
11461 data = [];
11462 else
11463 data.length = index + 1;
11464 }
11465
11466 data.push(level);
11467 index = data.length - 1;
11468
11469 self.onAdd.dispatch(self, level);
11470 editor.isNotDirty = 0;
11471
11472 return level;
11473 },
11474
11475 undo : function() {
11476 var level, i;
11477
11478 if (self.typing) {
11479 self.add();
11480 self.typing = 0;
11481 }
11482
11483 if (index > 0) {
11484 level = data[--index];
11485
11486 editor.setContent(level.content, {format : 'raw'});
11487 editor.selection.moveToBookmark(level.bookmark);
11488
11489 self.onUndo.dispatch(self, level);
11490 }
11491
11492 return level;
11493 },
11494
11495 redo : function() {
11496 var level;
11497
11498 if (index < data.length - 1) {
11499 level = data[++index];
11500
11501 editor.setContent(level.content, {format : 'raw'});
11502 editor.selection.moveToBookmark(level.bookmark);
11503
11504 self.onRedo.dispatch(self, level);
11505 }
11506
11507 return level;
11508 },
11509
11510 clear : function() {
11511 data = [];
11512 index = self.typing = 0;
11513 },
11514
11515 hasUndo : function() {
11516 return index > 0 || self.typing;
11517 },
11518
11519 hasRedo : function() {
11520 return index < data.length - 1;
11521 }
11522 };
11523 };
11524 })(tinymce);
11525
11526 (function(tinymce) {
11527 // Shorten names
11528 var Event = tinymce.dom.Event,
11529 isIE = tinymce.isIE,
11530 isGecko = tinymce.isGecko,
11531 isOpera = tinymce.isOpera,
11532 each = tinymce.each,
11533 extend = tinymce.extend,
11534 TRUE = true,
11535 FALSE = false;
11536
11537 function cloneFormats(node) {
11538 var clone, temp, inner;
11539
11540 do {
11541 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
11542 if (clone) {
11543 temp = node.cloneNode(false);
11544 temp.appendChild(clone);
11545 clone = temp;
11546 } else {
11547 clone = inner = node.cloneNode(false);
11548 }
11549
11550 clone.removeAttribute('id');
11551 }
11552 } while (node = node.parentNode);
11553
11554 if (clone)
11555 return {wrapper : clone, inner : inner};
11556 };
11557
11558 // Checks if the selection/caret is at the end of the specified block element
11559 function isAtEnd(rng, par) {
11560 var rng2 = par.ownerDocument.createRange();
11561
11562 rng2.setStart(rng.endContainer, rng.endOffset);
11563 rng2.setEndAfter(par);
11564
11565 // 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
11566 return rng2.cloneContents().textContent.length == 0;
11567 };
11568
11569 function isEmpty(n) {
11570 n = n.innerHTML;
11571
11572 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
11573 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
11574
11575 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
11576 };
11577
11578 function splitList(selection, dom, li) {
11579 var listBlock, block;
11580
11581 if (isEmpty(li)) {
11582 listBlock = dom.getParent(li, 'ul,ol');
11583
11584 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
11585 dom.split(listBlock, li);
11586 block = dom.create('p', 0, '<br _mce_bogus="1" />');
11587 dom.replace(block, li);
11588 selection.select(block, 1);
11589 }
11590
11591 return FALSE;
11592 }
11593
11594 return TRUE;
11595 };
11596
11597 tinymce.create('tinymce.ForceBlocks', {
11598 ForceBlocks : function(ed) {
11599 var t = this, s = ed.settings, elm;
11600
11601 t.editor = ed;
11602 t.dom = ed.dom;
11603 elm = (s.forced_root_block || 'p').toLowerCase();
11604 s.element = elm.toUpperCase();
11605
11606 ed.onPreInit.add(t.setup, t);
11607
11608 t.reOpera = new RegExp('(\\u00a0|&#160;|&nbsp;)<\/' + elm + '>', 'gi');
11609 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
11610 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
11611 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>(&nbsp;|&#160;)<\\\/%p>|<%p>(&nbsp;|&#160;)<\\\/%p>'.replace(/%p/g, elm), 'gi');
11612 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
11613
11614 function padd(ed, o) {
11615 if (isOpera)
11616 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
11617
11618 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
11619
11620 if (!isIE && !isOpera && o.set) {
11621 // Use &nbsp; instead of BR in padded paragraphs
11622 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
11623 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
11624 } else
11625 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
11626 };
11627
11628 ed.onBeforeSetContent.add(padd);
11629 ed.onPostProcess.add(padd);
11630
11631 if (s.forced_root_block) {
11632 ed.onInit.add(t.forceRoots, t);
11633 ed.onSetContent.add(t.forceRoots, t);
11634 ed.onBeforeGetContent.add(t.forceRoots, t);
11635 }
11636 },
11637
11638 setup : function() {
11639 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
11640
11641 // Force root blocks when typing and when getting output
11642 if (s.forced_root_block) {
11643 ed.onBeforeExecCommand.add(t.forceRoots, t);
11644 ed.onKeyUp.add(t.forceRoots, t);
11645 ed.onPreProcess.add(t.forceRoots, t);
11646 }
11647
11648 if (s.force_br_newlines) {
11649 // Force IE to produce BRs on enter
11650 if (isIE) {
11651 ed.onKeyPress.add(function(ed, e) {
11652 var n;
11653
11654 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
11655 selection.setContent('<br id="__" /> ', {format : 'raw'});
11656 n = dom.get('__');
11657 n.removeAttribute('id');
11658 selection.select(n);
11659 selection.collapse();
11660 return Event.cancel(e);
11661 }
11662 });
11663 }
11664 }
11665
11666 if (s.force_p_newlines) {
11667 if (!isIE) {
11668 ed.onKeyPress.add(function(ed, e) {
11669 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
11670 Event.cancel(e);
11671 });
11672 } else {
11673 // Ungly hack to for IE to preserve the formatting when you press
11674 // enter at the end of a block element with formatted contents
11675 // This logic overrides the browsers default logic with
11676 // custom logic that enables us to control the output
11677 tinymce.addUnload(function() {
11678 t._previousFormats = 0; // Fix IE leak
11679 });
11680
11681 ed.onKeyPress.add(function(ed, e) {
11682 t._previousFormats = 0;
11683
11684 // Clone the current formats, this will later be applied to the new block contents
11685 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
11686 t._previousFormats = cloneFormats(ed.selection.getStart());
11687 });
11688
11689 ed.onKeyUp.add(function(ed, e) {
11690 // Let IE break the element and the wrap the new caret location in the previous formats
11691 if (e.keyCode == 13 && !e.shiftKey) {
11692 var parent = ed.selection.getStart(), fmt = t._previousFormats;
11693
11694 // Parent is an empty block
11695 if (!parent.hasChildNodes() && fmt) {
11696 parent = dom.getParent(parent, dom.isBlock);
11697
11698 if (parent && parent.nodeName != 'LI') {
11699 parent.innerHTML = '';
11700
11701 if (t._previousFormats) {
11702 parent.appendChild(fmt.wrapper);
11703 fmt.inner.innerHTML = '\uFEFF';
11704 } else
11705 parent.innerHTML = '\uFEFF';
11706
11707 selection.select(parent, 1);
11708 ed.getDoc().execCommand('Delete', false, null);
11709 t._previousFormats = 0;
11710 }
11711 }
11712 }
11713 });
11714 }
11715
11716 if (isGecko) {
11717 ed.onKeyDown.add(function(ed, e) {
11718 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
11719 t.backspaceDelete(e, e.keyCode == 8);
11720 });
11721 }
11722 }
11723
11724 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
11725 if (tinymce.isWebKit) {
11726 function insertBr(ed) {
11727 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
11728
11729 // Insert BR element
11730 rng.insertNode(br = dom.create('br'));
11731
11732 // Place caret after BR
11733 rng.setStartAfter(br);
11734 rng.setEndAfter(br);
11735 selection.setRng(rng);
11736
11737 // Could not place caret after BR then insert an nbsp entity and move the caret
11738 if (selection.getSel().focusNode == br.previousSibling) {
11739 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
11740 selection.collapse(TRUE);
11741 }
11742
11743 // Create a temporary DIV after the BR and get the position as it
11744 // seems like getPos() returns 0 for text nodes and BR elements.
11745 dom.insertAfter(div, br);
11746 divYPos = dom.getPos(div).y;
11747 dom.remove(div);
11748
11749 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
11750 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
11751 ed.getWin().scrollTo(0, divYPos);
11752 };
11753
11754 ed.onKeyPress.add(function(ed, e) {
11755 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
11756 insertBr(ed);
11757 Event.cancel(e);
11758 }
11759 });
11760 }
11761
11762 // Padd empty inline elements within block elements
11763 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em>&nbsp;</em></strong></p>
11764 ed.onPreProcess.add(function(ed, o) {
11765 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
11766 if (isEmpty(p)) {
11767 each(dom.select('span,em,strong,b,i', o.node), function(n) {
11768 if (!n.hasChildNodes()) {
11769 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
11770 return FALSE; // Break the loop one padding is enough
11771 }
11772 });
11773 }
11774 });
11775 });
11776
11777 // IE specific fixes
11778 if (isIE) {
11779 // Replaces IE:s auto generated paragraphs with the specified element name
11780 if (s.element != 'P') {
11781 ed.onKeyPress.add(function(ed, e) {
11782 t.lastElm = selection.getNode().nodeName;
11783 });
11784
11785 ed.onKeyUp.add(function(ed, e) {
11786 var bl, n = selection.getNode(), b = ed.getBody();
11787
11788 if (b.childNodes.length === 1 && n.nodeName == 'P') {
11789 n = dom.rename(n, s.element);
11790 selection.select(n);
11791 selection.collapse();
11792 ed.nodeChanged();
11793 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
11794 bl = dom.getParent(n, 'p');
11795
11796 if (bl) {
11797 dom.rename(bl, s.element);
11798 ed.nodeChanged();
11799 }
11800 }
11801 });
11802 }
11803 }
11804 },
11805
11806 find : function(n, t, s) {
11807 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
11808
11809 while (n = w.nextNode()) {
11810 c++;
11811
11812 // Index by node
11813 if (t == 0 && n == s)
11814 return c;
11815
11816 // Node by index
11817 if (t == 1 && c == s)
11818 return n;
11819 }
11820
11821 return -1;
11822 },
11823
11824 forceRoots : function(ed, e) {
11825 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;
11826 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
11827
11828 // Fix for bug #1863847
11829 //if (e && e.keyCode == 13)
11830 // return TRUE;
11831
11832 // Wrap non blocks into blocks
11833 for (i = nl.length - 1; i >= 0; i--) {
11834 nx = nl[i];
11835
11836 // Ignore internal elements
11837 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
11838 bl = null;
11839 continue;
11840 }
11841
11842 // Is text or non block element
11843 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
11844 if (!bl) {
11845 // Create new block but ignore whitespace
11846 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
11847 // Store selection
11848 if (si == -2 && r) {
11849 if (!isIE) {
11850 // If selection is element then mark it
11851 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
11852 // Save the id of the selected element
11853 eid = n.getAttribute("id");
11854 n.setAttribute("id", "__mce");
11855 } else {
11856 // If element is inside body, might not be the case in contentEdiable mode
11857 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
11858 so = r.startOffset;
11859 eo = r.endOffset;
11860 si = t.find(b, 0, r.startContainer);
11861 ei = t.find(b, 0, r.endContainer);
11862 }
11863 }
11864 } else {
11865 // Force control range into text range
11866 if (r.item) {
11867 tr = d.body.createTextRange();
11868 tr.moveToElementText(r.item(0));
11869 r = tr;
11870 }
11871
11872 tr = d.body.createTextRange();
11873 tr.moveToElementText(b);
11874 tr.collapse(1);
11875 bp = tr.move('character', c) * -1;
11876
11877 tr = r.duplicate();
11878 tr.collapse(1);
11879 sp = tr.move('character', c) * -1;
11880
11881 tr = r.duplicate();
11882 tr.collapse(0);
11883 le = (tr.move('character', c) * -1) - sp;
11884
11885 si = sp - bp;
11886 ei = le;
11887 }
11888 }
11889
11890 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
11891 // See: http://support.microsoft.com/kb/829907
11892 bl = ed.dom.create(ed.settings.forced_root_block);
11893 nx.parentNode.replaceChild(bl, nx);
11894 bl.appendChild(nx);
11895 }
11896 } else {
11897 if (bl.hasChildNodes())
11898 bl.insertBefore(nx, bl.firstChild);
11899 else
11900 bl.appendChild(nx);
11901 }
11902 } else
11903 bl = null; // Time to create new block
11904 }
11905
11906 // Restore selection
11907 if (si != -2) {
11908 if (!isIE) {
11909 bl = b.getElementsByTagName(ed.settings.element)[0];
11910 r = d.createRange();
11911
11912 // Select last location or generated block
11913 if (si != -1)
11914 r.setStart(t.find(b, 1, si), so);
11915 else
11916 r.setStart(bl, 0);
11917
11918 // Select last location or generated block
11919 if (ei != -1)
11920 r.setEnd(t.find(b, 1, ei), eo);
11921 else
11922 r.setEnd(bl, 0);
11923
11924 if (s) {
11925 s.removeAllRanges();
11926 s.addRange(r);
11927 }
11928 } else {
11929 try {
11930 r = s.createRange();
11931 r.moveToElementText(b);
11932 r.collapse(1);
11933 r.moveStart('character', si);
11934 r.moveEnd('character', ei);
11935 r.select();
11936 } catch (ex) {
11937 // Ignore
11938 }
11939 }
11940 } else if (!isIE && (n = ed.dom.get('__mce'))) {
11941 // Restore the id of the selected element
11942 if (eid)
11943 n.setAttribute('id', eid);
11944 else
11945 n.removeAttribute('id');
11946
11947 // Move caret before selected element
11948 r = d.createRange();
11949 r.setStartBefore(n);
11950 r.setEndBefore(n);
11951 se.setRng(r);
11952 }
11953 },
11954
11955 getParentBlock : function(n) {
11956 var d = this.dom;
11957
11958 return d.getParent(n, d.isBlock);
11959 },
11960
11961 insertPara : function(e) {
11962 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;
11963 var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
11964
11965 // If root blocks are forced then use Operas default behavior since it's really good
11966 // Removed due to bug: #1853816
11967 // if (se.forced_root_block && isOpera)
11968 // return TRUE;
11969
11970 // Setup before range
11971 rb = d.createRange();
11972
11973 // If is before the first block element and in body, then move it into first block element
11974 rb.setStart(s.anchorNode, s.anchorOffset);
11975 rb.collapse(TRUE);
11976
11977 // Setup after range
11978 ra = d.createRange();
11979
11980 // If is before the first block element and in body, then move it into first block element
11981 ra.setStart(s.focusNode, s.focusOffset);
11982 ra.collapse(TRUE);
11983
11984 // Setup start/end points
11985 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
11986 sn = dir ? s.anchorNode : s.focusNode;
11987 so = dir ? s.anchorOffset : s.focusOffset;
11988 en = dir ? s.focusNode : s.anchorNode;
11989 eo = dir ? s.focusOffset : s.anchorOffset;
11990
11991 // If selection is in empty table cell
11992 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
11993 if (sn.firstChild.nodeName == 'BR')
11994 dom.remove(sn.firstChild); // Remove BR
11995
11996 // Create two new block elements
11997 if (sn.childNodes.length == 0) {
11998 ed.dom.add(sn, se.element, null, '<br />');
11999 aft = ed.dom.add(sn, se.element, null, '<br />');
12000 } else {
12001 n = sn.innerHTML;
12002 sn.innerHTML = '';
12003 ed.dom.add(sn, se.element, null, n);
12004 aft = ed.dom.add(sn, se.element, null, '<br />');
12005 }
12006
12007 // Move caret into the last one
12008 r = d.createRange();
12009 r.selectNodeContents(aft);
12010 r.collapse(1);
12011 ed.selection.setRng(r);
12012
12013 return FALSE;
12014 }
12015
12016 // If the caret is in an invalid location in FF we need to move it into the first block
12017 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
12018 sn = en = sn.firstChild;
12019 so = eo = 0;
12020 rb = d.createRange();
12021 rb.setStart(sn, 0);
12022 ra = d.createRange();
12023 ra.setStart(en, 0);
12024 }
12025
12026 // Never use body as start or end node
12027 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
12028 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
12029 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
12030 en = en.nodeName == "BODY" ? en.firstChild : en;
12031
12032 // Get start and end blocks
12033 sb = t.getParentBlock(sn);
12034 eb = t.getParentBlock(en);
12035 bn = sb ? sb.nodeName : se.element; // Get block name to create
12036
12037 // Return inside list use default browser behavior
12038 if (n = t.dom.getParent(sb, 'li,pre')) {
12039 if (n.nodeName == 'LI')
12040 return splitList(ed.selection, t.dom, n);
12041
12042 return TRUE;
12043 }
12044
12045 // If caption or absolute layers then always generate new blocks within
12046 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
12047 bn = se.element;
12048 sb = null;
12049 }
12050
12051 // If caption or absolute layers then always generate new blocks within
12052 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
12053 bn = se.element;
12054 eb = null;
12055 }
12056
12057 // Use P instead
12058 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
12059 bn = se.element;
12060 sb = eb = null;
12061 }
12062
12063 // Setup new before and after blocks
12064 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
12065 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
12066
12067 // Remove id from after clone
12068 aft.removeAttribute('id');
12069
12070 // Is header and cursor is at the end, then force paragraph under
12071 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
12072 aft = ed.dom.create(se.element);
12073
12074 // Find start chop node
12075 n = sc = sn;
12076 do {
12077 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
12078 break;
12079
12080 sc = n;
12081 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
12082
12083 // Find end chop node
12084 n = ec = en;
12085 do {
12086 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
12087 break;
12088
12089 ec = n;
12090 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
12091
12092 // Place first chop part into before block element
12093 if (sc.nodeName == bn)
12094 rb.setStart(sc, 0);
12095 else
12096 rb.setStartBefore(sc);
12097
12098 rb.setEnd(sn, so);
12099 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
12100
12101 // Place secnd chop part within new block element
12102 try {
12103 ra.setEndAfter(ec);
12104 } catch(ex) {
12105 //console.debug(s.focusNode, s.focusOffset);
12106 }
12107
12108 ra.setStart(en, eo);
12109 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
12110
12111 // Create range around everything
12112 r = d.createRange();
12113 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
12114 r.setStartBefore(sc.parentNode);
12115 } else {
12116 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
12117 r.setStartBefore(rb.startContainer);
12118 else
12119 r.setStart(rb.startContainer, rb.startOffset);
12120 }
12121
12122 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
12123 r.setEndAfter(ec.parentNode);
12124 else
12125 r.setEnd(ra.endContainer, ra.endOffset);
12126
12127 // Delete and replace it with new block elements
12128 r.deleteContents();
12129
12130 if (isOpera)
12131 ed.getWin().scrollTo(0, vp.y);
12132
12133 // Never wrap blocks in blocks
12134 if (bef.firstChild && bef.firstChild.nodeName == bn)
12135 bef.innerHTML = bef.firstChild.innerHTML;
12136
12137 if (aft.firstChild && aft.firstChild.nodeName == bn)
12138 aft.innerHTML = aft.firstChild.innerHTML;
12139
12140 // Padd empty blocks
12141 if (isEmpty(bef))
12142 bef.innerHTML = '<br />';
12143
12144 function appendStyles(e, en) {
12145 var nl = [], nn, n, i;
12146
12147 e.innerHTML = '';
12148
12149 // Make clones of style elements
12150 if (se.keep_styles) {
12151 n = en;
12152 do {
12153 // We only want style specific elements
12154 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
12155 nn = n.cloneNode(FALSE);
12156 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
12157 nl.push(nn);
12158 }
12159 } while (n = n.parentNode);
12160 }
12161
12162 // Append style elements to aft
12163 if (nl.length > 0) {
12164 for (i = nl.length - 1, nn = e; i >= 0; i--)
12165 nn = nn.appendChild(nl[i]);
12166
12167 // Padd most inner style element
12168 nl[0].innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
12169 return nl[0]; // Move caret to most inner element
12170 } else
12171 e.innerHTML = isOpera ? '&nbsp;' : '<br />'; // Extra space for Opera so that the caret can move there
12172 };
12173
12174 // Fill empty afterblook with current style
12175 if (isEmpty(aft))
12176 car = appendStyles(aft, en);
12177
12178 // Opera needs this one backwards for older versions
12179 if (isOpera && parseFloat(opera.version()) < 9.5) {
12180 r.insertNode(bef);
12181 r.insertNode(aft);
12182 } else {
12183 r.insertNode(aft);
12184 r.insertNode(bef);
12185 }
12186
12187 // Normalize
12188 aft.normalize();
12189 bef.normalize();
12190
12191 function first(n) {
12192 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
12193 };
12194
12195 // Move cursor and scroll into view
12196 r = d.createRange();
12197 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
12198 r.collapse(1);
12199 s.removeAllRanges();
12200 s.addRange(r);
12201
12202 // 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
12203 y = ed.dom.getPos(aft).y;
12204 ch = aft.clientHeight;
12205
12206 // Is element within viewport
12207 if (y < vp.y || y + ch > vp.y + vp.h) {
12208 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
12209 //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
12210 }
12211
12212 return FALSE;
12213 },
12214
12215 backspaceDelete : function(e, bs) {
12216 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;
12217
12218 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
12219 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
12220 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
12221
12222 // Walk the dom backwards until we find a text node
12223 for (n = sc.lastChild; n; n = walker.prev()) {
12224 if (n.nodeType == 3) {
12225 r.setStart(n, n.nodeValue.length);
12226 r.collapse(true);
12227 se.setRng(r);
12228 return;
12229 }
12230 }
12231 }
12232
12233 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
12234 // This workaround removes the element by hand and moves the caret to the previous element
12235 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
12236 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
12237 // Find previous block element
12238 n = sc;
12239 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
12240
12241 if (n) {
12242 if (sc != b.firstChild) {
12243 // Find last text node
12244 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
12245 while (tn = w.nextNode())
12246 n = tn;
12247
12248 // Place caret at the end of last text node
12249 r = ed.getDoc().createRange();
12250 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
12251 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
12252 se.setRng(r);
12253
12254 // Remove the target container
12255 ed.dom.remove(sc);
12256 }
12257
12258 return Event.cancel(e);
12259 }
12260 }
12261 }
12262 }
12263 });
12264 })(tinymce);
12265
12266 (function(tinymce) {
12267 // Shorten names
12268 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
12269
12270 tinymce.create('tinymce.ControlManager', {
12271 ControlManager : function(ed, s) {
12272 var t = this, i;
12273
12274 s = s || {};
12275 t.editor = ed;
12276 t.controls = {};
12277 t.onAdd = new tinymce.util.Dispatcher(t);
12278 t.onPostRender = new tinymce.util.Dispatcher(t);
12279 t.prefix = s.prefix || ed.id + '_';
12280 t._cls = {};
12281
12282 t.onPostRender.add(function() {
12283 each(t.controls, function(c) {
12284 c.postRender();
12285 });
12286 });
12287 },
12288
12289 get : function(id) {
12290 return this.controls[this.prefix + id] || this.controls[id];
12291 },
12292
12293 setActive : function(id, s) {
12294 var c = null;
12295
12296 if (c = this.get(id))
12297 c.setActive(s);
12298
12299 return c;
12300 },
12301
12302 setDisabled : function(id, s) {
12303 var c = null;
12304
12305 if (c = this.get(id))
12306 c.setDisabled(s);
12307
12308 return c;
12309 },
12310
12311 add : function(c) {
12312 var t = this;
12313
12314 if (c) {
12315 t.controls[c.id] = c;
12316 t.onAdd.dispatch(c, t);
12317 }
12318
12319 return c;
12320 },
12321
12322 createControl : function(n) {
12323 var c, t = this, ed = t.editor;
12324
12325 each(ed.plugins, function(p) {
12326 if (p.createControl) {
12327 c = p.createControl(n, t);
12328
12329 if (c)
12330 return false;
12331 }
12332 });
12333
12334 switch (n) {
12335 case "|":
12336 case "separator":
12337 return t.createSeparator();
12338 }
12339
12340 if (!c && ed.buttons && (c = ed.buttons[n]))
12341 return t.createButton(n, c);
12342
12343 return t.add(c);
12344 },
12345
12346 createDropMenu : function(id, s, cc) {
12347 var t = this, ed = t.editor, c, bm, v, cls;
12348
12349 s = extend({
12350 'class' : 'mceDropDown',
12351 constrain : ed.settings.constrain_menus
12352 }, s);
12353
12354 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
12355 if (v = ed.getParam('skin_variant'))
12356 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
12357
12358 id = t.prefix + id;
12359 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
12360 c = t.controls[id] = new cls(id, s);
12361 c.onAddItem.add(function(c, o) {
12362 var s = o.settings;
12363
12364 s.title = ed.getLang(s.title, s.title);
12365
12366 if (!s.onclick) {
12367 s.onclick = function(v) {
12368 if (s.cmd)
12369 ed.execCommand(s.cmd, s.ui || false, s.value);
12370 };
12371 }
12372 });
12373
12374 ed.onRemove.add(function() {
12375 c.destroy();
12376 });
12377
12378 // Fix for bug #1897785, #1898007
12379 if (tinymce.isIE) {
12380 c.onShowMenu.add(function() {
12381 // IE 8 needs focus in order to store away a range with the current collapsed caret location
12382 ed.focus();
12383
12384 bm = ed.selection.getBookmark(1);
12385 });
12386
12387 c.onHideMenu.add(function() {
12388 if (bm) {
12389 ed.selection.moveToBookmark(bm);
12390 bm = 0;
12391 }
12392 });
12393 }
12394
12395 return t.add(c);
12396 },
12397
12398 createListBox : function(id, s, cc) {
12399 var t = this, ed = t.editor, cmd, c, cls;
12400
12401 if (t.get(id))
12402 return null;
12403
12404 s.title = ed.translate(s.title);
12405 s.scope = s.scope || ed;
12406
12407 if (!s.onselect) {
12408 s.onselect = function(v) {
12409 ed.execCommand(s.cmd, s.ui || false, v || s.value);
12410 };
12411 }
12412
12413 s = extend({
12414 title : s.title,
12415 'class' : 'mce_' + id,
12416 scope : s.scope,
12417 control_manager : t
12418 }, s);
12419
12420 id = t.prefix + id;
12421
12422 if (ed.settings.use_native_selects)
12423 c = new tinymce.ui.NativeListBox(id, s);
12424 else {
12425 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
12426 c = new cls(id, s);
12427 }
12428
12429 t.controls[id] = c;
12430
12431 // Fix focus problem in Safari
12432 if (tinymce.isWebKit) {
12433 c.onPostRender.add(function(c, n) {
12434 // Store bookmark on mousedown
12435 Event.add(n, 'mousedown', function() {
12436 ed.bookmark = ed.selection.getBookmark(1);
12437 });
12438
12439 // Restore on focus, since it might be lost
12440 Event.add(n, 'focus', function() {
12441 ed.selection.moveToBookmark(ed.bookmark);
12442 ed.bookmark = null;
12443 });
12444 });
12445 }
12446
12447 if (c.hideMenu)
12448 ed.onMouseDown.add(c.hideMenu, c);
12449
12450 return t.add(c);
12451 },
12452
12453 createButton : function(id, s, cc) {
12454 var t = this, ed = t.editor, o, c, cls;
12455
12456 if (t.get(id))
12457 return null;
12458
12459 s.title = ed.translate(s.title);
12460 s.label = ed.translate(s.label);
12461 s.scope = s.scope || ed;
12462
12463 if (!s.onclick && !s.menu_button) {
12464 s.onclick = function() {
12465 ed.execCommand(s.cmd, s.ui || false, s.value);
12466 };
12467 }
12468
12469 s = extend({
12470 title : s.title,
12471 'class' : 'mce_' + id,
12472 unavailable_prefix : ed.getLang('unavailable', ''),
12473 scope : s.scope,
12474 control_manager : t
12475 }, s);
12476
12477 id = t.prefix + id;
12478
12479 if (s.menu_button) {
12480 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
12481 c = new cls(id, s);
12482 ed.onMouseDown.add(c.hideMenu, c);
12483 } else {
12484 cls = t._cls.button || tinymce.ui.Button;
12485 c = new cls(id, s);
12486 }
12487
12488 return t.add(c);
12489 },
12490
12491 createMenuButton : function(id, s, cc) {
12492 s = s || {};
12493 s.menu_button = 1;
12494
12495 return this.createButton(id, s, cc);
12496 },
12497
12498 createSplitButton : function(id, s, cc) {
12499 var t = this, ed = t.editor, cmd, c, cls;
12500
12501 if (t.get(id))
12502 return null;
12503
12504 s.title = ed.translate(s.title);
12505 s.scope = s.scope || ed;
12506
12507 if (!s.onclick) {
12508 s.onclick = function(v) {
12509 ed.execCommand(s.cmd, s.ui || false, v || s.value);
12510 };
12511 }
12512
12513 if (!s.onselect) {
12514 s.onselect = function(v) {
12515 ed.execCommand(s.cmd, s.ui || false, v || s.value);
12516 };
12517 }
12518
12519 s = extend({
12520 title : s.title,
12521 'class' : 'mce_' + id,
12522 scope : s.scope,
12523 control_manager : t
12524 }, s);
12525
12526 id = t.prefix + id;
12527 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
12528 c = t.add(new cls(id, s));
12529 ed.onMouseDown.add(c.hideMenu, c);
12530
12531 return c;
12532 },
12533
12534 createColorSplitButton : function(id, s, cc) {
12535 var t = this, ed = t.editor, cmd, c, cls, bm;
12536
12537 if (t.get(id))
12538 return null;
12539
12540 s.title = ed.translate(s.title);
12541 s.scope = s.scope || ed;
12542
12543 if (!s.onclick) {
12544 s.onclick = function(v) {
12545 if (tinymce.isIE)
12546 bm = ed.selection.getBookmark(1);
12547
12548 ed.execCommand(s.cmd, s.ui || false, v || s.value);
12549 };
12550 }
12551
12552 if (!s.onselect) {
12553 s.onselect = function(v) {
12554 ed.execCommand(s.cmd, s.ui || false, v || s.value);
12555 };
12556 }
12557
12558 s = extend({
12559 title : s.title,
12560 'class' : 'mce_' + id,
12561 'menu_class' : ed.getParam('skin') + 'Skin',
12562 scope : s.scope,
12563 more_colors_title : ed.getLang('more_colors')
12564 }, s);
12565
12566 id = t.prefix + id;
12567 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
12568 c = new cls(id, s);
12569 ed.onMouseDown.add(c.hideMenu, c);
12570
12571 // Remove the menu element when the editor is removed
12572 ed.onRemove.add(function() {
12573 c.destroy();
12574 });
12575
12576 // Fix for bug #1897785, #1898007
12577 if (tinymce.isIE) {
12578 c.onShowMenu.add(function() {
12579 // IE 8 needs focus in order to store away a range with the current collapsed caret location
12580 ed.focus();
12581 bm = ed.selection.getBookmark(1);
12582 });
12583
12584 c.onHideMenu.add(function() {
12585 if (bm) {
12586 ed.selection.moveToBookmark(bm);
12587 bm = 0;
12588 }
12589 });
12590 }
12591
12592 return t.add(c);
12593 },
12594
12595 createToolbar : function(id, s, cc) {
12596 var c, t = this, cls;
12597
12598 id = t.prefix + id;
12599 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
12600 c = new cls(id, s);
12601
12602 if (t.get(id))
12603 return null;
12604
12605 return t.add(c);
12606 },
12607
12608 createSeparator : function(cc) {
12609 var cls = cc || this._cls.separator || tinymce.ui.Separator;
12610
12611 return new cls();
12612 },
12613
12614 setControlType : function(n, c) {
12615 return this._cls[n.toLowerCase()] = c;
12616 },
12617
12618 destroy : function() {
12619 each(this.controls, function(c) {
12620 c.destroy();
12621 });
12622
12623 this.controls = null;
12624 }
12625 });
12626 })(tinymce);
12627
12628 (function(tinymce) {
12629 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
12630
12631 tinymce.create('tinymce.WindowManager', {
12632 WindowManager : function(ed) {
12633 var t = this;
12634
12635 t.editor = ed;
12636 t.onOpen = new Dispatcher(t);
12637 t.onClose = new Dispatcher(t);
12638 t.params = {};
12639 t.features = {};
12640 },
12641
12642 open : function(s, p) {
12643 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
12644
12645 // Default some options
12646 s = s || {};
12647 p = p || {};
12648 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
12649 sh = isOpera ? vp.h : screen.height;
12650 s.name = s.name || 'mc_' + new Date().getTime();
12651 s.width = parseInt(s.width || 320);
12652 s.height = parseInt(s.height || 240);
12653 s.resizable = true;
12654 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
12655 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
12656 p.inline = false;
12657 p.mce_width = s.width;
12658 p.mce_height = s.height;
12659 p.mce_auto_focus = s.auto_focus;
12660
12661 if (mo) {
12662 if (isIE) {
12663 s.center = true;
12664 s.help = false;
12665 s.dialogWidth = s.width + 'px';
12666 s.dialogHeight = s.height + 'px';
12667 s.scroll = s.scrollbars || false;
12668 }
12669 }
12670
12671 // Build features string
12672 each(s, function(v, k) {
12673 if (tinymce.is(v, 'boolean'))
12674 v = v ? 'yes' : 'no';
12675
12676 if (!/^(name|url)$/.test(k)) {
12677 if (isIE && mo)
12678 f += (f ? ';' : '') + k + ':' + v;
12679 else
12680 f += (f ? ',' : '') + k + '=' + v;
12681 }
12682 });
12683
12684 t.features = s;
12685 t.params = p;
12686 t.onOpen.dispatch(t, s, p);
12687
12688 u = s.url || s.file;
12689 u = tinymce._addVer(u);
12690
12691 try {
12692 if (isIE && mo) {
12693 w = 1;
12694 window.showModalDialog(u, window, f);
12695 } else
12696 w = window.open(u, s.name, f);
12697 } catch (ex) {
12698 // Ignore
12699 }
12700
12701 if (!w)
12702 alert(t.editor.getLang('popup_blocked'));
12703 },
12704
12705 close : function(w) {
12706 w.close();
12707 this.onClose.dispatch(this);
12708 },
12709
12710 createInstance : function(cl, a, b, c, d, e) {
12711 var f = tinymce.resolve(cl);
12712
12713 return new f(a, b, c, d, e);
12714 },
12715
12716 confirm : function(t, cb, s, w) {
12717 w = w || window;
12718
12719 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
12720 },
12721
12722 alert : function(tx, cb, s, w) {
12723 var t = this;
12724
12725 w = w || window;
12726 w.alert(t._decode(t.editor.getLang(tx, tx)));
12727
12728 if (cb)
12729 cb.call(s || t);
12730 },
12731
12732 resizeBy : function(dw, dh, win) {
12733 win.resizeBy(dw, dh);
12734 },
12735
12736 // Internal functions
12737
12738 _decode : function(s) {
12739 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
12740 }
12741 });
12742 }(tinymce));
12743 (function(tinymce) {
12744 function CommandManager() {
12745 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
12746
12747 function add(collection, cmd, func, scope) {
12748 if (typeof(cmd) == 'string')
12749 cmd = [cmd];
12750
12751 tinymce.each(cmd, function(cmd) {
12752 collection[cmd.toLowerCase()] = {func : func, scope : scope};
12753 });
12754 };
12755
12756 tinymce.extend(this, {
12757 add : function(cmd, func, scope) {
12758 add(execCommands, cmd, func, scope);
12759 },
12760
12761 addQueryStateHandler : function(cmd, func, scope) {
12762 add(queryStateCommands, cmd, func, scope);
12763 },
12764
12765 addQueryValueHandler : function(cmd, func, scope) {
12766 add(queryValueCommands, cmd, func, scope);
12767 },
12768
12769 execCommand : function(scope, cmd, ui, value, args) {
12770 if (cmd = execCommands[cmd.toLowerCase()]) {
12771 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
12772 return true;
12773 }
12774 },
12775
12776 queryCommandValue : function() {
12777 if (cmd = queryValueCommands[cmd.toLowerCase()])
12778 return cmd.func.call(scope || cmd.scope, ui, value, args);
12779 },
12780
12781 queryCommandState : function() {
12782 if (cmd = queryStateCommands[cmd.toLowerCase()])
12783 return cmd.func.call(scope || cmd.scope, ui, value, args);
12784 }
12785 });
12786 };
12787
12788 tinymce.GlobalCommands = new CommandManager();
12789 })(tinymce);
12790 (function(tinymce) {
12791 tinymce.Formatter = function(ed) {
12792 var formats = {},
12793 each = tinymce.each,
12794 dom = ed.dom,
12795 selection = ed.selection,
12796 TreeWalker = tinymce.dom.TreeWalker,
12797 rangeUtils = new tinymce.dom.RangeUtils(dom),
12798 isValid = ed.schema.isValid,
12799 isBlock = dom.isBlock,
12800 forcedRootBlock = ed.settings.forced_root_block,
12801 nodeIndex = dom.nodeIndex,
12802 INVISIBLE_CHAR = '\uFEFF',
12803 MCE_ATTR_RE = /^(src|href|style)$/,
12804 FALSE = false,
12805 TRUE = true,
12806 undefined,
12807 pendingFormats = {apply : [], remove : []};
12808
12809 function isArray(obj) {
12810 return obj instanceof Array;
12811 };
12812
12813 function getParents(node, selector) {
12814 return dom.getParents(node, selector, dom.getRoot());
12815 };
12816
12817 function isCaretNode(node) {
12818 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
12819 };
12820
12821 // Public functions
12822
12823 function get(name) {
12824 return name ? formats[name] : formats;
12825 };
12826
12827 function register(name, format) {
12828 if (name) {
12829 if (typeof(name) !== 'string') {
12830 each(name, function(format, name) {
12831 register(name, format);
12832 });
12833 } else {
12834 // Force format into array and add it to internal collection
12835 format = format.length ? format : [format];
12836
12837 each(format, function(format) {
12838 // Set deep to false by default on selector formats this to avoid removing
12839 // alignment on images inside paragraphs when alignment is changed on paragraphs
12840 if (format.deep === undefined)
12841 format.deep = !format.selector;
12842
12843 // Default to true
12844 if (format.split === undefined)
12845 format.split = !format.selector || format.inline;
12846
12847 // Default to true
12848 if (format.remove === undefined && format.selector && !format.inline)
12849 format.remove = 'none';
12850
12851 // Mark format as a mixed format inline + block level
12852 if (format.selector && format.inline) {
12853 format.mixed = true;
12854 format.block_expand = true;
12855 }
12856
12857 // Split classes if needed
12858 if (typeof(format.classes) === 'string')
12859 format.classes = format.classes.split(/\s+/);
12860 });
12861
12862 formats[name] = format;
12863 }
12864 }
12865 };
12866
12867 function apply(name, vars, node) {
12868 var formatList = get(name), format = formatList[0], bookmark, rng, i;
12869
12870 function moveStart(rng) {
12871 var container = rng.startContainer,
12872 offset = rng.startOffset,
12873 walker, node;
12874
12875 // Move startContainer/startOffset in to a suitable node
12876 if (container.nodeType == 1 || container.nodeValue === "") {
12877 container = container.nodeType == 1 ? container.childNodes[offset] : container;
12878
12879 // Might fail if the offset is behind the last element in it's container
12880 if (container) {
12881 walker = new TreeWalker(container, container.parentNode);
12882 for (node = walker.current(); node; node = walker.next()) {
12883 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
12884 rng.setStart(node, 0);
12885 break;
12886 }
12887 }
12888 }
12889 }
12890
12891 return rng;
12892 };
12893
12894 function setElementFormat(elm, fmt) {
12895 fmt = fmt || format;
12896
12897 if (elm) {
12898 each(fmt.styles, function(value, name) {
12899 dom.setStyle(elm, name, replaceVars(value, vars));
12900 });
12901
12902 each(fmt.attributes, function(value, name) {
12903 dom.setAttrib(elm, name, replaceVars(value, vars));
12904 });
12905
12906 each(fmt.classes, function(value) {
12907 value = replaceVars(value, vars);
12908
12909 if (!dom.hasClass(elm, value))
12910 dom.addClass(elm, value);
12911 });
12912 }
12913 };
12914
12915 function applyRngStyle(rng) {
12916 var newWrappers = [], wrapName, wrapElm;
12917
12918 // Setup wrapper element
12919 wrapName = format.inline || format.block;
12920 wrapElm = dom.create(wrapName);
12921 setElementFormat(wrapElm);
12922
12923 rangeUtils.walk(rng, function(nodes) {
12924 var currentWrapElm;
12925
12926 function process(node) {
12927 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
12928
12929 // Stop wrapping on br elements
12930 if (isEq(nodeName, 'br')) {
12931 currentWrapElm = 0;
12932
12933 // Remove any br elements when we wrap things
12934 if (format.block)
12935 dom.remove(node);
12936
12937 return;
12938 }
12939
12940 // If node is wrapper type
12941 if (format.wrapper && matchNode(node, name, vars)) {
12942 currentWrapElm = 0;
12943 return;
12944 }
12945
12946 // Can we rename the block
12947 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
12948 node = dom.rename(node, wrapName);
12949 setElementFormat(node);
12950 newWrappers.push(node);
12951 currentWrapElm = 0;
12952 return;
12953 }
12954
12955 // Handle selector patterns
12956 if (format.selector) {
12957 // Look for matching formats
12958 each(formatList, function(format) {
12959 if (dom.is(node, format.selector) && !isCaretNode(node)) {
12960 setElementFormat(node, format);
12961 found = true;
12962 }
12963 });
12964
12965 // Continue processing if a selector match wasn't found and a inline element is defined
12966 if (!format.inline || found) {
12967 currentWrapElm = 0;
12968 return;
12969 }
12970 }
12971
12972 // Is it valid to wrap this item
12973 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
12974 // Start wrapping
12975 if (!currentWrapElm) {
12976 // Wrap the node
12977 currentWrapElm = wrapElm.cloneNode(FALSE);
12978 node.parentNode.insertBefore(currentWrapElm, node);
12979 newWrappers.push(currentWrapElm);
12980 }
12981
12982 currentWrapElm.appendChild(node);
12983 } else {
12984 // Start a new wrapper for possible children
12985 currentWrapElm = 0;
12986
12987 each(tinymce.grep(node.childNodes), process);
12988
12989 // End the last wrapper
12990 currentWrapElm = 0;
12991 }
12992 };
12993
12994 // Process siblings from range
12995 each(nodes, process);
12996 });
12997
12998 // Cleanup
12999 each(newWrappers, function(node) {
13000 var childCount;
13001
13002 function getChildCount(node) {
13003 var count = 0;
13004
13005 each(node.childNodes, function(node) {
13006 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
13007 count++;
13008 });
13009
13010 return count;
13011 };
13012
13013 function mergeStyles(node) {
13014 var child, clone;
13015
13016 each(node.childNodes, function(node) {
13017 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
13018 child = node;
13019 return FALSE; // break loop
13020 }
13021 });
13022
13023 // If child was found and of the same type as the current node
13024 if (child && matchName(child, format)) {
13025 clone = child.cloneNode(FALSE);
13026 setElementFormat(clone);
13027
13028 dom.replace(clone, node, TRUE);
13029 dom.remove(child, 1);
13030 }
13031
13032 return clone || node;
13033 };
13034
13035 childCount = getChildCount(node);
13036
13037 // Remove empty nodes
13038 if (childCount === 0) {
13039 dom.remove(node, 1);
13040 return;
13041 }
13042
13043 if (format.inline || format.wrapper) {
13044 // Merges the current node with it's children of similar type to reduce the number of elements
13045 if (!format.exact && childCount === 1)
13046 node = mergeStyles(node);
13047
13048 // Remove/merge children
13049 each(formatList, function(format) {
13050 // Merge all children of similar type will move styles from child to parent
13051 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
13052 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
13053 each(dom.select(format.inline, node), function(child) {
13054 removeFormat(format, vars, child, format.exact ? child : null);
13055 });
13056 });
13057
13058 // Remove child if direct parent is of same type
13059 if (matchNode(node.parentNode, name, vars)) {
13060 dom.remove(node, 1);
13061 node = 0;
13062 return TRUE;
13063 }
13064
13065 // Look for parent with similar style format
13066 if (format.merge_with_parents) {
13067 dom.getParent(node.parentNode, function(parent) {
13068 if (matchNode(parent, name, vars)) {
13069 dom.remove(node, 1);
13070 node = 0;
13071 return TRUE;
13072 }
13073 });
13074 }
13075
13076 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
13077 if (node) {
13078 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
13079 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
13080 }
13081 }
13082 });
13083 };
13084
13085 if (format) {
13086 if (node) {
13087 rng = dom.createRng();
13088
13089 rng.setStartBefore(node);
13090 rng.setEndAfter(node);
13091
13092 applyRngStyle(expandRng(rng, formatList));
13093 } else {
13094 if (!selection.isCollapsed() || !format.inline) {
13095 // Apply formatting to selection
13096 bookmark = selection.getBookmark();
13097 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
13098
13099 selection.moveToBookmark(bookmark);
13100 selection.setRng(moveStart(selection.getRng(TRUE)));
13101 ed.nodeChanged();
13102 } else
13103 performCaretAction('apply', name, vars);
13104 }
13105 }
13106 };
13107
13108 function remove(name, vars, node) {
13109 var formatList = get(name), format = formatList[0], bookmark, i, rng;
13110
13111 function moveStart(rng) {
13112 var container = rng.startContainer,
13113 offset = rng.startOffset,
13114 walker, node, nodes, tmpNode;
13115
13116 // Convert text node into index if possible
13117 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
13118 container = container.parentNode;
13119 offset = nodeIndex(container) + 1;
13120 }
13121
13122 // Move startContainer/startOffset in to a suitable node
13123 if (container.nodeType == 1) {
13124 nodes = container.childNodes;
13125 container = nodes[Math.min(offset, nodes.length - 1)];
13126 walker = new TreeWalker(container);
13127
13128 // If offset is at end of the parent node walk to the next one
13129 if (offset > nodes.length - 1)
13130 walker.next();
13131
13132 for (node = walker.current(); node; node = walker.next()) {
13133 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
13134 // IE has a "neat" feature where it moves the start node into the closest element
13135 // we can avoid this by inserting an element before it and then remove it after we set the selection
13136 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
13137 node.parentNode.insertBefore(tmpNode, node);
13138
13139 // Set selection and remove tmpNode
13140 rng.setStart(node, 0);
13141 selection.setRng(rng);
13142 dom.remove(tmpNode);
13143
13144 return;
13145 }
13146 }
13147 }
13148 };
13149
13150 // Merges the styles for each node
13151 function process(node) {
13152 var children, i, l;
13153
13154 // Grab the children first since the nodelist might be changed
13155 children = tinymce.grep(node.childNodes);
13156
13157 // Process current node
13158 for (i = 0, l = formatList.length; i < l; i++) {
13159 if (removeFormat(formatList[i], vars, node, node))
13160 break;
13161 }
13162
13163 // Process the children
13164 if (format.deep) {
13165 for (i = 0, l = children.length; i < l; i++)
13166 process(children[i]);
13167 }
13168 };
13169
13170 function findFormatRoot(container) {
13171 var formatRoot;
13172
13173 // Find format root
13174 each(getParents(container.parentNode).reverse(), function(parent) {
13175 var format;
13176
13177 // Find format root element
13178 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
13179 // Is the node matching the format we are looking for
13180 format = matchNode(parent, name, vars);
13181 if (format && format.split !== false)
13182 formatRoot = parent;
13183 }
13184 });
13185
13186 return formatRoot;
13187 };
13188
13189 function wrapAndSplit(format_root, container, target, split) {
13190 var parent, clone, lastClone, firstClone, i, formatRootParent;
13191
13192 // Format root found then clone formats and split it
13193 if (format_root) {
13194 formatRootParent = format_root.parentNode;
13195
13196 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
13197 clone = parent.cloneNode(FALSE);
13198
13199 for (i = 0; i < formatList.length; i++) {
13200 if (removeFormat(formatList[i], vars, clone, clone)) {
13201 clone = 0;
13202 break;
13203 }
13204 }
13205
13206 // Build wrapper node
13207 if (clone) {
13208 if (lastClone)
13209 clone.appendChild(lastClone);
13210
13211 if (!firstClone)
13212 firstClone = clone;
13213
13214 lastClone = clone;
13215 }
13216 }
13217
13218 // Never split block elements if the format is mixed
13219 if (split && (!format.mixed || !isBlock(format_root)))
13220 container = dom.split(format_root, container);
13221
13222 // Wrap container in cloned formats
13223 if (lastClone) {
13224 target.parentNode.insertBefore(lastClone, target);
13225 firstClone.appendChild(target);
13226 }
13227 }
13228
13229 return container;
13230 };
13231
13232 function splitToFormatRoot(container) {
13233 return wrapAndSplit(findFormatRoot(container), container, container, true);
13234 };
13235
13236 function unwrap(start) {
13237 var node = dom.get(start ? '_start' : '_end'),
13238 out = node[start ? 'firstChild' : 'lastChild'];
13239
13240 // If the end is placed within the start the result will be removed
13241 // So this checks if the out node is a bookmark node if it is it
13242 // checks for another more suitable node
13243 if (isBookmarkNode(out))
13244 out = out[start ? 'firstChild' : 'lastChild'];
13245
13246 dom.remove(node, true);
13247
13248 return out;
13249 };
13250
13251 function removeRngStyle(rng) {
13252 var startContainer, endContainer;
13253
13254 rng = expandRng(rng, formatList, TRUE);
13255
13256 if (format.split) {
13257 startContainer = getContainer(rng, TRUE);
13258 endContainer = getContainer(rng);
13259
13260 if (startContainer != endContainer) {
13261 // Wrap start/end nodes in span element since these might be cloned/moved
13262 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
13263 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
13264
13265 // Split start/end
13266 splitToFormatRoot(startContainer);
13267 splitToFormatRoot(endContainer);
13268
13269 // Unwrap start/end to get real elements again
13270 startContainer = unwrap(TRUE);
13271 endContainer = unwrap();
13272 } else
13273 startContainer = endContainer = splitToFormatRoot(startContainer);
13274
13275 // Update range positions since they might have changed after the split operations
13276 rng.startContainer = startContainer.parentNode;
13277 rng.startOffset = nodeIndex(startContainer);
13278 rng.endContainer = endContainer.parentNode;
13279 rng.endOffset = nodeIndex(endContainer) + 1;
13280 }
13281
13282 // Remove items between start/end
13283 rangeUtils.walk(rng, function(nodes) {
13284 each(nodes, function(node) {
13285 process(node);
13286 });
13287 });
13288 };
13289
13290 // Handle node
13291 if (node) {
13292 rng = dom.createRng();
13293 rng.setStartBefore(node);
13294 rng.setEndAfter(node);
13295 removeRngStyle(rng);
13296 return;
13297 }
13298
13299 if (!selection.isCollapsed() || !format.inline) {
13300 bookmark = selection.getBookmark();
13301 removeRngStyle(selection.getRng(TRUE));
13302 selection.moveToBookmark(bookmark);
13303
13304 // 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
13305 if (match(name, vars, selection.getStart())) {
13306 moveStart(selection.getRng(true));
13307 }
13308
13309 ed.nodeChanged();
13310 } else
13311 performCaretAction('remove', name, vars);
13312 };
13313
13314 function toggle(name, vars, node) {
13315 if (match(name, vars, node))
13316 remove(name, vars, node);
13317 else
13318 apply(name, vars, node);
13319 };
13320
13321 function matchNode(node, name, vars, similar) {
13322 var formatList = get(name), format, i, classes;
13323
13324 function matchItems(node, format, item_name) {
13325 var key, value, items = format[item_name], i;
13326
13327 // Check all items
13328 if (items) {
13329 // Non indexed object
13330 if (items.length === undefined) {
13331 for (key in items) {
13332 if (items.hasOwnProperty(key)) {
13333 if (item_name === 'attributes')
13334 value = dom.getAttrib(node, key);
13335 else
13336 value = getStyle(node, key);
13337
13338 if (similar && !value && !format.exact)
13339 return;
13340
13341 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
13342 return;
13343 }
13344 }
13345 } else {
13346 // Only one match needed for indexed arrays
13347 for (i = 0; i < items.length; i++) {
13348 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
13349 return format;
13350 }
13351 }
13352 }
13353
13354 return format;
13355 };
13356
13357 if (formatList && node) {
13358 // Check each format in list
13359 for (i = 0; i < formatList.length; i++) {
13360 format = formatList[i];
13361
13362 // Name name, attributes, styles and classes
13363 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
13364 // Match classes
13365 if (classes = format.classes) {
13366 for (i = 0; i < classes.length; i++) {
13367 if (!dom.hasClass(node, classes[i]))
13368 return;
13369 }
13370 }
13371
13372 return format;
13373 }
13374 }
13375 }
13376 };
13377
13378 function match(name, vars, node) {
13379 var startNode, i;
13380
13381 function matchParents(node) {
13382 // Find first node with similar format settings
13383 node = dom.getParent(node, function(node) {
13384 return !!matchNode(node, name, vars, true);
13385 });
13386
13387 // Do an exact check on the similar format element
13388 return matchNode(node, name, vars);
13389 };
13390
13391 // Check specified node
13392 if (node)
13393 return matchParents(node);
13394
13395 // Check pending formats
13396 if (selection.isCollapsed()) {
13397 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
13398 if (pendingFormats.apply[i].name == name)
13399 return true;
13400 }
13401
13402 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
13403 if (pendingFormats.remove[i].name == name)
13404 return false;
13405 }
13406
13407 return matchParents(selection.getNode());
13408 }
13409
13410 // Check selected node
13411 node = selection.getNode();
13412 if (matchParents(node))
13413 return TRUE;
13414
13415 // Check start node if it's different
13416 startNode = selection.getStart();
13417 if (startNode != node) {
13418 if (matchParents(startNode))
13419 return TRUE;
13420 }
13421
13422 return FALSE;
13423 };
13424
13425 function matchAll(names, vars) {
13426 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
13427
13428 // If the selection is collapsed then check pending formats
13429 if (selection.isCollapsed()) {
13430 for (ni = 0; ni < names.length; ni++) {
13431 // If the name is to be removed, then stop it from being added
13432 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
13433 name = names[ni];
13434
13435 if (pendingFormats.remove[i].name == name) {
13436 checkedMap[name] = true;
13437 break;
13438 }
13439 }
13440 }
13441
13442 // If the format is to be applied
13443 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
13444 for (ni = 0; ni < names.length; ni++) {
13445 name = names[ni];
13446
13447 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
13448 checkedMap[name] = true;
13449 matchedFormatNames.push(name);
13450 }
13451 }
13452 }
13453 }
13454
13455 // Check start of selection for formats
13456 startElement = selection.getStart();
13457 dom.getParent(startElement, function(node) {
13458 var i, name;
13459
13460 for (i = 0; i < names.length; i++) {
13461 name = names[i];
13462
13463 if (!checkedMap[name] && matchNode(node, name, vars)) {
13464 checkedMap[name] = true;
13465 matchedFormatNames.push(name);
13466 }
13467 }
13468 });
13469
13470 return matchedFormatNames;
13471 };
13472
13473 function canApply(name) {
13474 var formatList = get(name), startNode, parents, i, x, selector;
13475
13476 if (formatList) {
13477 startNode = selection.getStart();
13478 parents = getParents(startNode);
13479
13480 for (x = formatList.length - 1; x >= 0; x--) {
13481 selector = formatList[x].selector;
13482
13483 // Format is not selector based, then always return TRUE
13484 if (!selector)
13485 return TRUE;
13486
13487 for (i = parents.length - 1; i >= 0; i--) {
13488 if (dom.is(parents[i], selector))
13489 return TRUE;
13490 }
13491 }
13492 }
13493
13494 return FALSE;
13495 };
13496
13497 // Expose to public
13498 tinymce.extend(this, {
13499 get : get,
13500 register : register,
13501 apply : apply,
13502 remove : remove,
13503 toggle : toggle,
13504 match : match,
13505 matchAll : matchAll,
13506 matchNode : matchNode,
13507 canApply : canApply
13508 });
13509
13510 // Private functions
13511
13512 function matchName(node, format) {
13513 // Check for inline match
13514 if (isEq(node, format.inline))
13515 return TRUE;
13516
13517 // Check for block match
13518 if (isEq(node, format.block))
13519 return TRUE;
13520
13521 // Check for selector match
13522 if (format.selector)
13523 return dom.is(node, format.selector);
13524 };
13525
13526 function isEq(str1, str2) {
13527 str1 = str1 || '';
13528 str2 = str2 || '';
13529
13530 str1 = '' + (str1.nodeName || str1);
13531 str2 = '' + (str2.nodeName || str2);
13532
13533 return str1.toLowerCase() == str2.toLowerCase();
13534 };
13535
13536 function getStyle(node, name) {
13537 var styleVal = dom.getStyle(node, name);
13538
13539 // Force the format to hex
13540 if (name == 'color' || name == 'backgroundColor')
13541 styleVal = dom.toHex(styleVal);
13542
13543 // Opera will return bold as 700
13544 if (name == 'fontWeight' && styleVal == 700)
13545 styleVal = 'bold';
13546
13547 return '' + styleVal;
13548 };
13549
13550 function replaceVars(value, vars) {
13551 if (typeof(value) != "string")
13552 value = value(vars);
13553 else if (vars) {
13554 value = value.replace(/%(\w+)/g, function(str, name) {
13555 return vars[name] || str;
13556 });
13557 }
13558
13559 return value;
13560 };
13561
13562 function isWhiteSpaceNode(node) {
13563 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
13564 };
13565
13566 function wrap(node, name, attrs) {
13567 var wrapper = dom.create(name, attrs);
13568
13569 node.parentNode.insertBefore(wrapper, node);
13570 wrapper.appendChild(node);
13571
13572 return wrapper;
13573 };
13574
13575 function expandRng(rng, format, remove) {
13576 var startContainer = rng.startContainer,
13577 startOffset = rng.startOffset,
13578 endContainer = rng.endContainer,
13579 endOffset = rng.endOffset, sibling, lastIdx;
13580
13581 // This function walks up the tree if there is no siblings before/after the node
13582 function findParentContainer(container, child_name, sibling_name, root) {
13583 var parent, child;
13584
13585 root = root || dom.getRoot();
13586
13587 for (;;) {
13588 // Check if we can move up are we at root level or body level
13589 parent = container.parentNode;
13590
13591 // Stop expanding on block elements or root depending on format
13592 if (parent == root || (!format[0].block_expand && isBlock(parent)))
13593 return container;
13594
13595 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
13596 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
13597 return container;
13598
13599 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
13600 return container;
13601 }
13602
13603 container = container.parentNode;
13604 }
13605
13606 return container;
13607 };
13608
13609 // If index based start position then resolve it
13610 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
13611 lastIdx = startContainer.childNodes.length - 1;
13612 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
13613
13614 if (startContainer.nodeType == 3)
13615 startOffset = 0;
13616 }
13617
13618 // If index based end position then resolve it
13619 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
13620 lastIdx = endContainer.childNodes.length - 1;
13621 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
13622
13623 if (endContainer.nodeType == 3)
13624 endOffset = endContainer.nodeValue.length;
13625 }
13626
13627 // Exclude bookmark nodes if possible
13628 if (isBookmarkNode(startContainer.parentNode))
13629 startContainer = startContainer.parentNode;
13630
13631 if (isBookmarkNode(startContainer))
13632 startContainer = startContainer.nextSibling || startContainer;
13633
13634 if (isBookmarkNode(endContainer.parentNode))
13635 endContainer = endContainer.parentNode;
13636
13637 if (isBookmarkNode(endContainer))
13638 endContainer = endContainer.previousSibling || endContainer;
13639
13640 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
13641 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
13642 // This will reduce the number of wrapper elements that needs to be created
13643 // Move start point up the tree
13644 if (format[0].inline || format[0].block_expand) {
13645 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
13646 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
13647 }
13648
13649 // Expand start/end container to matching selector
13650 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
13651 function findSelectorEndPoint(container, sibling_name) {
13652 var parents, i, y;
13653
13654 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
13655 container = container[sibling_name];
13656
13657 parents = getParents(container);
13658 for (i = 0; i < parents.length; i++) {
13659 for (y = 0; y < format.length; y++) {
13660 if (dom.is(parents[i], format[y].selector))
13661 return parents[i];
13662 }
13663 }
13664
13665 return container;
13666 };
13667
13668 // Find new startContainer/endContainer if there is better one
13669 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
13670 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
13671 }
13672
13673 // Expand start/end container to matching block element or text node
13674 if (format[0].block || format[0].selector) {
13675 function findBlockEndPoint(container, sibling_name, sibling_name2) {
13676 var node;
13677
13678 // Expand to block of similar type
13679 if (!format[0].wrapper)
13680 node = dom.getParent(container, format[0].block);
13681
13682 // Expand to first wrappable block element or any block element
13683 if (!node)
13684 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
13685
13686 // Exclude inner lists from wrapping
13687 if (node && format[0].wrapper)
13688 node = getParents(node, 'ul,ol').reverse()[0] || node;
13689
13690 // Didn't find a block element look for first/last wrappable element
13691 if (!node) {
13692 node = container;
13693
13694 while (node[sibling_name] && !isBlock(node[sibling_name])) {
13695 node = node[sibling_name];
13696
13697 // Break on BR but include it will be removed later on
13698 // we can't remove it now since we need to check if it can be wrapped
13699 if (isEq(node, 'br'))
13700 break;
13701 }
13702 }
13703
13704 return node || container;
13705 };
13706
13707 // Find new startContainer/endContainer if there is better one
13708 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
13709 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
13710
13711 // Non block element then try to expand up the leaf
13712 if (format[0].block) {
13713 if (!isBlock(startContainer))
13714 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
13715
13716 if (!isBlock(endContainer))
13717 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
13718 }
13719 }
13720
13721 // Setup index for startContainer
13722 if (startContainer.nodeType == 1) {
13723 startOffset = nodeIndex(startContainer);
13724 startContainer = startContainer.parentNode;
13725 }
13726
13727 // Setup index for endContainer
13728 if (endContainer.nodeType == 1) {
13729 endOffset = nodeIndex(endContainer) + 1;
13730 endContainer = endContainer.parentNode;
13731 }
13732
13733 // Return new range like object
13734 return {
13735 startContainer : startContainer,
13736 startOffset : startOffset,
13737 endContainer : endContainer,
13738 endOffset : endOffset
13739 };
13740 }
13741
13742 function removeFormat(format, vars, node, compare_node) {
13743 var i, attrs, stylesModified;
13744
13745 // Check if node matches format
13746 if (!matchName(node, format))
13747 return FALSE;
13748
13749 // Should we compare with format attribs and styles
13750 if (format.remove != 'all') {
13751 // Remove styles
13752 each(format.styles, function(value, name) {
13753 value = replaceVars(value, vars);
13754
13755 // Indexed array
13756 if (typeof(name) === 'number') {
13757 name = value;
13758 compare_node = 0;
13759 }
13760
13761 if (!compare_node || isEq(getStyle(compare_node, name), value))
13762 dom.setStyle(node, name, '');
13763
13764 stylesModified = 1;
13765 });
13766
13767 // Remove style attribute if it's empty
13768 if (stylesModified && dom.getAttrib(node, 'style') == '') {
13769 node.removeAttribute('style');
13770 node.removeAttribute('_mce_style');
13771 }
13772
13773 // Remove attributes
13774 each(format.attributes, function(value, name) {
13775 var valueOut;
13776
13777 value = replaceVars(value, vars);
13778
13779 // Indexed array
13780 if (typeof(name) === 'number') {
13781 name = value;
13782 compare_node = 0;
13783 }
13784
13785 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
13786 // Keep internal classes
13787 if (name == 'class') {
13788 value = dom.getAttrib(node, name);
13789 if (value) {
13790 // Build new class value where everything is removed except the internal prefixed classes
13791 valueOut = '';
13792 each(value.split(/\s+/), function(cls) {
13793 if (/mce\w+/.test(cls))
13794 valueOut += (valueOut ? ' ' : '') + cls;
13795 });
13796
13797 // We got some internal classes left
13798 if (valueOut) {
13799 dom.setAttrib(node, name, valueOut);
13800 return;
13801 }
13802 }
13803 }
13804
13805 // IE6 has a bug where the attribute doesn't get removed correctly
13806 if (name == "class")
13807 node.removeAttribute('className');
13808
13809 // Remove mce prefixed attributes
13810 if (MCE_ATTR_RE.test(name))
13811 node.removeAttribute('_mce_' + name);
13812
13813 node.removeAttribute(name);
13814 }
13815 });
13816
13817 // Remove classes
13818 each(format.classes, function(value) {
13819 value = replaceVars(value, vars);
13820
13821 if (!compare_node || dom.hasClass(compare_node, value))
13822 dom.removeClass(node, value);
13823 });
13824
13825 // Check for non internal attributes
13826 attrs = dom.getAttribs(node);
13827 for (i = 0; i < attrs.length; i++) {
13828 if (attrs[i].nodeName.indexOf('_') !== 0)
13829 return FALSE;
13830 }
13831 }
13832
13833 // Remove the inline child if it's empty for example <b> or <span>
13834 if (format.remove != 'none') {
13835 removeNode(node, format);
13836 return TRUE;
13837 }
13838 };
13839
13840 function removeNode(node, format) {
13841 var parentNode = node.parentNode, rootBlockElm;
13842
13843 if (format.block) {
13844 if (!forcedRootBlock) {
13845 function find(node, next, inc) {
13846 node = getNonWhiteSpaceSibling(node, next, inc);
13847
13848 return !node || (node.nodeName == 'BR' || isBlock(node));
13849 };
13850
13851 // Append BR elements if needed before we remove the block
13852 if (isBlock(node) && !isBlock(parentNode)) {
13853 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
13854 node.insertBefore(dom.create('br'), node.firstChild);
13855
13856 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
13857 node.appendChild(dom.create('br'));
13858 }
13859 } else {
13860 // Wrap the block in a forcedRootBlock if we are at the root of document
13861 if (parentNode == dom.getRoot()) {
13862 if (!format.list_block || !isEq(node, format.list_block)) {
13863 each(tinymce.grep(node.childNodes), function(node) {
13864 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
13865 if (!rootBlockElm)
13866 rootBlockElm = wrap(node, forcedRootBlock);
13867 else
13868 rootBlockElm.appendChild(node);
13869 } else
13870 rootBlockElm = 0;
13871 });
13872 }
13873 }
13874 }
13875 }
13876
13877 // Never remove nodes that isn't the specified inline element if a selector is specified too
13878 if (format.selector && format.inline && !isEq(format.inline, node))
13879 return;
13880
13881 dom.remove(node, 1);
13882 };
13883
13884 function getNonWhiteSpaceSibling(node, next, inc) {
13885 if (node) {
13886 next = next ? 'nextSibling' : 'previousSibling';
13887
13888 for (node = inc ? node : node[next]; node; node = node[next]) {
13889 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
13890 return node;
13891 }
13892 }
13893 };
13894
13895 function isBookmarkNode(node) {
13896 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
13897 };
13898
13899 function mergeSiblings(prev, next) {
13900 var marker, sibling, tmpSibling;
13901
13902 function compareElements(node1, node2) {
13903 // Not the same name
13904 if (node1.nodeName != node2.nodeName)
13905 return FALSE;
13906
13907 function getAttribs(node) {
13908 var attribs = {};
13909
13910 each(dom.getAttribs(node), function(attr) {
13911 var name = attr.nodeName.toLowerCase();
13912
13913 // Don't compare internal attributes or style
13914 if (name.indexOf('_') !== 0 && name !== 'style')
13915 attribs[name] = dom.getAttrib(node, name);
13916 });
13917
13918 return attribs;
13919 };
13920
13921 function compareObjects(obj1, obj2) {
13922 var value, name;
13923
13924 for (name in obj1) {
13925 // Obj1 has item obj2 doesn't have
13926 if (obj1.hasOwnProperty(name)) {
13927 value = obj2[name];
13928
13929 // Obj2 doesn't have obj1 item
13930 if (value === undefined)
13931 return FALSE;
13932
13933 // Obj2 item has a different value
13934 if (obj1[name] != value)
13935 return FALSE;
13936
13937 // Delete similar value
13938 delete obj2[name];
13939 }
13940 }
13941
13942 // Check if obj 2 has something obj 1 doesn't have
13943 for (name in obj2) {
13944 // Obj2 has item obj1 doesn't have
13945 if (obj2.hasOwnProperty(name))
13946 return FALSE;
13947 }
13948
13949 return TRUE;
13950 };
13951
13952 // Attribs are not the same
13953 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
13954 return FALSE;
13955
13956 // Styles are not the same
13957 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
13958 return FALSE;
13959
13960 return TRUE;
13961 };
13962
13963 // Check if next/prev exists and that they are elements
13964 if (prev && next) {
13965 function findElementSibling(node, sibling_name) {
13966 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
13967 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
13968 return node;
13969
13970 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
13971 return sibling;
13972 }
13973
13974 return node;
13975 };
13976
13977 // If previous sibling is empty then jump over it
13978 prev = findElementSibling(prev, 'previousSibling');
13979 next = findElementSibling(next, 'nextSibling');
13980
13981 // Compare next and previous nodes
13982 if (compareElements(prev, next)) {
13983 // Append nodes between
13984 for (sibling = prev.nextSibling; sibling && sibling != next;) {
13985 tmpSibling = sibling;
13986 sibling = sibling.nextSibling;
13987 prev.appendChild(tmpSibling);
13988 }
13989
13990 // Remove next node
13991 dom.remove(next);
13992
13993 // Move children into prev node
13994 each(tinymce.grep(next.childNodes), function(node) {
13995 prev.appendChild(node);
13996 });
13997
13998 return prev;
13999 }
14000 }
14001
14002 return next;
14003 };
14004
14005 function isTextBlock(name) {
14006 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
14007 };
14008
14009 function getContainer(rng, start) {
14010 var container, offset, lastIdx;
14011
14012 container = rng[start ? 'startContainer' : 'endContainer'];
14013 offset = rng[start ? 'startOffset' : 'endOffset'];
14014
14015 if (container.nodeType == 1) {
14016 lastIdx = container.childNodes.length - 1;
14017
14018 if (!start && offset)
14019 offset--;
14020
14021 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
14022 }
14023
14024 return container;
14025 };
14026
14027 function performCaretAction(type, name, vars) {
14028 var i, currentPendingFormats = pendingFormats[type],
14029 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
14030
14031 function hasPending() {
14032 return pendingFormats.apply.length || pendingFormats.remove.length;
14033 };
14034
14035 function resetPending() {
14036 pendingFormats.apply = [];
14037 pendingFormats.remove = [];
14038 };
14039
14040 function perform(caret_node) {
14041 // Apply pending formats
14042 each(pendingFormats.apply.reverse(), function(item) {
14043 apply(item.name, item.vars, caret_node);
14044 });
14045
14046 // Remove pending formats
14047 each(pendingFormats.remove.reverse(), function(item) {
14048 remove(item.name, item.vars, caret_node);
14049 });
14050
14051 dom.remove(caret_node, 1);
14052 resetPending();
14053 };
14054
14055 // Check if it already exists then ignore it
14056 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
14057 if (currentPendingFormats[i].name == name)
14058 return;
14059 }
14060
14061 currentPendingFormats.push({name : name, vars : vars});
14062
14063 // Check if it's in the other type, then remove it
14064 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
14065 if (otherPendingFormats[i].name == name)
14066 otherPendingFormats.splice(i, 1);
14067 }
14068
14069 // Pending apply or remove formats
14070 if (hasPending()) {
14071 ed.getDoc().execCommand('FontName', false, 'mceinline');
14072 pendingFormats.lastRng = selection.getRng();
14073
14074 // IE will convert the current word
14075 each(dom.select('font,span'), function(node) {
14076 var bookmark;
14077
14078 if (isCaretNode(node)) {
14079 bookmark = selection.getBookmark();
14080 perform(node);
14081 selection.moveToBookmark(bookmark);
14082 ed.nodeChanged();
14083 }
14084 });
14085
14086 // Only register listeners once if we need to
14087 if (!pendingFormats.isListening && hasPending()) {
14088 pendingFormats.isListening = true;
14089
14090 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
14091 ed[event].addToTop(function(ed, e) {
14092 // Do we have pending formats and is the selection moved has moved
14093 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
14094 each(dom.select('font,span'), function(node) {
14095 var textNode, rng;
14096
14097 // Look for marker
14098 if (isCaretNode(node)) {
14099 textNode = node.firstChild;
14100
14101 if (textNode) {
14102 perform(node);
14103
14104 rng = dom.createRng();
14105 rng.setStart(textNode, textNode.nodeValue.length);
14106 rng.setEnd(textNode, textNode.nodeValue.length);
14107 selection.setRng(rng);
14108 ed.nodeChanged();
14109 } else
14110 dom.remove(node);
14111 }
14112 });
14113
14114 // Always unbind and clear pending styles on keyup
14115 if (e.type == 'keyup' || e.type == 'mouseup')
14116 resetPending();
14117 }
14118 });
14119 });
14120 }
14121 }
14122 };
14123 };
14124 })(tinymce);
14125
14126 tinymce.onAddEditor.add(function(tinymce, ed) {
14127 var filters, fontSizes, dom, settings = ed.settings;
14128
14129 if (settings.inline_styles) {
14130 fontSizes = tinymce.explode(settings.font_size_style_values);
14131
14132 function replaceWithSpan(node, styles) {
14133 tinymce.each(styles, function(value, name) {
14134 if (value)
14135 dom.setStyle(node, name, value);
14136 });
14137
14138 dom.rename(node, 'span');
14139 };
14140
14141 filters = {
14142 font : function(dom, node) {
14143 replaceWithSpan(node, {
14144 backgroundColor : node.style.backgroundColor,
14145 color : node.color,
14146 fontFamily : node.face,
14147 fontSize : fontSizes[parseInt(node.size) - 1]
14148 });
14149 },
14150
14151 u : function(dom, node) {
14152 replaceWithSpan(node, {
14153 textDecoration : 'underline'
14154 });
14155 },
14156
14157 strike : function(dom, node) {
14158 replaceWithSpan(node, {
14159 textDecoration : 'line-through'
14160 });
14161 }
14162 };
14163
14164 function convert(editor, params) {
14165 dom = editor.dom;
14166
14167 if (settings.convert_fonts_to_spans) {
14168 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
14169 filters[node.nodeName.toLowerCase()](ed.dom, node);
14170 });
14171 }
14172 };
14173
14174 ed.onPreProcess.add(convert);
14175
14176 ed.onInit.add(function() {
14177 ed.selection.onSetContent.add(convert);
14178 });
14179 }
14180 });
14181