comparison static/js/tinymce/jscripts/tiny_mce/tiny_mce_src.js @ 45:966cde8635c0

For issue #3, created a separate news app. Created import management command. Using TinyMCE editor in the admin. News model now stores HTML. The news page is now paginated. Each story has a perma-link now.
author Brian Neal <bgneal@gmail.com>
date Thu, 15 Mar 2012 20:02:39 -0500
parents
children
comparison
equal deleted inserted replaced
44:42a6bde9913c 45:966cde8635c0
1 (function(win) {
2 var whiteSpaceRe = /^\s*|\s*$/g,
3 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
4
5 var tinymce = {
6 majorVersion : '3',
7
8 minorVersion : '5b1',
9
10 releaseDate : '2012-03-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.isIE7 = t.isIE && /MSIE [7]/.test(ua);
24
25 t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
26
27 t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
28
29 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
30
31 t.isMac = ua.indexOf('Mac') != -1;
32
33 t.isAir = /adobeair/i.test(ua);
34
35 t.isIDevice = /(iPad|iPhone)/.test(ua);
36
37 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
38
39 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
40 if (win.tinyMCEPreInit) {
41 t.suffix = tinyMCEPreInit.suffix;
42 t.baseURL = tinyMCEPreInit.base;
43 t.query = tinyMCEPreInit.query;
44 return;
45 }
46
47 // Get suffix and base
48 t.suffix = '';
49
50 // If base element found, add that infront of baseURL
51 nl = d.getElementsByTagName('base');
52 for (i=0; i<nl.length; i++) {
53 if (v = nl[i].href) {
54 // Host only value like http://site.com or http://site.com:8008
55 if (/^https?:\/\/[^\/]+$/.test(v))
56 v += '/';
57
58 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
59 }
60 }
61
62 function getBase(n) {
63 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
64 if (/_(src|dev)\.js/g.test(n.src))
65 t.suffix = '_src';
66
67 if ((p = n.src.indexOf('?')) != -1)
68 t.query = n.src.substring(p + 1);
69
70 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
71
72 // If path to script is relative and a base href was found add that one infront
73 // the src property will always be an absolute one on non IE browsers and IE 8
74 // so this logic will basically only be executed on older IE versions
75 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
76 t.baseURL = base + t.baseURL;
77
78 return t.baseURL;
79 }
80
81 return null;
82 };
83
84 // Check document
85 nl = d.getElementsByTagName('script');
86 for (i=0; i<nl.length; i++) {
87 if (getBase(nl[i]))
88 return;
89 }
90
91 // Check head
92 n = d.getElementsByTagName('head')[0];
93 if (n) {
94 nl = n.getElementsByTagName('script');
95 for (i=0; i<nl.length; i++) {
96 if (getBase(nl[i]))
97 return;
98 }
99 }
100
101 return;
102 },
103
104 is : function(o, t) {
105 if (!t)
106 return o !== undefined;
107
108 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
109 return true;
110
111 return typeof(o) == t;
112 },
113
114 makeMap : function(items, delim, map) {
115 var i;
116
117 items = items || [];
118 delim = delim || ',';
119
120 if (typeof(items) == "string")
121 items = items.split(delim);
122
123 map = map || {};
124
125 i = items.length;
126 while (i--)
127 map[items[i]] = {};
128
129 return map;
130 },
131
132 each : function(o, cb, s) {
133 var n, l;
134
135 if (!o)
136 return 0;
137
138 s = s || o;
139
140 if (o.length !== undefined) {
141 // Indexed arrays, needed for Safari
142 for (n=0, l = o.length; n < l; n++) {
143 if (cb.call(s, o[n], n, o) === false)
144 return 0;
145 }
146 } else {
147 // Hashtables
148 for (n in o) {
149 if (o.hasOwnProperty(n)) {
150 if (cb.call(s, o[n], n, o) === false)
151 return 0;
152 }
153 }
154 }
155
156 return 1;
157 },
158
159
160 map : function(a, f) {
161 var o = [];
162
163 tinymce.each(a, function(v) {
164 o.push(f(v));
165 });
166
167 return o;
168 },
169
170 grep : function(a, f) {
171 var o = [];
172
173 tinymce.each(a, function(v) {
174 if (!f || f(v))
175 o.push(v);
176 });
177
178 return o;
179 },
180
181 inArray : function(a, v) {
182 var i, l;
183
184 if (a) {
185 for (i = 0, l = a.length; i < l; i++) {
186 if (a[i] === v)
187 return i;
188 }
189 }
190
191 return -1;
192 },
193
194 extend : function(o, e) {
195 var i, l, a = arguments;
196
197 for (i = 1, l = a.length; i < l; i++) {
198 e = a[i];
199
200 tinymce.each(e, function(v, n) {
201 if (v !== undefined)
202 o[n] = v;
203 });
204 }
205
206 return o;
207 },
208
209
210 trim : function(s) {
211 return (s ? '' + s : '').replace(whiteSpaceRe, '');
212 },
213
214 create : function(s, p, root) {
215 var t = this, sp, ns, cn, scn, c, de = 0;
216
217 // Parse : <prefix> <class>:<super class>
218 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
219 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
220
221 // Create namespace for new class
222 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
223
224 // Class already exists
225 if (ns[cn])
226 return;
227
228 // Make pure static class
229 if (s[2] == 'static') {
230 ns[cn] = p;
231
232 if (this.onCreate)
233 this.onCreate(s[2], s[3], ns[cn]);
234
235 return;
236 }
237
238 // Create default constructor
239 if (!p[cn]) {
240 p[cn] = function() {};
241 de = 1;
242 }
243
244 // Add constructor and methods
245 ns[cn] = p[cn];
246 t.extend(ns[cn].prototype, p);
247
248 // Extend
249 if (s[5]) {
250 sp = t.resolve(s[5]).prototype;
251 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
252
253 // Extend constructor
254 c = ns[cn];
255 if (de) {
256 // Add passthrough constructor
257 ns[cn] = function() {
258 return sp[scn].apply(this, arguments);
259 };
260 } else {
261 // Add inherit constructor
262 ns[cn] = function() {
263 this.parent = sp[scn];
264 return c.apply(this, arguments);
265 };
266 }
267 ns[cn].prototype[cn] = ns[cn];
268
269 // Add super methods
270 t.each(sp, function(f, n) {
271 ns[cn].prototype[n] = sp[n];
272 });
273
274 // Add overridden methods
275 t.each(p, function(f, n) {
276 // Extend methods if needed
277 if (sp[n]) {
278 ns[cn].prototype[n] = function() {
279 this.parent = sp[n];
280 return f.apply(this, arguments);
281 };
282 } else {
283 if (n != cn)
284 ns[cn].prototype[n] = f;
285 }
286 });
287 }
288
289 // Add static methods
290 t.each(p['static'], function(f, n) {
291 ns[cn][n] = f;
292 });
293
294 if (this.onCreate)
295 this.onCreate(s[2], s[3], ns[cn].prototype);
296 },
297
298 walk : function(o, f, n, s) {
299 s = s || this;
300
301 if (o) {
302 if (n)
303 o = o[n];
304
305 tinymce.each(o, function(o, i) {
306 if (f.call(s, o, i, n) === false)
307 return false;
308
309 tinymce.walk(o, f, n, s);
310 });
311 }
312 },
313
314 createNS : function(n, o) {
315 var i, v;
316
317 o = o || win;
318
319 n = n.split('.');
320 for (i=0; i<n.length; i++) {
321 v = n[i];
322
323 if (!o[v])
324 o[v] = {};
325
326 o = o[v];
327 }
328
329 return o;
330 },
331
332 resolve : function(n, o) {
333 var i, l;
334
335 o = o || win;
336
337 n = n.split('.');
338 for (i = 0, l = n.length; i < l; i++) {
339 o = o[n[i]];
340
341 if (!o)
342 break;
343 }
344
345 return o;
346 },
347
348 addUnload : function(f, s) {
349 var t = this;
350
351 f = {func : f, scope : s || this};
352
353 if (!t.unloads) {
354 function unload() {
355 var li = t.unloads, o, n;
356
357 if (li) {
358 // Call unload handlers
359 for (n in li) {
360 o = li[n];
361
362 if (o && o.func)
363 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
364 }
365
366 // Detach unload function
367 if (win.detachEvent) {
368 win.detachEvent('onbeforeunload', fakeUnload);
369 win.detachEvent('onunload', unload);
370 } else if (win.removeEventListener)
371 win.removeEventListener('unload', unload, false);
372
373 // Destroy references
374 t.unloads = o = li = w = unload = 0;
375
376 // Run garbarge collector on IE
377 if (win.CollectGarbage)
378 CollectGarbage();
379 }
380 };
381
382 function fakeUnload() {
383 var d = document;
384
385 // Is there things still loading, then do some magic
386 if (d.readyState == 'interactive') {
387 function stop() {
388 // Prevent memory leak
389 d.detachEvent('onstop', stop);
390
391 // Call unload handler
392 if (unload)
393 unload();
394
395 d = 0;
396 };
397
398 // Fire unload when the currently loading page is stopped
399 if (d)
400 d.attachEvent('onstop', stop);
401
402 // Remove onstop listener after a while to prevent the unload function
403 // to execute if the user presses cancel in an onbeforeunload
404 // confirm dialog and then presses the browser stop button
405 win.setTimeout(function() {
406 if (d)
407 d.detachEvent('onstop', stop);
408 }, 0);
409 }
410 };
411
412 // Attach unload handler
413 if (win.attachEvent) {
414 win.attachEvent('onunload', unload);
415 win.attachEvent('onbeforeunload', fakeUnload);
416 } else if (win.addEventListener)
417 win.addEventListener('unload', unload, false);
418
419 // Setup initial unload handler array
420 t.unloads = [f];
421 } else
422 t.unloads.push(f);
423
424 return f;
425 },
426
427 removeUnload : function(f) {
428 var u = this.unloads, r = null;
429
430 tinymce.each(u, function(o, i) {
431 if (o && o.func == f) {
432 u.splice(i, 1);
433 r = f;
434 return false;
435 }
436 });
437
438 return r;
439 },
440
441 explode : function(s, d) {
442 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
443 },
444
445 _addVer : function(u) {
446 var v;
447
448 if (!this.query)
449 return u;
450
451 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
452
453 if (u.indexOf('#') == -1)
454 return u + v;
455
456 return u.replace('#', v + '#');
457 },
458
459 // Fix function for IE 9 where regexps isn't working correctly
460 // Todo: remove me once MS fixes the bug
461 _replace : function(find, replace, str) {
462 // On IE9 we have to fake $x replacement
463 if (isRegExpBroken) {
464 return str.replace(find, function() {
465 var val = replace, args = arguments, i;
466
467 for (i = 0; i < args.length - 2; i++) {
468 if (args[i] === undefined) {
469 val = val.replace(new RegExp('\\$' + i, 'g'), '');
470 } else {
471 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
472 }
473 }
474
475 return val;
476 });
477 }
478
479 return str.replace(find, replace);
480 }
481
482 };
483
484 // Initialize the API
485 tinymce._init();
486
487 // Expose tinymce namespace to the global namespace (window)
488 win.tinymce = win.tinyMCE = tinymce;
489
490 // Describe the different namespaces
491
492 })(window);
493
494
495
496 tinymce.create('tinymce.util.Dispatcher', {
497 scope : null,
498 listeners : null,
499
500 Dispatcher : function(s) {
501 this.scope = s || this;
502 this.listeners = [];
503 },
504
505 add : function(cb, s) {
506 this.listeners.push({cb : cb, scope : s || this.scope});
507
508 return cb;
509 },
510
511 addToTop : function(cb, s) {
512 this.listeners.unshift({cb : cb, scope : s || this.scope});
513
514 return cb;
515 },
516
517 remove : function(cb) {
518 var l = this.listeners, o = null;
519
520 tinymce.each(l, function(c, i) {
521 if (cb == c.cb) {
522 o = cb;
523 l.splice(i, 1);
524 return false;
525 }
526 });
527
528 return o;
529 },
530
531 dispatch : function() {
532 var s, a = arguments, i, li = this.listeners, c;
533
534 // Needs to be a real loop since the listener count might change while looping
535 // And this is also more efficient
536 for (i = 0; i<li.length; i++) {
537 c = li[i];
538 s = c.cb.apply(c.scope, a.length > 0 ? a : [c.scope]);
539
540 if (s === false)
541 break;
542 }
543
544 return s;
545 }
546
547 });
548
549 (function() {
550 var each = tinymce.each;
551
552 tinymce.create('tinymce.util.URI', {
553 URI : function(u, s) {
554 var t = this, o, a, b, base_url;
555
556 // Trim whitespace
557 u = tinymce.trim(u);
558
559 // Default settings
560 s = t.settings = s || {};
561
562 // Strange app protocol that isn't http/https or local anchor
563 // For example: mailto,skype,tel etc.
564 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
565 t.source = u;
566 return;
567 }
568
569 // Absolute path with no host, fake host and protocol
570 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
571 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
572
573 // Relative path http:// or protocol relative //path
574 if (!/^[\w-]*:?\/\//.test(u)) {
575 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
576 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
577 }
578
579 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
580 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
581 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
582 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
583 var s = u[i];
584
585 // Zope 3 workaround, they use @@something
586 if (s)
587 s = s.replace(/\(mce_at\)/g, '@@');
588
589 t[v] = s;
590 });
591
592 if (b = s.base_uri) {
593 if (!t.protocol)
594 t.protocol = b.protocol;
595
596 if (!t.userInfo)
597 t.userInfo = b.userInfo;
598
599 if (!t.port && t.host == 'mce_host')
600 t.port = b.port;
601
602 if (!t.host || t.host == 'mce_host')
603 t.host = b.host;
604
605 t.source = '';
606 }
607
608 //t.path = t.path || '/';
609 },
610
611 setPath : function(p) {
612 var t = this;
613
614 p = /^(.*?)\/?(\w+)?$/.exec(p);
615
616 // Update path parts
617 t.path = p[0];
618 t.directory = p[1];
619 t.file = p[2];
620
621 // Rebuild source
622 t.source = '';
623 t.getURI();
624 },
625
626 toRelative : function(u) {
627 var t = this, o;
628
629 if (u === "./")
630 return u;
631
632 u = new tinymce.util.URI(u, {base_uri : t});
633
634 // Not on same domain/port or protocol
635 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
636 return u.getURI();
637
638 o = t.toRelPath(t.path, u.path);
639
640 // Add query
641 if (u.query)
642 o += '?' + u.query;
643
644 // Add anchor
645 if (u.anchor)
646 o += '#' + u.anchor;
647
648 return o;
649 },
650
651 toAbsolute : function(u, nh) {
652 var u = new tinymce.util.URI(u, {base_uri : this});
653
654 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
655 },
656
657 toRelPath : function(base, path) {
658 var items, bp = 0, out = '', i, l;
659
660 // Split the paths
661 base = base.substring(0, base.lastIndexOf('/'));
662 base = base.split('/');
663 items = path.split('/');
664
665 if (base.length >= items.length) {
666 for (i = 0, l = base.length; i < l; i++) {
667 if (i >= items.length || base[i] != items[i]) {
668 bp = i + 1;
669 break;
670 }
671 }
672 }
673
674 if (base.length < items.length) {
675 for (i = 0, l = items.length; i < l; i++) {
676 if (i >= base.length || base[i] != items[i]) {
677 bp = i + 1;
678 break;
679 }
680 }
681 }
682
683 if (bp == 1)
684 return path;
685
686 for (i = 0, l = base.length - (bp - 1); i < l; i++)
687 out += "../";
688
689 for (i = bp - 1, l = items.length; i < l; i++) {
690 if (i != bp - 1)
691 out += "/" + items[i];
692 else
693 out += items[i];
694 }
695
696 return out;
697 },
698
699 toAbsPath : function(base, path) {
700 var i, nb = 0, o = [], tr, outPath;
701
702 // Split paths
703 tr = /\/$/.test(path) ? '/' : '';
704 base = base.split('/');
705 path = path.split('/');
706
707 // Remove empty chunks
708 each(base, function(k) {
709 if (k)
710 o.push(k);
711 });
712
713 base = o;
714
715 // Merge relURLParts chunks
716 for (i = path.length - 1, o = []; i >= 0; i--) {
717 // Ignore empty or .
718 if (path[i].length == 0 || path[i] == ".")
719 continue;
720
721 // Is parent
722 if (path[i] == '..') {
723 nb++;
724 continue;
725 }
726
727 // Move up
728 if (nb > 0) {
729 nb--;
730 continue;
731 }
732
733 o.push(path[i]);
734 }
735
736 i = base.length - nb;
737
738 // If /a/b/c or /
739 if (i <= 0)
740 outPath = o.reverse().join('/');
741 else
742 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
743
744 // Add front / if it's needed
745 if (outPath.indexOf('/') !== 0)
746 outPath = '/' + outPath;
747
748 // Add traling / if it's needed
749 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
750 outPath += tr;
751
752 return outPath;
753 },
754
755 getURI : function(nh) {
756 var s, t = this;
757
758 // Rebuild source
759 if (!t.source || nh) {
760 s = '';
761
762 if (!nh) {
763 if (t.protocol)
764 s += t.protocol + '://';
765
766 if (t.userInfo)
767 s += t.userInfo + '@';
768
769 if (t.host)
770 s += t.host;
771
772 if (t.port)
773 s += ':' + t.port;
774 }
775
776 if (t.path)
777 s += t.path;
778
779 if (t.query)
780 s += '?' + t.query;
781
782 if (t.anchor)
783 s += '#' + t.anchor;
784
785 t.source = s;
786 }
787
788 return t.source;
789 }
790 });
791 })();
792
793 (function() {
794 var each = tinymce.each;
795
796 tinymce.create('static tinymce.util.Cookie', {
797 getHash : function(n) {
798 var v = this.get(n), h;
799
800 if (v) {
801 each(v.split('&'), function(v) {
802 v = v.split('=');
803 h = h || {};
804 h[unescape(v[0])] = unescape(v[1]);
805 });
806 }
807
808 return h;
809 },
810
811 setHash : function(n, v, e, p, d, s) {
812 var o = '';
813
814 each(v, function(v, k) {
815 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
816 });
817
818 this.set(n, o, e, p, d, s);
819 },
820
821 get : function(n) {
822 var c = document.cookie, e, p = n + "=", b;
823
824 // Strict mode
825 if (!c)
826 return;
827
828 b = c.indexOf("; " + p);
829
830 if (b == -1) {
831 b = c.indexOf(p);
832
833 if (b != 0)
834 return null;
835 } else
836 b += 2;
837
838 e = c.indexOf(";", b);
839
840 if (e == -1)
841 e = c.length;
842
843 return unescape(c.substring(b + p.length, e));
844 },
845
846 set : function(n, v, e, p, d, s) {
847 document.cookie = n + "=" + escape(v) +
848 ((e) ? "; expires=" + e.toGMTString() : "") +
849 ((p) ? "; path=" + escape(p) : "") +
850 ((d) ? "; domain=" + d : "") +
851 ((s) ? "; secure" : "");
852 },
853
854 remove : function(n, p) {
855 var d = new Date();
856
857 d.setTime(d.getTime() - 1000);
858
859 this.set(n, '', d, p, d);
860 }
861 });
862 })();
863
864 (function() {
865 function serialize(o, quote) {
866 var i, v, t;
867
868 quote = quote || '"';
869
870 if (o == null)
871 return 'null';
872
873 t = typeof o;
874
875 if (t == 'string') {
876 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
877
878 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
879 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
880 if (quote === '"' && a === "'")
881 return a;
882
883 i = v.indexOf(b);
884
885 if (i + 1)
886 return '\\' + v.charAt(i + 1);
887
888 a = b.charCodeAt().toString(16);
889
890 return '\\u' + '0000'.substring(a.length) + a;
891 }) + quote;
892 }
893
894 if (t == 'object') {
895 if (o.hasOwnProperty && o instanceof Array) {
896 for (i=0, v = '['; i<o.length; i++)
897 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
898
899 return v + ']';
900 }
901
902 v = '{';
903
904 for (i in o) {
905 if (o.hasOwnProperty(i)) {
906 v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
907 }
908 }
909
910 return v + '}';
911 }
912
913 return '' + o;
914 };
915
916 tinymce.util.JSON = {
917 serialize: serialize,
918
919 parse: function(s) {
920 try {
921 return eval('(' + s + ')');
922 } catch (ex) {
923 // Ignore
924 }
925 }
926
927 };
928 })();
929
930 tinymce.create('static tinymce.util.XHR', {
931 send : function(o) {
932 var x, t, w = window, c = 0;
933
934 // Default settings
935 o.scope = o.scope || this;
936 o.success_scope = o.success_scope || o.scope;
937 o.error_scope = o.error_scope || o.scope;
938 o.async = o.async === false ? false : true;
939 o.data = o.data || '';
940
941 function get(s) {
942 x = 0;
943
944 try {
945 x = new ActiveXObject(s);
946 } catch (ex) {
947 }
948
949 return x;
950 };
951
952 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
953
954 if (x) {
955 if (x.overrideMimeType)
956 x.overrideMimeType(o.content_type);
957
958 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
959
960 if (o.content_type)
961 x.setRequestHeader('Content-Type', o.content_type);
962
963 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
964
965 x.send(o.data);
966
967 function ready() {
968 if (!o.async || x.readyState == 4 || c++ > 10000) {
969 if (o.success && c < 10000 && x.status == 200)
970 o.success.call(o.success_scope, '' + x.responseText, x, o);
971 else if (o.error)
972 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
973
974 x = null;
975 } else
976 w.setTimeout(ready, 10);
977 };
978
979 // Syncronous request
980 if (!o.async)
981 return ready();
982
983 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
984 t = w.setTimeout(ready, 10);
985 }
986 }
987 });
988
989 (function() {
990 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
991
992 tinymce.create('tinymce.util.JSONRequest', {
993 JSONRequest : function(s) {
994 this.settings = extend({
995 }, s);
996 this.count = 0;
997 },
998
999 send : function(o) {
1000 var ecb = o.error, scb = o.success;
1001
1002 o = extend(this.settings, o);
1003
1004 o.success = function(c, x) {
1005 c = JSON.parse(c);
1006
1007 if (typeof(c) == 'undefined') {
1008 c = {
1009 error : 'JSON Parse error.'
1010 };
1011 }
1012
1013 if (c.error)
1014 ecb.call(o.error_scope || o.scope, c.error, x);
1015 else
1016 scb.call(o.success_scope || o.scope, c.result);
1017 };
1018
1019 o.error = function(ty, x) {
1020 if (ecb)
1021 ecb.call(o.error_scope || o.scope, ty, x);
1022 };
1023
1024 o.data = JSON.serialize({
1025 id : o.id || 'c' + (this.count++),
1026 method : o.method,
1027 params : o.params
1028 });
1029
1030 // JSON content type for Ruby on rails. Bug: #1883287
1031 o.content_type = 'application/json';
1032
1033 XHR.send(o);
1034 },
1035
1036 'static' : {
1037 sendRPC : function(o) {
1038 return new tinymce.util.JSONRequest().send(o);
1039 }
1040 }
1041 });
1042 }());
1043 (function(tinymce){
1044 tinymce.VK = {
1045 BACKSPACE: 8,
1046 DELETE: 46,
1047 DOWN: 40,
1048 ENTER: 13,
1049 LEFT: 37,
1050 RIGHT: 39,
1051 SPACEBAR: 32,
1052 TAB: 9,
1053 UP: 38,
1054
1055 modifierPressed: function (e) {
1056 return e.shiftKey || e.ctrlKey || e.altKey;
1057 }
1058 }
1059 })(tinymce);
1060
1061 (function(tinymce) {
1062 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE;
1063
1064 function cleanupStylesWhenDeleting(ed) {
1065 var dom = ed.dom, selection = ed.selection;
1066
1067 ed.onKeyDown.add(function(ed, e) {
1068 var rng, blockElm, node, clonedSpan, isDelete;
1069
1070 if (e.isDefaultPrevented()) {
1071 return;
1072 }
1073
1074 isDelete = e.keyCode == DELETE;
1075 if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1076 e.preventDefault();
1077 rng = selection.getRng();
1078
1079 // Find root block
1080 blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1081
1082 // On delete clone the root span of the next block element
1083 if (isDelete)
1084 blockElm = dom.getNext(blockElm, dom.isBlock);
1085
1086 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1087 if (blockElm) {
1088 node = blockElm.firstChild;
1089
1090 // Ignore empty text nodes
1091 while (node && node.nodeType == 3 && node.nodeValue.length == 0)
1092 node = node.nextSibling;
1093
1094 if (node && node.nodeName === 'SPAN') {
1095 clonedSpan = node.cloneNode(false);
1096 }
1097 }
1098
1099 // Do the backspace/delete action
1100 ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1101
1102 // Find all odd apple-style-spans
1103 blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1104 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1105 var bm = selection.getBookmark();
1106
1107 if (clonedSpan) {
1108 dom.replace(clonedSpan.cloneNode(false), span, true);
1109 } else {
1110 dom.remove(span, true);
1111 }
1112
1113 // Restore the selection
1114 selection.moveToBookmark(bm);
1115 });
1116 }
1117 });
1118 };
1119
1120 function emptyEditorWhenDeleting(ed) {
1121 function serializeRng(rng) {
1122 var body = ed.dom.create("body");
1123 var contents = rng.cloneContents();
1124 body.appendChild(contents);
1125 return ed.selection.serializer.serialize(body, {format: 'html'});
1126 }
1127
1128 function allContentsSelected(rng) {
1129 var selection = serializeRng(rng);
1130
1131 var allRng = ed.dom.createRng();
1132 allRng.selectNode(ed.getBody());
1133
1134 var allSelection = serializeRng(allRng);
1135 return selection === allSelection;
1136 }
1137
1138 ed.onKeyDown.addToTop(function(ed, e) {
1139 var keyCode = e.keyCode;
1140 if (keyCode == DELETE || keyCode == BACKSPACE) {
1141 var rng = ed.selection.getRng(true);
1142 if (!rng.collapsed && allContentsSelected(rng)) {
1143 ed.setContent('', {format : 'raw'});
1144 ed.nodeChanged();
1145 e.preventDefault();
1146 }
1147 }
1148 });
1149 };
1150
1151 function inputMethodFocus(ed) {
1152 ed.dom.bind(ed.getDoc(), 'focusin', function() {
1153 ed.selection.setRng(ed.selection.getRng());
1154 });
1155 };
1156
1157 function removeHrOnBackspace(ed) {
1158 ed.onKeyDown.add(function(ed, e) {
1159 if (e.keyCode === BACKSPACE) {
1160 if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) {
1161 var node = ed.selection.getNode();
1162 var previousSibling = node.previousSibling;
1163 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1164 ed.dom.remove(previousSibling);
1165 tinymce.dom.Event.cancel(e);
1166 }
1167 }
1168 }
1169 })
1170 }
1171
1172 function focusBody(ed) {
1173 // Fix for a focus bug in FF 3.x where the body element
1174 // wouldn't get proper focus if the user clicked on the HTML element
1175 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1176 ed.onMouseDown.add(function(ed, e) {
1177 if (e.target.nodeName === "HTML") {
1178 var body = ed.getBody();
1179
1180 // Blur the body it's focused but not correctly focused
1181 body.blur();
1182
1183 // Refocus the body after a little while
1184 setTimeout(function() {
1185 body.focus();
1186 }, 0);
1187 }
1188 });
1189 }
1190 };
1191
1192 function selectControlElements(ed) {
1193 ed.onClick.add(function(ed, e) {
1194 e = e.target;
1195
1196 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1197 // WebKit can't even do simple things like selecting an image
1198 // Needs tobe the setBaseAndExtend or it will fail to select floated images
1199 if (/^(IMG|HR)$/.test(e.nodeName))
1200 ed.selection.getSel().setBaseAndExtent(e, 0, e, 1);
1201
1202 if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor'))
1203 ed.selection.select(e);
1204
1205 ed.nodeChanged();
1206 });
1207 };
1208
1209 function removeStylesWhenDeletingAccrossBlockElements(ed) {
1210 var selection = ed.selection, dom = ed.dom;
1211
1212 function getAttributeApplyFunction() {
1213 var template = dom.getAttribs(selection.getStart().cloneNode(false));
1214
1215 return function() {
1216 var target = selection.getStart();
1217
1218 if (target !== ed.getBody()) {
1219 dom.setAttrib(target, "style", null);
1220
1221 tinymce.each(template, function(attr) {
1222 target.setAttributeNode(attr.cloneNode(true));
1223 });
1224 }
1225 };
1226 }
1227
1228 function isSelectionAcrossElements() {
1229 return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1230 }
1231
1232 function blockEvent(ed, e) {
1233 e.preventDefault();
1234 return false;
1235 }
1236
1237 ed.onKeyPress.add(function(ed, e) {
1238 var applyAttributes;
1239
1240 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1241 applyAttributes = getAttributeApplyFunction();
1242 ed.getDoc().execCommand('delete', false, null);
1243 applyAttributes();
1244 e.preventDefault();
1245 return false;
1246 }
1247 });
1248
1249 dom.bind(ed.getDoc(), 'cut', function(e) {
1250 var applyAttributes;
1251
1252 if (isSelectionAcrossElements()) {
1253 applyAttributes = getAttributeApplyFunction();
1254 ed.onKeyUp.addToTop(blockEvent);
1255
1256 setTimeout(function() {
1257 applyAttributes();
1258 ed.onKeyUp.remove(blockEvent);
1259 }, 0);
1260 }
1261 });
1262 }
1263
1264 /*
1265 function removeStylesOnPTagsInheritedFromHeadingTag(ed) {
1266 ed.onKeyDown.add(function(ed, event) {
1267 function checkInHeadingTag(ed) {
1268 var currentNode = ed.selection.getNode();
1269 var headingTags = 'h1,h2,h3,h4,h5,h6';
1270 return ed.dom.is(currentNode, headingTags) || ed.dom.getParent(currentNode, headingTags) !== null;
1271 }
1272
1273 if (event.keyCode === VK.ENTER && !VK.modifierPressed(event) && checkInHeadingTag(ed)) {
1274 setTimeout(function() {
1275 var currentNode = ed.selection.getNode();
1276 if (ed.dom.is(currentNode, 'p')) {
1277 ed.dom.setAttrib(currentNode, 'style', null);
1278 // While tiny's content is correct after this method call, the content shown is not representative of it and needs to be 'repainted'
1279 ed.execCommand('mceCleanup');
1280 }
1281 }, 0);
1282 }
1283 });
1284 }
1285 */
1286
1287 function selectionChangeNodeChanged(ed) {
1288 var lastRng, selectionTimer;
1289
1290 ed.dom.bind(ed.getDoc(), 'selectionchange', function() {
1291 if (selectionTimer) {
1292 clearTimeout(selectionTimer);
1293 selectionTimer = 0;
1294 }
1295
1296 selectionTimer = window.setTimeout(function() {
1297 var rng = ed.selection.getRng();
1298
1299 // Compare the ranges to see if it was a real change or not
1300 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1301 ed.nodeChanged();
1302 lastRng = rng;
1303 }
1304 }, 50);
1305 });
1306 }
1307
1308 function ensureBodyHasRoleApplication(ed) {
1309 document.body.setAttribute("role", "application");
1310 }
1311
1312 function disableBackspaceIntoATable(ed) {
1313 ed.onKeyDown.add(function(ed, e) {
1314 if (e.keyCode === BACKSPACE) {
1315 if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) {
1316 var previousSibling = ed.selection.getNode().previousSibling;
1317 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1318 return tinymce.dom.Event.cancel(e);
1319 }
1320 }
1321 }
1322 })
1323 }
1324
1325 tinymce.create('tinymce.util.Quirks', {
1326 Quirks: function(ed) {
1327 // All browsers
1328 disableBackspaceIntoATable(ed);
1329
1330 // WebKit
1331 if (tinymce.isWebKit) {
1332 cleanupStylesWhenDeleting(ed);
1333 emptyEditorWhenDeleting(ed);
1334 inputMethodFocus(ed);
1335 selectControlElements(ed);
1336
1337 // iOS
1338 if (tinymce.isIDevice) {
1339 selectionChangeNodeChanged(ed);
1340 }
1341 }
1342
1343 // IE
1344 if (tinymce.isIE) {
1345 removeHrOnBackspace(ed);
1346 emptyEditorWhenDeleting(ed);
1347 ensureBodyHasRoleApplication(ed);
1348 //removeStylesOnPTagsInheritedFromHeadingTag(ed)
1349 }
1350
1351 // Gecko
1352 if (tinymce.isGecko) {
1353 removeHrOnBackspace(ed);
1354 focusBody(ed);
1355 removeStylesWhenDeletingAccrossBlockElements(ed);
1356 }
1357 }
1358 });
1359 })(tinymce);
1360
1361 (function(tinymce) {
1362 var namedEntities, baseEntities, reverseEntities,
1363 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1364 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1365 rawCharsRegExp = /[<>&\"\']/g,
1366 entityRegExp = /&(#x|#)?([\w]+);/g,
1367 asciiMap = {
1368 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1369 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1370 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1371 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1372 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1373 };
1374
1375 // Raw entities
1376 baseEntities = {
1377 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
1378 "'" : '&#39;',
1379 '<' : '&lt;',
1380 '>' : '&gt;',
1381 '&' : '&amp;'
1382 };
1383
1384 // Reverse lookup table for raw entities
1385 reverseEntities = {
1386 '&lt;' : '<',
1387 '&gt;' : '>',
1388 '&amp;' : '&',
1389 '&quot;' : '"',
1390 '&apos;' : "'"
1391 };
1392
1393 // Decodes text by using the browser
1394 function nativeDecode(text) {
1395 var elm;
1396
1397 elm = document.createElement("div");
1398 elm.innerHTML = text;
1399
1400 return elm.textContent || elm.innerText || text;
1401 };
1402
1403 // Build a two way lookup table for the entities
1404 function buildEntitiesLookup(items, radix) {
1405 var i, chr, entity, lookup = {};
1406
1407 if (items) {
1408 items = items.split(',');
1409 radix = radix || 10;
1410
1411 // Build entities lookup table
1412 for (i = 0; i < items.length; i += 2) {
1413 chr = String.fromCharCode(parseInt(items[i], radix));
1414
1415 // Only add non base entities
1416 if (!baseEntities[chr]) {
1417 entity = '&' + items[i + 1] + ';';
1418 lookup[chr] = entity;
1419 lookup[entity] = chr;
1420 }
1421 }
1422
1423 return lookup;
1424 }
1425 };
1426
1427 // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1428 namedEntities = buildEntitiesLookup(
1429 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1430 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1431 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1432 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1433 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1434 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1435 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1436 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1437 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1438 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1439 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1440 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1441 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1442 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1443 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1444 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1445 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1446 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1447 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1448 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1449 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1450 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1451 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1452 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1453 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1454 , 32);
1455
1456 tinymce.html = tinymce.html || {};
1457
1458 tinymce.html.Entities = {
1459 encodeRaw : function(text, attr) {
1460 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1461 return baseEntities[chr] || chr;
1462 });
1463 },
1464
1465 encodeAllRaw : function(text) {
1466 return ('' + text).replace(rawCharsRegExp, function(chr) {
1467 return baseEntities[chr] || chr;
1468 });
1469 },
1470
1471 encodeNumeric : function(text, attr) {
1472 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1473 // Multi byte sequence convert it to a single entity
1474 if (chr.length > 1)
1475 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1476
1477 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1478 });
1479 },
1480
1481 encodeNamed : function(text, attr, entities) {
1482 entities = entities || namedEntities;
1483
1484 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1485 return baseEntities[chr] || entities[chr] || chr;
1486 });
1487 },
1488
1489 getEncodeFunc : function(name, entities) {
1490 var Entities = tinymce.html.Entities;
1491
1492 entities = buildEntitiesLookup(entities) || namedEntities;
1493
1494 function encodeNamedAndNumeric(text, attr) {
1495 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1496 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1497 });
1498 };
1499
1500 function encodeCustomNamed(text, attr) {
1501 return Entities.encodeNamed(text, attr, entities);
1502 };
1503
1504 // Replace + with , to be compatible with previous TinyMCE versions
1505 name = tinymce.makeMap(name.replace(/\+/g, ','));
1506
1507 // Named and numeric encoder
1508 if (name.named && name.numeric)
1509 return encodeNamedAndNumeric;
1510
1511 // Named encoder
1512 if (name.named) {
1513 // Custom names
1514 if (entities)
1515 return encodeCustomNamed;
1516
1517 return Entities.encodeNamed;
1518 }
1519
1520 // Numeric
1521 if (name.numeric)
1522 return Entities.encodeNumeric;
1523
1524 // Raw encoder
1525 return Entities.encodeRaw;
1526 },
1527
1528 decode : function(text) {
1529 return text.replace(entityRegExp, function(all, numeric, value) {
1530 if (numeric) {
1531 value = parseInt(value, numeric.length === 2 ? 16 : 10);
1532
1533 // Support upper UTF
1534 if (value > 0xFFFF) {
1535 value -= 0x10000;
1536
1537 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1538 } else
1539 return asciiMap[value] || String.fromCharCode(value);
1540 }
1541
1542 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1543 });
1544 }
1545 };
1546 })(tinymce);
1547
1548 tinymce.html.Styles = function(settings, schema) {
1549 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1550 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1551 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1552 trimRightRegExp = /\s+$/,
1553 urlColorRegExp = /rgb/,
1554 undef, i, encodingLookup = {}, encodingItems;
1555
1556 settings = settings || {};
1557
1558 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1559 for (i = 0; i < encodingItems.length; i++) {
1560 encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1561 encodingLookup['\uFEFF' + i] = encodingItems[i];
1562 }
1563
1564 function toHex(match, r, g, b) {
1565 function hex(val) {
1566 val = parseInt(val).toString(16);
1567
1568 return val.length > 1 ? val : '0' + val; // 0 -> 00
1569 };
1570
1571 return '#' + hex(r) + hex(g) + hex(b);
1572 };
1573
1574 return {
1575 toHex : function(color) {
1576 return color.replace(rgbRegExp, toHex);
1577 },
1578
1579 parse : function(css) {
1580 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1581
1582 function compress(prefix, suffix) {
1583 var top, right, bottom, left;
1584
1585 // Get values and check it it needs compressing
1586 top = styles[prefix + '-top' + suffix];
1587 if (!top)
1588 return;
1589
1590 right = styles[prefix + '-right' + suffix];
1591 if (top != right)
1592 return;
1593
1594 bottom = styles[prefix + '-bottom' + suffix];
1595 if (right != bottom)
1596 return;
1597
1598 left = styles[prefix + '-left' + suffix];
1599 if (bottom != left)
1600 return;
1601
1602 // Compress
1603 styles[prefix + suffix] = left;
1604 delete styles[prefix + '-top' + suffix];
1605 delete styles[prefix + '-right' + suffix];
1606 delete styles[prefix + '-bottom' + suffix];
1607 delete styles[prefix + '-left' + suffix];
1608 };
1609
1610 function canCompress(key) {
1611 var value = styles[key], i;
1612
1613 if (!value || value.indexOf(' ') < 0)
1614 return;
1615
1616 value = value.split(' ');
1617 i = value.length;
1618 while (i--) {
1619 if (value[i] !== value[0])
1620 return false;
1621 }
1622
1623 styles[key] = value[0];
1624
1625 return true;
1626 };
1627
1628 function compress2(target, a, b, c) {
1629 if (!canCompress(a))
1630 return;
1631
1632 if (!canCompress(b))
1633 return;
1634
1635 if (!canCompress(c))
1636 return;
1637
1638 // Compress
1639 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1640 delete styles[a];
1641 delete styles[b];
1642 delete styles[c];
1643 };
1644
1645 // Encodes the specified string by replacing all \" \' ; : with _<num>
1646 function encode(str) {
1647 isEncoded = true;
1648
1649 return encodingLookup[str];
1650 };
1651
1652 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1653 // It will also decode the \" \' if keep_slashes is set to fale or omitted
1654 function decode(str, keep_slashes) {
1655 if (isEncoded) {
1656 str = str.replace(/\uFEFF[0-9]/g, function(str) {
1657 return encodingLookup[str];
1658 });
1659 }
1660
1661 if (!keep_slashes)
1662 str = str.replace(/\\([\'\";:])/g, "$1");
1663
1664 return str;
1665 }
1666
1667 if (css) {
1668 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1669 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1670 return str.replace(/[;:]/g, encode);
1671 });
1672
1673 // Parse styles
1674 while (matches = styleRegExp.exec(css)) {
1675 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1676 value = matches[2].replace(trimRightRegExp, '');
1677
1678 if (name && value.length > 0) {
1679 // Opera will produce 700 instead of bold in their style values
1680 if (name === 'font-weight' && value === '700')
1681 value = 'bold';
1682 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1683 value = value.toLowerCase();
1684
1685 // Convert RGB colors to HEX
1686 value = value.replace(rgbRegExp, toHex);
1687
1688 // Convert URLs and force them into url('value') format
1689 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1690 str = str || str2;
1691
1692 if (str) {
1693 str = decode(str);
1694
1695 // Force strings into single quote format
1696 return "'" + str.replace(/\'/g, "\\'") + "'";
1697 }
1698
1699 url = decode(url || url2 || url3);
1700
1701 // Convert the URL to relative/absolute depending on config
1702 if (urlConverter)
1703 url = urlConverter.call(urlConverterScope, url, 'style');
1704
1705 // Output new URL format
1706 return "url('" + url.replace(/\'/g, "\\'") + "')";
1707 });
1708
1709 styles[name] = isEncoded ? decode(value, true) : value;
1710 }
1711
1712 styleRegExp.lastIndex = matches.index + matches[0].length;
1713 }
1714
1715 // Compress the styles to reduce it's size for example IE will expand styles
1716 compress("border", "");
1717 compress("border", "-width");
1718 compress("border", "-color");
1719 compress("border", "-style");
1720 compress("padding", "");
1721 compress("margin", "");
1722 compress2('border', 'border-width', 'border-style', 'border-color');
1723
1724 // Remove pointless border, IE produces these
1725 if (styles.border === 'medium none')
1726 delete styles.border;
1727 }
1728
1729 return styles;
1730 },
1731
1732 serialize : function(styles, element_name) {
1733 var css = '', name, value;
1734
1735 function serializeStyles(name) {
1736 var styleList, i, l, value;
1737
1738 styleList = schema.styles[name];
1739 if (styleList) {
1740 for (i = 0, l = styleList.length; i < l; i++) {
1741 name = styleList[i];
1742 value = styles[name];
1743
1744 if (value !== undef && value.length > 0)
1745 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1746 }
1747 }
1748 };
1749
1750 // Serialize styles according to schema
1751 if (element_name && schema && schema.styles) {
1752 // Serialize global styles and element specific styles
1753 serializeStyles('*');
1754 serializeStyles(element_name);
1755 } else {
1756 // Output the styles in the order they are inside the object
1757 for (name in styles) {
1758 value = styles[name];
1759
1760 if (value !== undef && value.length > 0)
1761 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1762 }
1763 }
1764
1765 return css;
1766 }
1767 };
1768 };
1769
1770 (function(tinymce) {
1771 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
1772
1773 function split(str, delim) {
1774 return str.split(delim || ',');
1775 };
1776
1777 function unpack(lookup, data) {
1778 var key, elements = {};
1779
1780 function replace(value) {
1781 return value.replace(/[A-Z]+/g, function(key) {
1782 return replace(lookup[key]);
1783 });
1784 };
1785
1786 // Unpack lookup
1787 for (key in lookup) {
1788 if (lookup.hasOwnProperty(key))
1789 lookup[key] = replace(lookup[key]);
1790 }
1791
1792 // Unpack and parse data into object map
1793 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1794 attributes = split(attributes, '|');
1795
1796 elements[name] = {
1797 attributes : makeMap(attributes),
1798 attributesOrder : attributes,
1799 children : makeMap(children, '|', {'#comment' : {}})
1800 }
1801 });
1802
1803 return elements;
1804 };
1805
1806 function getHTML5() {
1807 var html5 = mapCache.html5;
1808
1809 if (!html5) {
1810 html5 = mapCache.html5 = unpack({
1811 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title',
1812 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video',
1813 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
1814 }, 'html[A|manifest][body|head]' +
1815 'head[A][base|command|link|meta|noscript|script|style|title]' +
1816 'title[A][#]' +
1817 'base[A|href|target][]' +
1818 'link[A|href|rel|media|type|sizes][]' +
1819 'meta[A|http-equiv|name|content|charset][]' +
1820 'style[A|type|media|scoped][#]' +
1821 'script[A|charset|type|src|defer|async][#]' +
1822 'noscript[A][C]' +
1823 'body[A][C]' +
1824 'section[A][C]' +
1825 'nav[A][C]' +
1826 'article[A][C]' +
1827 'aside[A][C]' +
1828 'h1[A][B]' +
1829 'h2[A][B]' +
1830 'h3[A][B]' +
1831 'h4[A][B]' +
1832 'h5[A][B]' +
1833 'h6[A][B]' +
1834 'hgroup[A][h1|h2|h3|h4|h5|h6]' +
1835 'header[A][C]' +
1836 'footer[A][C]' +
1837 'address[A][C]' +
1838 'p[A][B]' +
1839 'br[A][]' +
1840 'pre[A][B]' +
1841 'dialog[A][dd|dt]' +
1842 'blockquote[A|cite][C]' +
1843 'ol[A|start|reversed][li]' +
1844 'ul[A][li]' +
1845 'li[A|value][C]' +
1846 'dl[A][dd|dt]' +
1847 'dt[A][B]' +
1848 'dd[A][C]' +
1849 'a[A|href|target|ping|rel|media|type][C]' +
1850 'em[A][B]' +
1851 'strong[A][B]' +
1852 'small[A][B]' +
1853 'cite[A][B]' +
1854 'q[A|cite][B]' +
1855 'dfn[A][B]' +
1856 'abbr[A][B]' +
1857 'code[A][B]' +
1858 'var[A][B]' +
1859 'samp[A][B]' +
1860 'kbd[A][B]' +
1861 'sub[A][B]' +
1862 'sup[A][B]' +
1863 'i[A][B]' +
1864 'b[A][B]' +
1865 'mark[A][B]' +
1866 'progress[A|value|max][B]' +
1867 'meter[A|value|min|max|low|high|optimum][B]' +
1868 'time[A|datetime][B]' +
1869 'ruby[A][B|rt|rp]' +
1870 'rt[A][B]' +
1871 'rp[A][B]' +
1872 'bdo[A][B]' +
1873 'span[A][B]' +
1874 'ins[A|cite|datetime][B]' +
1875 'del[A|cite|datetime][B]' +
1876 'figure[A][C|legend]' +
1877 'img[A|alt|src|height|width|usemap|ismap][]' +
1878 'iframe[A|name|src|height|width|sandbox|seamless][]' +
1879 'embed[A|src|height|width|type][]' +
1880 'object[A|data|type|height|width|usemap|name|form|classid][param]' +
1881 'param[A|name|value][]' +
1882 'details[A|open][C|legend]' +
1883 'command[A|type|label|icon|disabled|checked|radiogroup][]' +
1884 'menu[A|type|label][C|li]' +
1885 'legend[A][C|B]' +
1886 'div[A][C]' +
1887 'source[A|src|type|media][]' +
1888 'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
1889 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
1890 'hr[A][]' +
1891 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
1892 'fieldset[A|disabled|form|name][C|legend]' +
1893 'label[A|form|for][B]' +
1894 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value][]' +
1895 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
1896 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
1897 'datalist[A][B|option]' +
1898 'optgroup[A|disabled|label][option]' +
1899 'option[A|disabled|selected|label|value][]' +
1900 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
1901 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
1902 'output[A|for|form|name][B]' +
1903 'canvas[A|width|height][]' +
1904 'map[A|name][B|C]' +
1905 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
1906 'mathml[A][]' +
1907 'svg[A][]' +
1908 'table[A|summary][caption|colgroup|thead|tfoot|tbody|tr]' +
1909 'caption[A][C]' +
1910 'colgroup[A|span][col]' +
1911 'col[A|span][]' +
1912 'thead[A][tr]' +
1913 'tfoot[A][tr]' +
1914 'tbody[A][tr]' +
1915 'tr[A][th|td]' +
1916 'th[A|headers|rowspan|colspan|scope][B]' +
1917 'td[A|headers|rowspan|colspan][C]'
1918 );
1919 }
1920
1921 return html5;
1922 };
1923
1924 function getHTML4() {
1925 var html4 = mapCache.html4;
1926
1927 if (!html4) {
1928 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1929 html4 = mapCache.html4 = unpack({
1930 Z : 'H|K|N|O|P',
1931 Y : 'X|form|R|Q',
1932 ZG : 'E|span|width|align|char|charoff|valign',
1933 X : 'p|T|div|U|W|isindex|fieldset|table',
1934 ZF : 'E|align|char|charoff|valign',
1935 W : 'pre|hr|blockquote|address|center|noframes',
1936 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1937 ZD : '[E][S]',
1938 U : 'ul|ol|dl|menu|dir',
1939 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1940 T : 'h1|h2|h3|h4|h5|h6',
1941 ZB : 'X|S|Q',
1942 S : 'R|P',
1943 ZA : 'a|G|J|M|O|P',
1944 R : 'a|H|K|N|O',
1945 Q : 'noscript|P',
1946 P : 'ins|del|script',
1947 O : 'input|select|textarea|label|button',
1948 N : 'M|L',
1949 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1950 L : 'sub|sup',
1951 K : 'J|I',
1952 J : 'tt|i|b|u|s|strike',
1953 I : 'big|small|font|basefont',
1954 H : 'G|F',
1955 G : 'br|span|bdo',
1956 F : 'object|applet|img|map|iframe',
1957 E : 'A|B|C',
1958 D : 'accesskey|tabindex|onfocus|onblur',
1959 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1960 B : 'lang|xml:lang|dir',
1961 A : 'id|class|style|title'
1962 }, 'script[id|charset|type|language|src|defer|xml:space][]' +
1963 'style[B|id|type|media|title|xml:space][]' +
1964 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
1965 'param[id|name|value|valuetype|type][]' +
1966 'p[E|align][#|S]' +
1967 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
1968 'br[A|clear][]' +
1969 'span[E][#|S]' +
1970 'bdo[A|C|B][#|S]' +
1971 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
1972 'h1[E|align][#|S]' +
1973 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
1974 'map[B|C|A|name][X|form|Q|area]' +
1975 'h2[E|align][#|S]' +
1976 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
1977 'h3[E|align][#|S]' +
1978 'tt[E][#|S]' +
1979 'i[E][#|S]' +
1980 'b[E][#|S]' +
1981 'u[E][#|S]' +
1982 's[E][#|S]' +
1983 'strike[E][#|S]' +
1984 'big[E][#|S]' +
1985 'small[E][#|S]' +
1986 'font[A|B|size|color|face][#|S]' +
1987 'basefont[id|size|color|face][]' +
1988 'em[E][#|S]' +
1989 'strong[E][#|S]' +
1990 'dfn[E][#|S]' +
1991 'code[E][#|S]' +
1992 'q[E|cite][#|S]' +
1993 'samp[E][#|S]' +
1994 'kbd[E][#|S]' +
1995 'var[E][#|S]' +
1996 'cite[E][#|S]' +
1997 'abbr[E][#|S]' +
1998 'acronym[E][#|S]' +
1999 'sub[E][#|S]' +
2000 'sup[E][#|S]' +
2001 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
2002 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
2003 'optgroup[E|disabled|label][option]' +
2004 'option[E|selected|disabled|label|value][]' +
2005 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
2006 'label[E|for|accesskey|onfocus|onblur][#|S]' +
2007 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
2008 'h4[E|align][#|S]' +
2009 'ins[E|cite|datetime][#|Y]' +
2010 'h5[E|align][#|S]' +
2011 'del[E|cite|datetime][#|Y]' +
2012 'h6[E|align][#|S]' +
2013 'div[E|align][#|Y]' +
2014 'ul[E|type|compact][li]' +
2015 'li[E|type|value][#|Y]' +
2016 'ol[E|type|compact|start][li]' +
2017 'dl[E|compact][dt|dd]' +
2018 'dt[E][#|S]' +
2019 'dd[E][#|Y]' +
2020 'menu[E|compact][li]' +
2021 'dir[E|compact][li]' +
2022 'pre[E|width|xml:space][#|ZA]' +
2023 'hr[E|align|noshade|size|width][]' +
2024 'blockquote[E|cite][#|Y]' +
2025 'address[E][#|S|p]' +
2026 'center[E][#|Y]' +
2027 'noframes[E][#|Y]' +
2028 'isindex[A|B|prompt][]' +
2029 'fieldset[E][#|legend|Y]' +
2030 'legend[E|accesskey|align][#|S]' +
2031 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
2032 'caption[E|align][#|S]' +
2033 'col[ZG][]' +
2034 'colgroup[ZG][col]' +
2035 'thead[ZF][tr]' +
2036 'tr[ZF|bgcolor][th|td]' +
2037 'th[E|ZE][#|Y]' +
2038 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
2039 'noscript[E][#|Y]' +
2040 'td[E|ZE][#|Y]' +
2041 'tfoot[ZF][tr]' +
2042 'tbody[ZF][tr]' +
2043 'area[E|D|shape|coords|href|nohref|alt|target][]' +
2044 'base[id|href|target][]' +
2045 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2046 );
2047 }
2048
2049 return html4;
2050 };
2051
2052 tinymce.html.Schema = function(settings) {
2053 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2054 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2055
2056 // Creates an lookup table map object for the specified option or the default value
2057 function createLookupTable(option, default_value, extend) {
2058 var value = settings[option];
2059
2060 if (!value) {
2061 // Get cached default map or make it if needed
2062 value = mapCache[option];
2063
2064 if (!value) {
2065 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2066 value = tinymce.extend(value, extend);
2067
2068 mapCache[option] = value;
2069 }
2070 } else {
2071 // Create custom map
2072 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2073 }
2074
2075 return value;
2076 };
2077
2078 settings = settings || {};
2079 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2080
2081 // Allow all elements and attributes if verify_html is set to false
2082 if (settings.verify_html === false)
2083 settings.valid_elements = '*[*]';
2084
2085 // Build styles list
2086 if (settings.valid_styles) {
2087 validStyles = {};
2088
2089 // Convert styles into a rule list
2090 each(settings.valid_styles, function(value, key) {
2091 validStyles[key] = tinymce.explode(value);
2092 });
2093 }
2094
2095 // Setup map objects
2096 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2097 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li options p td tfoot th thead tr');
2098 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source');
2099 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2100 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2101 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' +
2102 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' +
2103 'noscript menu isindex samp header footer article section hgroup aside nav');
2104
2105 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2106 function patternToRegExp(str) {
2107 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2108 };
2109
2110 // Parses the specified valid_elements string and adds to the current rules
2111 // This function is a bit hard to read since it's heavily optimized for speed
2112 function addValidElements(valid_elements) {
2113 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2114 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2115 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2116 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2117 hasPatternsRegExp = /[*?+]/;
2118
2119 if (valid_elements) {
2120 // Split valid elements into an array with rules
2121 valid_elements = split(valid_elements);
2122
2123 if (elements['@']) {
2124 globalAttributes = elements['@'].attributes;
2125 globalAttributesOrder = elements['@'].attributesOrder;
2126 }
2127
2128 // Loop all rules
2129 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2130 // Parse element rule
2131 matches = elementRuleRegExp.exec(valid_elements[ei]);
2132 if (matches) {
2133 // Setup local names for matches
2134 prefix = matches[1];
2135 elementName = matches[2];
2136 outputName = matches[3];
2137 attrData = matches[4];
2138
2139 // Create new attributes and attributesOrder
2140 attributes = {};
2141 attributesOrder = [];
2142
2143 // Create the new element
2144 element = {
2145 attributes : attributes,
2146 attributesOrder : attributesOrder
2147 };
2148
2149 // Padd empty elements prefix
2150 if (prefix === '#')
2151 element.paddEmpty = true;
2152
2153 // Remove empty elements prefix
2154 if (prefix === '-')
2155 element.removeEmpty = true;
2156
2157 // Copy attributes from global rule into current rule
2158 if (globalAttributes) {
2159 for (key in globalAttributes)
2160 attributes[key] = globalAttributes[key];
2161
2162 attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2163 }
2164
2165 // Attributes defined
2166 if (attrData) {
2167 attrData = split(attrData, '|');
2168 for (ai = 0, al = attrData.length; ai < al; ai++) {
2169 matches = attrRuleRegExp.exec(attrData[ai]);
2170 if (matches) {
2171 attr = {};
2172 attrType = matches[1];
2173 attrName = matches[2].replace(/::/g, ':');
2174 prefix = matches[3];
2175 value = matches[4];
2176
2177 // Required
2178 if (attrType === '!') {
2179 element.attributesRequired = element.attributesRequired || [];
2180 element.attributesRequired.push(attrName);
2181 attr.required = true;
2182 }
2183
2184 // Denied from global
2185 if (attrType === '-') {
2186 delete attributes[attrName];
2187 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2188 continue;
2189 }
2190
2191 // Default value
2192 if (prefix) {
2193 // Default value
2194 if (prefix === '=') {
2195 element.attributesDefault = element.attributesDefault || [];
2196 element.attributesDefault.push({name: attrName, value: value});
2197 attr.defaultValue = value;
2198 }
2199
2200 // Forced value
2201 if (prefix === ':') {
2202 element.attributesForced = element.attributesForced || [];
2203 element.attributesForced.push({name: attrName, value: value});
2204 attr.forcedValue = value;
2205 }
2206
2207 // Required values
2208 if (prefix === '<')
2209 attr.validValues = makeMap(value, '?');
2210 }
2211
2212 // Check for attribute patterns
2213 if (hasPatternsRegExp.test(attrName)) {
2214 element.attributePatterns = element.attributePatterns || [];
2215 attr.pattern = patternToRegExp(attrName);
2216 element.attributePatterns.push(attr);
2217 } else {
2218 // Add attribute to order list if it doesn't already exist
2219 if (!attributes[attrName])
2220 attributesOrder.push(attrName);
2221
2222 attributes[attrName] = attr;
2223 }
2224 }
2225 }
2226 }
2227
2228 // Global rule, store away these for later usage
2229 if (!globalAttributes && elementName == '@') {
2230 globalAttributes = attributes;
2231 globalAttributesOrder = attributesOrder;
2232 }
2233
2234 // Handle substitute elements such as b/strong
2235 if (outputName) {
2236 element.outputName = elementName;
2237 elements[outputName] = element;
2238 }
2239
2240 // Add pattern or exact element
2241 if (hasPatternsRegExp.test(elementName)) {
2242 element.pattern = patternToRegExp(elementName);
2243 patternElements.push(element);
2244 } else
2245 elements[elementName] = element;
2246 }
2247 }
2248 }
2249 };
2250
2251 function setValidElements(valid_elements) {
2252 elements = {};
2253 patternElements = [];
2254
2255 addValidElements(valid_elements);
2256
2257 each(schemaItems, function(element, name) {
2258 children[name] = element.children;
2259 });
2260 };
2261
2262 // Adds custom non HTML elements to the schema
2263 function addCustomElements(custom_elements) {
2264 var customElementRegExp = /^(~)?(.+)$/;
2265
2266 if (custom_elements) {
2267 each(split(custom_elements), function(rule) {
2268 var matches = customElementRegExp.exec(rule),
2269 inline = matches[1] === '~',
2270 cloneName = inline ? 'span' : 'div',
2271 name = matches[2];
2272
2273 children[name] = children[cloneName];
2274 customElementsMap[name] = cloneName;
2275
2276 // If it's not marked as inline then add it to valid block elements
2277 if (!inline)
2278 blockElementsMap[name] = {};
2279
2280 // Add custom elements at span/div positions
2281 each(children, function(element, child) {
2282 if (element[cloneName])
2283 element[name] = element[cloneName];
2284 });
2285 });
2286 }
2287 };
2288
2289 // Adds valid children to the schema object
2290 function addValidChildren(valid_children) {
2291 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2292
2293 if (valid_children) {
2294 each(split(valid_children), function(rule) {
2295 var matches = childRuleRegExp.exec(rule), parent, prefix;
2296
2297 if (matches) {
2298 prefix = matches[1];
2299
2300 // Add/remove items from default
2301 if (prefix)
2302 parent = children[matches[2]];
2303 else
2304 parent = children[matches[2]] = {'#comment' : {}};
2305
2306 parent = children[matches[2]];
2307
2308 each(split(matches[3], '|'), function(child) {
2309 if (prefix === '-')
2310 delete parent[child];
2311 else
2312 parent[child] = {};
2313 });
2314 }
2315 });
2316 }
2317 };
2318
2319 function getElementRule(name) {
2320 var element = elements[name], i;
2321
2322 // Exact match found
2323 if (element)
2324 return element;
2325
2326 // No exact match then try the patterns
2327 i = patternElements.length;
2328 while (i--) {
2329 element = patternElements[i];
2330
2331 if (element.pattern.test(name))
2332 return element;
2333 }
2334 };
2335
2336 if (!settings.valid_elements) {
2337 // No valid elements defined then clone the elements from the schema spec
2338 each(schemaItems, function(element, name) {
2339 elements[name] = {
2340 attributes : element.attributes,
2341 attributesOrder : element.attributesOrder
2342 };
2343
2344 children[name] = element.children;
2345 });
2346
2347 // Switch these on HTML4
2348 if (settings.schema != "html5") {
2349 each(split('strong/b,em/i'), function(item) {
2350 item = split(item, '/');
2351 elements[item[1]].outputName = item[0];
2352 });
2353 }
2354
2355 // Add default alt attribute for images
2356 elements.img.attributesDefault = [{name: 'alt', value: ''}];
2357
2358 // Remove these if they are empty by default
2359 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
2360 if (elements[name]) {
2361 elements[name].removeEmpty = true;
2362 }
2363 });
2364
2365 // Padd these by default
2366 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2367 elements[name].paddEmpty = true;
2368 });
2369 } else
2370 setValidElements(settings.valid_elements);
2371
2372 addCustomElements(settings.custom_elements);
2373 addValidChildren(settings.valid_children);
2374 addValidElements(settings.extended_valid_elements);
2375
2376 // Todo: Remove this when we fix list handling to be valid
2377 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2378
2379 // Delete invalid elements
2380 if (settings.invalid_elements) {
2381 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2382 if (elements[item])
2383 delete elements[item];
2384 });
2385 }
2386
2387 // If the user didn't allow span only allow internal spans
2388 if (!getElementRule('span'))
2389 addValidElements('span[!data-mce-type|*]');
2390
2391 self.children = children;
2392
2393 self.styles = validStyles;
2394
2395 self.getBoolAttrs = function() {
2396 return boolAttrMap;
2397 };
2398
2399 self.getBlockElements = function() {
2400 return blockElementsMap;
2401 };
2402
2403 self.getShortEndedElements = function() {
2404 return shortEndedElementsMap;
2405 };
2406
2407 self.getSelfClosingElements = function() {
2408 return selfClosingElementsMap;
2409 };
2410
2411 self.getNonEmptyElements = function() {
2412 return nonEmptyElementsMap;
2413 };
2414
2415 self.getWhiteSpaceElements = function() {
2416 return whiteSpaceElementsMap;
2417 };
2418
2419 self.isValidChild = function(name, child) {
2420 var parent = children[name];
2421
2422 return !!(parent && parent[child]);
2423 };
2424
2425 self.getElementRule = getElementRule;
2426
2427 self.getCustomElements = function() {
2428 return customElementsMap;
2429 };
2430
2431 self.addValidElements = addValidElements;
2432
2433 self.setValidElements = setValidElements;
2434
2435 self.addCustomElements = addCustomElements;
2436
2437 self.addValidChildren = addValidChildren;
2438 };
2439 })(tinymce);
2440
2441 (function(tinymce) {
2442 tinymce.html.SaxParser = function(settings, schema) {
2443 var self = this, noop = function() {};
2444
2445 settings = settings || {};
2446 self.schema = schema = schema || new tinymce.html.Schema();
2447
2448 if (settings.fix_self_closing !== false)
2449 settings.fix_self_closing = true;
2450
2451 // Add handler functions from settings and setup default handlers
2452 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2453 if (name)
2454 self[name] = settings[name] || noop;
2455 });
2456
2457 self.parse = function(html) {
2458 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2459 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2460 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2461 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2462
2463 function processEndTag(name) {
2464 var pos, i;
2465
2466 // Find position of parent of the same type
2467 pos = stack.length;
2468 while (pos--) {
2469 if (stack[pos].name === name)
2470 break;
2471 }
2472
2473 // Found parent
2474 if (pos >= 0) {
2475 // Close all the open elements
2476 for (i = stack.length - 1; i >= pos; i--) {
2477 name = stack[i];
2478
2479 if (name.valid)
2480 self.end(name.name);
2481 }
2482
2483 // Remove the open elements from the stack
2484 stack.length = pos;
2485 }
2486 };
2487
2488 // Precompile RegExps and map objects
2489 tokenRegExp = new RegExp('<(?:' +
2490 '(?:!--([\\w\\W]*?)-->)|' + // Comment
2491 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2492 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2493 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2494 '(?:\\/([^>]+)>)|' + // End element
2495 '(?:([^\\s\\/<>]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
2496 ')', 'g');
2497
2498 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2499 specialElements = {
2500 'script' : /<\/script[^>]*>/gi,
2501 'style' : /<\/style[^>]*>/gi,
2502 'noscript' : /<\/noscript[^>]*>/gi
2503 };
2504
2505 // Setup lookup tables for empty elements and boolean attributes
2506 shortEndedElements = schema.getShortEndedElements();
2507 selfClosing = schema.getSelfClosingElements();
2508 fillAttrsMap = schema.getBoolAttrs();
2509 validate = settings.validate;
2510 removeInternalElements = settings.remove_internals;
2511 fixSelfClosing = settings.fix_self_closing;
2512 isIE = tinymce.isIE;
2513 invalidPrefixRegExp = /^:/;
2514
2515 while (matches = tokenRegExp.exec(html)) {
2516 // Text
2517 if (index < matches.index)
2518 self.text(decode(html.substr(index, matches.index - index)));
2519
2520 if (value = matches[6]) { // End element
2521 value = value.toLowerCase();
2522
2523 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2524 if (isIE && invalidPrefixRegExp.test(value))
2525 value = value.substr(1);
2526
2527 processEndTag(value);
2528 } else if (value = matches[7]) { // Start element
2529 value = value.toLowerCase();
2530
2531 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2532 if (isIE && invalidPrefixRegExp.test(value))
2533 value = value.substr(1);
2534
2535 isShortEnded = value in shortEndedElements;
2536
2537 // Is self closing tag for example an <li> after an open <li>
2538 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2539 processEndTag(value);
2540
2541 // Validate element
2542 if (!validate || (elementRule = schema.getElementRule(value))) {
2543 isValidElement = true;
2544
2545 // Grab attributes map and patters when validation is enabled
2546 if (validate) {
2547 validAttributesMap = elementRule.attributes;
2548 validAttributePatterns = elementRule.attributePatterns;
2549 }
2550
2551 // Parse attributes
2552 if (attribsValue = matches[8]) {
2553 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
2554
2555 // If the element has internal attributes then remove it if we are told to do so
2556 if (isInternalElement && removeInternalElements)
2557 isValidElement = false;
2558
2559 attrList = [];
2560 attrList.map = {};
2561
2562 attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2563 var attrRule, i;
2564
2565 name = name.toLowerCase();
2566 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2567
2568 // Validate name and value
2569 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2570 attrRule = validAttributesMap[name];
2571
2572 // Find rule by pattern matching
2573 if (!attrRule && validAttributePatterns) {
2574 i = validAttributePatterns.length;
2575 while (i--) {
2576 attrRule = validAttributePatterns[i];
2577 if (attrRule.pattern.test(name))
2578 break;
2579 }
2580
2581 // No rule matched
2582 if (i === -1)
2583 attrRule = null;
2584 }
2585
2586 // No attribute rule found
2587 if (!attrRule)
2588 return;
2589
2590 // Validate value
2591 if (attrRule.validValues && !(value in attrRule.validValues))
2592 return;
2593 }
2594
2595 // Add attribute to list and map
2596 attrList.map[name] = value;
2597 attrList.push({
2598 name: name,
2599 value: value
2600 });
2601 });
2602 } else {
2603 attrList = [];
2604 attrList.map = {};
2605 }
2606
2607 // Process attributes if validation is enabled
2608 if (validate && !isInternalElement) {
2609 attributesRequired = elementRule.attributesRequired;
2610 attributesDefault = elementRule.attributesDefault;
2611 attributesForced = elementRule.attributesForced;
2612
2613 // Handle forced attributes
2614 if (attributesForced) {
2615 i = attributesForced.length;
2616 while (i--) {
2617 attr = attributesForced[i];
2618 name = attr.name;
2619 attrValue = attr.value;
2620
2621 if (attrValue === '{$uid}')
2622 attrValue = 'mce_' + idCount++;
2623
2624 attrList.map[name] = attrValue;
2625 attrList.push({name: name, value: attrValue});
2626 }
2627 }
2628
2629 // Handle default attributes
2630 if (attributesDefault) {
2631 i = attributesDefault.length;
2632 while (i--) {
2633 attr = attributesDefault[i];
2634 name = attr.name;
2635
2636 if (!(name in attrList.map)) {
2637 attrValue = attr.value;
2638
2639 if (attrValue === '{$uid}')
2640 attrValue = 'mce_' + idCount++;
2641
2642 attrList.map[name] = attrValue;
2643 attrList.push({name: name, value: attrValue});
2644 }
2645 }
2646 }
2647
2648 // Handle required attributes
2649 if (attributesRequired) {
2650 i = attributesRequired.length;
2651 while (i--) {
2652 if (attributesRequired[i] in attrList.map)
2653 break;
2654 }
2655
2656 // None of the required attributes where found
2657 if (i === -1)
2658 isValidElement = false;
2659 }
2660
2661 // Invalidate element if it's marked as bogus
2662 if (attrList.map['data-mce-bogus'])
2663 isValidElement = false;
2664 }
2665
2666 if (isValidElement)
2667 self.start(value, attrList, isShortEnded);
2668 } else
2669 isValidElement = false;
2670
2671 // Treat script, noscript and style a bit different since they may include code that looks like elements
2672 if (endRegExp = specialElements[value]) {
2673 endRegExp.lastIndex = index = matches.index + matches[0].length;
2674
2675 if (matches = endRegExp.exec(html)) {
2676 if (isValidElement)
2677 text = html.substr(index, matches.index - index);
2678
2679 index = matches.index + matches[0].length;
2680 } else {
2681 text = html.substr(index);
2682 index = html.length;
2683 }
2684
2685 if (isValidElement && text.length > 0)
2686 self.text(text, true);
2687
2688 if (isValidElement)
2689 self.end(value);
2690
2691 tokenRegExp.lastIndex = index;
2692 continue;
2693 }
2694
2695 // Push value on to stack
2696 if (!isShortEnded) {
2697 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2698 stack.push({name: value, valid: isValidElement});
2699 else if (isValidElement)
2700 self.end(value);
2701 }
2702 } else if (value = matches[1]) { // Comment
2703 self.comment(value);
2704 } else if (value = matches[2]) { // CDATA
2705 self.cdata(value);
2706 } else if (value = matches[3]) { // DOCTYPE
2707 self.doctype(value);
2708 } else if (value = matches[4]) { // PI
2709 self.pi(value, matches[5]);
2710 }
2711
2712 index = matches.index + matches[0].length;
2713 }
2714
2715 // Text
2716 if (index < html.length)
2717 self.text(decode(html.substr(index)));
2718
2719 // Close any open elements
2720 for (i = stack.length - 1; i >= 0; i--) {
2721 value = stack[i];
2722
2723 if (value.valid)
2724 self.end(value.name);
2725 }
2726 };
2727 }
2728 })(tinymce);
2729
2730 (function(tinymce) {
2731 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2732 '#text' : 3,
2733 '#comment' : 8,
2734 '#cdata' : 4,
2735 '#pi' : 7,
2736 '#doctype' : 10,
2737 '#document-fragment' : 11
2738 };
2739
2740 // Walks the tree left/right
2741 function walk(node, root_node, prev) {
2742 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2743
2744 // Walk into nodes if it has a start
2745 if (node[startName])
2746 return node[startName];
2747
2748 // Return the sibling if it has one
2749 if (node !== root_node) {
2750 sibling = node[siblingName];
2751
2752 if (sibling)
2753 return sibling;
2754
2755 // Walk up the parents to look for siblings
2756 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2757 sibling = parent[siblingName];
2758
2759 if (sibling)
2760 return sibling;
2761 }
2762 }
2763 };
2764
2765 function Node(name, type) {
2766 this.name = name;
2767 this.type = type;
2768
2769 if (type === 1) {
2770 this.attributes = [];
2771 this.attributes.map = {};
2772 }
2773 }
2774
2775 tinymce.extend(Node.prototype, {
2776 replace : function(node) {
2777 var self = this;
2778
2779 if (node.parent)
2780 node.remove();
2781
2782 self.insert(node, self);
2783 self.remove();
2784
2785 return self;
2786 },
2787
2788 attr : function(name, value) {
2789 var self = this, attrs, i, undef;
2790
2791 if (typeof name !== "string") {
2792 for (i in name)
2793 self.attr(i, name[i]);
2794
2795 return self;
2796 }
2797
2798 if (attrs = self.attributes) {
2799 if (value !== undef) {
2800 // Remove attribute
2801 if (value === null) {
2802 if (name in attrs.map) {
2803 delete attrs.map[name];
2804
2805 i = attrs.length;
2806 while (i--) {
2807 if (attrs[i].name === name) {
2808 attrs = attrs.splice(i, 1);
2809 return self;
2810 }
2811 }
2812 }
2813
2814 return self;
2815 }
2816
2817 // Set attribute
2818 if (name in attrs.map) {
2819 // Set attribute
2820 i = attrs.length;
2821 while (i--) {
2822 if (attrs[i].name === name) {
2823 attrs[i].value = value;
2824 break;
2825 }
2826 }
2827 } else
2828 attrs.push({name: name, value: value});
2829
2830 attrs.map[name] = value;
2831
2832 return self;
2833 } else {
2834 return attrs.map[name];
2835 }
2836 }
2837 },
2838
2839 clone : function() {
2840 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2841
2842 // Clone element attributes
2843 if (selfAttrs = self.attributes) {
2844 cloneAttrs = [];
2845 cloneAttrs.map = {};
2846
2847 for (i = 0, l = selfAttrs.length; i < l; i++) {
2848 selfAttr = selfAttrs[i];
2849
2850 // Clone everything except id
2851 if (selfAttr.name !== 'id') {
2852 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2853 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2854 }
2855 }
2856
2857 clone.attributes = cloneAttrs;
2858 }
2859
2860 clone.value = self.value;
2861 clone.shortEnded = self.shortEnded;
2862
2863 return clone;
2864 },
2865
2866 wrap : function(wrapper) {
2867 var self = this;
2868
2869 self.parent.insert(wrapper, self);
2870 wrapper.append(self);
2871
2872 return self;
2873 },
2874
2875 unwrap : function() {
2876 var self = this, node, next;
2877
2878 for (node = self.firstChild; node; ) {
2879 next = node.next;
2880 self.insert(node, self, true);
2881 node = next;
2882 }
2883
2884 self.remove();
2885 },
2886
2887 remove : function() {
2888 var self = this, parent = self.parent, next = self.next, prev = self.prev;
2889
2890 if (parent) {
2891 if (parent.firstChild === self) {
2892 parent.firstChild = next;
2893
2894 if (next)
2895 next.prev = null;
2896 } else {
2897 prev.next = next;
2898 }
2899
2900 if (parent.lastChild === self) {
2901 parent.lastChild = prev;
2902
2903 if (prev)
2904 prev.next = null;
2905 } else {
2906 next.prev = prev;
2907 }
2908
2909 self.parent = self.next = self.prev = null;
2910 }
2911
2912 return self;
2913 },
2914
2915 append : function(node) {
2916 var self = this, last;
2917
2918 if (node.parent)
2919 node.remove();
2920
2921 last = self.lastChild;
2922 if (last) {
2923 last.next = node;
2924 node.prev = last;
2925 self.lastChild = node;
2926 } else
2927 self.lastChild = self.firstChild = node;
2928
2929 node.parent = self;
2930
2931 return node;
2932 },
2933
2934 insert : function(node, ref_node, before) {
2935 var parent;
2936
2937 if (node.parent)
2938 node.remove();
2939
2940 parent = ref_node.parent || this;
2941
2942 if (before) {
2943 if (ref_node === parent.firstChild)
2944 parent.firstChild = node;
2945 else
2946 ref_node.prev.next = node;
2947
2948 node.prev = ref_node.prev;
2949 node.next = ref_node;
2950 ref_node.prev = node;
2951 } else {
2952 if (ref_node === parent.lastChild)
2953 parent.lastChild = node;
2954 else
2955 ref_node.next.prev = node;
2956
2957 node.next = ref_node.next;
2958 node.prev = ref_node;
2959 ref_node.next = node;
2960 }
2961
2962 node.parent = parent;
2963
2964 return node;
2965 },
2966
2967 getAll : function(name) {
2968 var self = this, node, collection = [];
2969
2970 for (node = self.firstChild; node; node = walk(node, self)) {
2971 if (node.name === name)
2972 collection.push(node);
2973 }
2974
2975 return collection;
2976 },
2977
2978 empty : function() {
2979 var self = this, nodes, i, node;
2980
2981 // Remove all children
2982 if (self.firstChild) {
2983 nodes = [];
2984
2985 // Collect the children
2986 for (node = self.firstChild; node; node = walk(node, self))
2987 nodes.push(node);
2988
2989 // Remove the children
2990 i = nodes.length;
2991 while (i--) {
2992 node = nodes[i];
2993 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2994 }
2995 }
2996
2997 self.firstChild = self.lastChild = null;
2998
2999 return self;
3000 },
3001
3002 isEmpty : function(elements) {
3003 var self = this, node = self.firstChild, i, name;
3004
3005 if (node) {
3006 do {
3007 if (node.type === 1) {
3008 // Ignore bogus elements
3009 if (node.attributes.map['data-mce-bogus'])
3010 continue;
3011
3012 // Keep empty elements like <img />
3013 if (elements[node.name])
3014 return false;
3015
3016 // Keep elements with data attributes or name attribute like <a name="1"></a>
3017 i = node.attributes.length;
3018 while (i--) {
3019 name = node.attributes[i].name;
3020 if (name === "name" || name.indexOf('data-') === 0)
3021 return false;
3022 }
3023 }
3024
3025 // Keep comments
3026 if (node.type === 8)
3027 return false;
3028
3029 // Keep non whitespace text nodes
3030 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3031 return false;
3032 } while (node = walk(node, self));
3033 }
3034
3035 return true;
3036 },
3037
3038 walk : function(prev) {
3039 return walk(this, null, prev);
3040 }
3041 });
3042
3043 tinymce.extend(Node, {
3044 create : function(name, attrs) {
3045 var node, attrName;
3046
3047 // Create node
3048 node = new Node(name, typeLookup[name] || 1);
3049
3050 // Add attributes if needed
3051 if (attrs) {
3052 for (attrName in attrs)
3053 node.attr(attrName, attrs[attrName]);
3054 }
3055
3056 return node;
3057 }
3058 });
3059
3060 tinymce.html.Node = Node;
3061 })(tinymce);
3062
3063 (function(tinymce) {
3064 var Node = tinymce.html.Node;
3065
3066 tinymce.html.DomParser = function(settings, schema) {
3067 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3068
3069 settings = settings || {};
3070 settings.validate = "validate" in settings ? settings.validate : true;
3071 settings.root_name = settings.root_name || 'body';
3072 self.schema = schema = schema || new tinymce.html.Schema();
3073
3074 function fixInvalidChildren(nodes) {
3075 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3076 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3077
3078 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3079 nonEmptyElements = schema.getNonEmptyElements();
3080
3081 for (ni = 0; ni < nodes.length; ni++) {
3082 node = nodes[ni];
3083
3084 // Already removed
3085 if (!node.parent)
3086 continue;
3087
3088 // Get list of all parent nodes until we find a valid parent to stick the child into
3089 parents = [node];
3090 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3091 parents.push(parent);
3092
3093 // Found a suitable parent
3094 if (parent && parents.length > 1) {
3095 // Reverse the array since it makes looping easier
3096 parents.reverse();
3097
3098 // Clone the related parent and insert that after the moved node
3099 newParent = currentNode = self.filterNode(parents[0].clone());
3100
3101 // Start cloning and moving children on the left side of the target node
3102 for (i = 0; i < parents.length - 1; i++) {
3103 if (schema.isValidChild(currentNode.name, parents[i].name)) {
3104 tempNode = self.filterNode(parents[i].clone());
3105 currentNode.append(tempNode);
3106 } else
3107 tempNode = currentNode;
3108
3109 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3110 nextNode = childNode.next;
3111 tempNode.append(childNode);
3112 childNode = nextNode;
3113 }
3114
3115 currentNode = tempNode;
3116 }
3117
3118 if (!newParent.isEmpty(nonEmptyElements)) {
3119 parent.insert(newParent, parents[0], true);
3120 parent.insert(node, newParent);
3121 } else {
3122 parent.insert(node, parents[0], true);
3123 }
3124
3125 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3126 parent = parents[0];
3127 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3128 parent.empty().remove();
3129 }
3130 } else if (node.parent) {
3131 // If it's an LI try to find a UL/OL for it or wrap it
3132 if (node.name === 'li') {
3133 sibling = node.prev;
3134 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3135 sibling.append(node);
3136 continue;
3137 }
3138
3139 sibling = node.next;
3140 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3141 sibling.insert(node, sibling.firstChild, true);
3142 continue;
3143 }
3144
3145 node.wrap(self.filterNode(new Node('ul', 1)));
3146 continue;
3147 }
3148
3149 // Try wrapping the element in a DIV
3150 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3151 node.wrap(self.filterNode(new Node('div', 1)));
3152 } else {
3153 // We failed wrapping it, then remove or unwrap it
3154 if (node.name === 'style' || node.name === 'script')
3155 node.empty().remove();
3156 else
3157 node.unwrap();
3158 }
3159 }
3160 }
3161 };
3162
3163 self.filterNode = function(node) {
3164 var i, name, list;
3165
3166 // Run element filters
3167 if (name in nodeFilters) {
3168 list = matchedNodes[name];
3169
3170 if (list)
3171 list.push(node);
3172 else
3173 matchedNodes[name] = [node];
3174 }
3175
3176 // Run attribute filters
3177 i = attributeFilters.length;
3178 while (i--) {
3179 name = attributeFilters[i].name;
3180
3181 if (name in node.attributes.map) {
3182 list = matchedAttributes[name];
3183
3184 if (list)
3185 list.push(node);
3186 else
3187 matchedAttributes[name] = [node];
3188 }
3189 }
3190
3191 return node;
3192 };
3193
3194 self.addNodeFilter = function(name, callback) {
3195 tinymce.each(tinymce.explode(name), function(name) {
3196 var list = nodeFilters[name];
3197
3198 if (!list)
3199 nodeFilters[name] = list = [];
3200
3201 list.push(callback);
3202 });
3203 };
3204
3205 self.addAttributeFilter = function(name, callback) {
3206 tinymce.each(tinymce.explode(name), function(name) {
3207 var i;
3208
3209 for (i = 0; i < attributeFilters.length; i++) {
3210 if (attributeFilters[i].name === name) {
3211 attributeFilters[i].callbacks.push(callback);
3212 return;
3213 }
3214 }
3215
3216 attributeFilters.push({name: name, callbacks: [callback]});
3217 });
3218 };
3219
3220 self.parse = function(html, args) {
3221 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3222 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3223 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3224
3225 args = args || {};
3226 matchedNodes = {};
3227 matchedAttributes = {};
3228 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3229 nonEmptyElements = schema.getNonEmptyElements();
3230 children = schema.children;
3231 validate = settings.validate;
3232 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3233
3234 whiteSpaceElements = schema.getWhiteSpaceElements();
3235 startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3236 endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3237 allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3238
3239 function addRootBlocks() {
3240 var node = rootNode.firstChild, next, rootBlockNode;
3241
3242 while (node) {
3243 next = node.next;
3244
3245 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3246 if (!rootBlockNode) {
3247 // Create a new root block element
3248 rootBlockNode = createNode(rootBlockName, 1);
3249 rootNode.insert(rootBlockNode, node);
3250 rootBlockNode.append(node);
3251 } else
3252 rootBlockNode.append(node);
3253 } else {
3254 rootBlockNode = null;
3255 }
3256
3257 node = next;
3258 };
3259 };
3260
3261 function createNode(name, type) {
3262 var node = new Node(name, type), list;
3263
3264 if (name in nodeFilters) {
3265 list = matchedNodes[name];
3266
3267 if (list)
3268 list.push(node);
3269 else
3270 matchedNodes[name] = [node];
3271 }
3272
3273 return node;
3274 };
3275
3276 function removeWhitespaceBefore(node) {
3277 var textNode, textVal, sibling;
3278
3279 for (textNode = node.prev; textNode && textNode.type === 3; ) {
3280 textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3281
3282 if (textVal.length > 0) {
3283 textNode.value = textVal;
3284 textNode = textNode.prev;
3285 } else {
3286 sibling = textNode.prev;
3287 textNode.remove();
3288 textNode = sibling;
3289 }
3290 }
3291 };
3292
3293 parser = new tinymce.html.SaxParser({
3294 validate : validate,
3295 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
3296
3297 cdata: function(text) {
3298 node.append(createNode('#cdata', 4)).value = text;
3299 },
3300
3301 text: function(text, raw) {
3302 var textNode;
3303
3304 // Trim all redundant whitespace on non white space elements
3305 if (!isInWhiteSpacePreservedElement) {
3306 text = text.replace(allWhiteSpaceRegExp, ' ');
3307
3308 if (node.lastChild && blockElements[node.lastChild.name])
3309 text = text.replace(startWhiteSpaceRegExp, '');
3310 }
3311
3312 // Do we need to create the node
3313 if (text.length !== 0) {
3314 textNode = createNode('#text', 3);
3315 textNode.raw = !!raw;
3316 node.append(textNode).value = text;
3317 }
3318 },
3319
3320 comment: function(text) {
3321 node.append(createNode('#comment', 8)).value = text;
3322 },
3323
3324 pi: function(name, text) {
3325 node.append(createNode(name, 7)).value = text;
3326 removeWhitespaceBefore(node);
3327 },
3328
3329 doctype: function(text) {
3330 var newNode;
3331
3332 newNode = node.append(createNode('#doctype', 10));
3333 newNode.value = text;
3334 removeWhitespaceBefore(node);
3335 },
3336
3337 start: function(name, attrs, empty) {
3338 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3339
3340 elementRule = validate ? schema.getElementRule(name) : {};
3341 if (elementRule) {
3342 newNode = createNode(elementRule.outputName || name, 1);
3343 newNode.attributes = attrs;
3344 newNode.shortEnded = empty;
3345
3346 node.append(newNode);
3347
3348 // Check if node is valid child of the parent node is the child is
3349 // unknown we don't collect it since it's probably a custom element
3350 parent = children[node.name];
3351 if (parent && children[newNode.name] && !parent[newNode.name])
3352 invalidChildren.push(newNode);
3353
3354 attrFiltersLen = attributeFilters.length;
3355 while (attrFiltersLen--) {
3356 attrName = attributeFilters[attrFiltersLen].name;
3357
3358 if (attrName in attrs.map) {
3359 list = matchedAttributes[attrName];
3360
3361 if (list)
3362 list.push(newNode);
3363 else
3364 matchedAttributes[attrName] = [newNode];
3365 }
3366 }
3367
3368 // Trim whitespace before block
3369 if (blockElements[name])
3370 removeWhitespaceBefore(newNode);
3371
3372 // Change current node if the element wasn't empty i.e not <br /> or <img />
3373 if (!empty)
3374 node = newNode;
3375
3376 // Check if we are inside a whitespace preserved element
3377 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3378 isInWhiteSpacePreservedElement = true;
3379 }
3380 }
3381 },
3382
3383 end: function(name) {
3384 var textNode, elementRule, text, sibling, tempNode;
3385
3386 elementRule = validate ? schema.getElementRule(name) : {};
3387 if (elementRule) {
3388 if (blockElements[name]) {
3389 if (!isInWhiteSpacePreservedElement) {
3390 // Trim whitespace at beginning of block
3391 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
3392 text = textNode.value.replace(startWhiteSpaceRegExp, '');
3393
3394 if (text.length > 0) {
3395 textNode.value = text;
3396 textNode = textNode.next;
3397 } else {
3398 sibling = textNode.next;
3399 textNode.remove();
3400 textNode = sibling;
3401 }
3402 }
3403
3404 // Trim whitespace at end of block
3405 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
3406 text = textNode.value.replace(endWhiteSpaceRegExp, '');
3407
3408 if (text.length > 0) {
3409 textNode.value = text;
3410 textNode = textNode.prev;
3411 } else {
3412 sibling = textNode.prev;
3413 textNode.remove();
3414 textNode = sibling;
3415 }
3416 }
3417 }
3418
3419 // Trim start white space
3420 textNode = node.prev;
3421 if (textNode && textNode.type === 3) {
3422 text = textNode.value.replace(startWhiteSpaceRegExp, '');
3423
3424 if (text.length > 0)
3425 textNode.value = text;
3426 else
3427 textNode.remove();
3428 }
3429 }
3430
3431 // Check if we exited a whitespace preserved element
3432 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3433 isInWhiteSpacePreservedElement = false;
3434 }
3435
3436 // Handle empty nodes
3437 if (elementRule.removeEmpty || elementRule.paddEmpty) {
3438 if (node.isEmpty(nonEmptyElements)) {
3439 if (elementRule.paddEmpty)
3440 node.empty().append(new Node('#text', '3')).value = '\u00a0';
3441 else {
3442 // Leave nodes that have a name like <a name="name">
3443 if (!node.attributes.map.name) {
3444 tempNode = node.parent;
3445 node.empty().remove();
3446 node = tempNode;
3447 return;
3448 }
3449 }
3450 }
3451 }
3452
3453 node = node.parent;
3454 }
3455 }
3456 }, schema);
3457
3458 rootNode = node = new Node(args.context || settings.root_name, 11);
3459
3460 parser.parse(html);
3461
3462 // Fix invalid children or report invalid children in a contextual parsing
3463 if (validate && invalidChildren.length) {
3464 if (!args.context)
3465 fixInvalidChildren(invalidChildren);
3466 else
3467 args.invalid = true;
3468 }
3469
3470 // Wrap nodes in the root into block elements if the root is body
3471 if (rootBlockName && rootNode.name == 'body')
3472 addRootBlocks();
3473
3474 // Run filters only when the contents is valid
3475 if (!args.invalid) {
3476 // Run node filters
3477 for (name in matchedNodes) {
3478 list = nodeFilters[name];
3479 nodes = matchedNodes[name];
3480
3481 // Remove already removed children
3482 fi = nodes.length;
3483 while (fi--) {
3484 if (!nodes[fi].parent)
3485 nodes.splice(fi, 1);
3486 }
3487
3488 for (i = 0, l = list.length; i < l; i++)
3489 list[i](nodes, name, args);
3490 }
3491
3492 // Run attribute filters
3493 for (i = 0, l = attributeFilters.length; i < l; i++) {
3494 list = attributeFilters[i];
3495
3496 if (list.name in matchedAttributes) {
3497 nodes = matchedAttributes[list.name];
3498
3499 // Remove already removed children
3500 fi = nodes.length;
3501 while (fi--) {
3502 if (!nodes[fi].parent)
3503 nodes.splice(fi, 1);
3504 }
3505
3506 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3507 list.callbacks[fi](nodes, list.name, args);
3508 }
3509 }
3510 }
3511
3512 return rootNode;
3513 };
3514
3515 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
3516 // make it possible to place the caret inside empty blocks. This logic tries to remove
3517 // these elements and keep br elements that where intended to be there intact
3518 if (settings.remove_trailing_brs) {
3519 self.addNodeFilter('br', function(nodes, name) {
3520 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
3521 nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
3522
3523 // Remove brs from body element as well
3524 blockElements.body = 1;
3525
3526 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
3527 for (i = 0; i < l; i++) {
3528 node = nodes[i];
3529 parent = node.parent;
3530
3531 if (blockElements[node.parent.name] && node === parent.lastChild) {
3532 // Loop all nodes to the right of the current node and check for other BR elements
3533 // excluding bookmarks since they are invisible
3534 prev = node.prev;
3535 while (prev) {
3536 prevName = prev.name;
3537
3538 // Ignore bookmarks
3539 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
3540 // Found a non BR element
3541 if (prevName !== "br")
3542 break;
3543
3544 // Found another br it's a <br><br> structure then don't remove anything
3545 if (prevName === 'br') {
3546 node = null;
3547 break;
3548 }
3549 }
3550
3551 prev = prev.prev;
3552 }
3553
3554 if (node) {
3555 node.remove();
3556
3557 // Is the parent to be considered empty after we removed the BR
3558 if (parent.isEmpty(nonEmptyElements)) {
3559 elementRule = schema.getElementRule(parent.name);
3560
3561 // Remove or padd the element depending on schema rule
3562 if (elementRule) {
3563 if (elementRule.removeEmpty)
3564 parent.remove();
3565 else if (elementRule.paddEmpty)
3566 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3567 }
3568 }
3569 }
3570 }
3571 }
3572 });
3573 }
3574 }
3575 })(tinymce);
3576
3577 tinymce.html.Writer = function(settings) {
3578 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3579
3580 settings = settings || {};
3581 indent = settings.indent;
3582 indentBefore = tinymce.makeMap(settings.indent_before || '');
3583 indentAfter = tinymce.makeMap(settings.indent_after || '');
3584 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3585 htmlOutput = settings.element_format == "html";
3586
3587 return {
3588 start: function(name, attrs, empty) {
3589 var i, l, attr, value;
3590
3591 if (indent && indentBefore[name] && html.length > 0) {
3592 value = html[html.length - 1];
3593
3594 if (value.length > 0 && value !== '\n')
3595 html.push('\n');
3596 }
3597
3598 html.push('<', name);
3599
3600 if (attrs) {
3601 for (i = 0, l = attrs.length; i < l; i++) {
3602 attr = attrs[i];
3603 html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3604 }
3605 }
3606
3607 if (!empty || htmlOutput)
3608 html[html.length] = '>';
3609 else
3610 html[html.length] = ' />';
3611
3612 if (empty && indent && indentAfter[name] && html.length > 0) {
3613 value = html[html.length - 1];
3614
3615 if (value.length > 0 && value !== '\n')
3616 html.push('\n');
3617 }
3618 },
3619
3620 end: function(name) {
3621 var value;
3622
3623 /*if (indent && indentBefore[name] && html.length > 0) {
3624 value = html[html.length - 1];
3625
3626 if (value.length > 0 && value !== '\n')
3627 html.push('\n');
3628 }*/
3629
3630 html.push('</', name, '>');
3631
3632 if (indent && indentAfter[name] && html.length > 0) {
3633 value = html[html.length - 1];
3634
3635 if (value.length > 0 && value !== '\n')
3636 html.push('\n');
3637 }
3638 },
3639
3640 text: function(text, raw) {
3641 if (text.length > 0)
3642 html[html.length] = raw ? text : encode(text);
3643 },
3644
3645 cdata: function(text) {
3646 html.push('<![CDATA[', text, ']]>');
3647 },
3648
3649 comment: function(text) {
3650 html.push('<!--', text, '-->');
3651 },
3652
3653 pi: function(name, text) {
3654 if (text)
3655 html.push('<?', name, ' ', text, '?>');
3656 else
3657 html.push('<?', name, '?>');
3658
3659 if (indent)
3660 html.push('\n');
3661 },
3662
3663 doctype: function(text) {
3664 html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3665 },
3666
3667 reset: function() {
3668 html.length = 0;
3669 },
3670
3671 getContent: function() {
3672 return html.join('').replace(/\n$/, '');
3673 }
3674 };
3675 };
3676
3677 (function(tinymce) {
3678 tinymce.html.Serializer = function(settings, schema) {
3679 var self = this, writer = new tinymce.html.Writer(settings);
3680
3681 settings = settings || {};
3682 settings.validate = "validate" in settings ? settings.validate : true;
3683
3684 self.schema = schema = schema || new tinymce.html.Schema();
3685 self.writer = writer;
3686
3687 self.serialize = function(node) {
3688 var handlers, validate;
3689
3690 validate = settings.validate;
3691
3692 handlers = {
3693 // #text
3694 3: function(node, raw) {
3695 writer.text(node.value, node.raw);
3696 },
3697
3698 // #comment
3699 8: function(node) {
3700 writer.comment(node.value);
3701 },
3702
3703 // Processing instruction
3704 7: function(node) {
3705 writer.pi(node.name, node.value);
3706 },
3707
3708 // Doctype
3709 10: function(node) {
3710 writer.doctype(node.value);
3711 },
3712
3713 // CDATA
3714 4: function(node) {
3715 writer.cdata(node.value);
3716 },
3717
3718 // Document fragment
3719 11: function(node) {
3720 if ((node = node.firstChild)) {
3721 do {
3722 walk(node);
3723 } while (node = node.next);
3724 }
3725 }
3726 };
3727
3728 writer.reset();
3729
3730 function walk(node) {
3731 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3732
3733 if (!handler) {
3734 name = node.name;
3735 isEmpty = node.shortEnded;
3736 attrs = node.attributes;
3737
3738 // Sort attributes
3739 if (validate && attrs && attrs.length > 1) {
3740 sortedAttrs = [];
3741 sortedAttrs.map = {};
3742
3743 elementRule = schema.getElementRule(node.name);
3744 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3745 attrName = elementRule.attributesOrder[i];
3746
3747 if (attrName in attrs.map) {
3748 attrValue = attrs.map[attrName];
3749 sortedAttrs.map[attrName] = attrValue;
3750 sortedAttrs.push({name: attrName, value: attrValue});
3751 }
3752 }
3753
3754 for (i = 0, l = attrs.length; i < l; i++) {
3755 attrName = attrs[i].name;
3756
3757 if (!(attrName in sortedAttrs.map)) {
3758 attrValue = attrs.map[attrName];
3759 sortedAttrs.map[attrName] = attrValue;
3760 sortedAttrs.push({name: attrName, value: attrValue});
3761 }
3762 }
3763
3764 attrs = sortedAttrs;
3765 }
3766
3767 writer.start(node.name, attrs, isEmpty);
3768
3769 if (!isEmpty) {
3770 if ((node = node.firstChild)) {
3771 do {
3772 walk(node);
3773 } while (node = node.next);
3774 }
3775
3776 writer.end(name);
3777 }
3778 } else
3779 handler(node);
3780 }
3781
3782 // Serialize element and treat all non elements as fragments
3783 if (node.type == 1 && !settings.inner)
3784 walk(node);
3785 else
3786 handlers[11](node);
3787
3788 return writer.getContent();
3789 };
3790 }
3791 })(tinymce);
3792
3793 // JSLint defined globals
3794 /*global tinymce:false, window:false */
3795
3796 tinymce.dom = {};
3797
3798 (function(namespace, expando) {
3799 var w3cEventModel = !!document.addEventListener;
3800
3801 function addEvent(target, name, callback, capture) {
3802 if (target.addEventListener) {
3803 target.addEventListener(name, callback, capture || false);
3804 } else if (target.attachEvent) {
3805 target.attachEvent('on' + name, callback);
3806 }
3807 }
3808
3809 function removeEvent(target, name, callback, capture) {
3810 if (target.removeEventListener) {
3811 target.removeEventListener(name, callback, capture || false);
3812 } else if (target.detachEvent) {
3813 target.detachEvent('on' + name, callback);
3814 }
3815 }
3816
3817 function fix(original_event, data) {
3818 var name, event = data || {};
3819
3820 // Dummy function that gets replaced on the delegation state functions
3821 function returnFalse() {
3822 return false;
3823 }
3824
3825 // Dummy function that gets replaced on the delegation state functions
3826 function returnTrue() {
3827 return true;
3828 }
3829
3830 // Copy all properties from the original event
3831 for (name in original_event) {
3832 // layerX/layerY is deprecated in Chrome and produces a warning
3833 if (name !== "layerX" && name !== "layerY") {
3834 event[name] = original_event[name];
3835 }
3836 }
3837
3838 // Normalize target IE uses srcElement
3839 if (!event.target) {
3840 event.target = event.srcElement || document;
3841 }
3842
3843 // Add preventDefault method
3844 event.preventDefault = function() {
3845 event.isDefaultPrevented = returnTrue;
3846
3847 // Execute preventDefault on the original event object
3848 if (original_event) {
3849 if (original_event.preventDefault) {
3850 original_event.preventDefault();
3851 } else {
3852 original_event.returnValue = false; // IE
3853 }
3854 }
3855 };
3856
3857 // Add stopPropagation
3858 event.stopPropagation = function() {
3859 event.isPropagationStopped = returnTrue;
3860
3861 // Execute stopPropagation on the original event object
3862 if (original_event) {
3863 if (original_event.stopPropagation) {
3864 original_event.stopPropagation();
3865 } else {
3866 original_event.cancelBubble = true; // IE
3867 }
3868 }
3869 };
3870
3871 // Add stopImmediatePropagation
3872 event.stopImmediatePropagation = function() {
3873 event.isImmediatePropagationStopped = returnTrue;
3874 event.stopPropagation();
3875 };
3876
3877 // Add event delegation states
3878 if (!event.isDefaultPrevented) {
3879 event.isDefaultPrevented = returnFalse;
3880 event.isPropagationStopped = returnFalse;
3881 event.isImmediatePropagationStopped = returnFalse;
3882 }
3883
3884 return event;
3885 }
3886
3887 function bindOnReady(win, callback, event_utils) {
3888 var doc = win.document, event = {type: 'ready'};
3889
3890 // Gets called when the DOM is ready
3891 function readyHandler() {
3892 if (!event_utils.domLoaded) {
3893 event_utils.domLoaded = true;
3894 callback(event);
3895 }
3896 }
3897
3898 // Use W3C method
3899 if (w3cEventModel) {
3900 addEvent(win, 'DOMContentLoaded', readyHandler);
3901 } else {
3902 // Use IE method
3903 addEvent(doc, "readystatechange", function() {
3904 if (doc.readyState === "complete") {
3905 removeEvent(doc, "readystatechange", arguments.callee);
3906 readyHandler();
3907 }
3908 });
3909
3910 // Wait until we can scroll, when we can the DOM is initialized
3911 if (doc.documentElement.doScroll && win === win.top) {
3912 (function() {
3913 try {
3914 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
3915 // http://javascript.nwbox.com/IEContentLoaded/
3916 doc.documentElement.doScroll("left");
3917 } catch (ex) {
3918 setTimeout(arguments.callee, 0);
3919 return;
3920 }
3921
3922 readyHandler();
3923 })();
3924 }
3925 }
3926
3927 // Fallback if any of the above methods should fail for some odd reason
3928 addEvent(win, 'load', readyHandler);
3929 }
3930
3931 function EventUtils(proxy) {
3932 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
3933
3934 hasMouseEnterLeave = "onmouseenter" in document.documentElement;
3935 hasFocusIn = "onfocusin" in document.documentElement;
3936 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
3937 count = 1;
3938
3939 // State if the DOMContentLoaded was executed or not
3940 self.domLoaded = false;
3941 self.events = events;
3942
3943 function executeHandlers(evt, id) {
3944 var callbackList, i, l, callback;
3945
3946 callbackList = events[id][evt.type];
3947 if (callbackList) {
3948 for (i = 0, l = callbackList.length; i < l; i++) {
3949 callback = callbackList[i];
3950
3951 // Check if callback exists might be removed if a unbind is called inside the callback
3952 if (callback && callback.func.call(callback.scope, evt) === false) {
3953 evt.preventDefault();
3954 }
3955
3956 // Should we stop propagation to immediate listeners
3957 if (evt.isImmediatePropagationStopped()) {
3958 return;
3959 }
3960 }
3961 }
3962 }
3963
3964 self.bind = function(target, names, callback, scope) {
3965 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
3966
3967 // Native event handler function patches the event and executes the callbacks for the expando
3968 function defaultNativeHandler(evt) {
3969 executeHandlers(fix(evt || win.event), id);
3970 }
3971
3972 // Don't bind to text nodes or comments
3973 if (!target || target.nodeType === 3 || target.nodeType === 8) {
3974 return;
3975 }
3976
3977 // Create or get events id for the target
3978 if (!target[expando]) {
3979 id = count++;
3980 target[expando] = id;
3981 events[id] = {};
3982 } else {
3983 id = target[expando];
3984
3985 if (!events[id]) {
3986 events[id] = {};
3987 }
3988 }
3989
3990 // Setup the specified scope or use the target as a default
3991 scope = scope || target;
3992
3993 // Split names and bind each event, enables you to bind multiple events with one call
3994 names = names.split(' ');
3995 i = names.length;
3996 while (i--) {
3997 name = names[i];
3998 nativeHandler = defaultNativeHandler;
3999 fakeName = capture = false;
4000
4001 // Use ready instead of DOMContentLoaded
4002 if (name === "DOMContentLoaded") {
4003 name = "ready";
4004 }
4005
4006 // DOM is already ready
4007 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4008 self.domLoaded = true;
4009 callback.call(scope, fix({type: name}));
4010 continue;
4011 }
4012
4013 // Handle mouseenter/mouseleaver
4014 if (!hasMouseEnterLeave) {
4015 fakeName = mouseEnterLeave[name];
4016
4017 if (fakeName) {
4018 nativeHandler = function(evt) {
4019 var current, related;
4020
4021 current = evt.currentTarget;
4022 related = evt.relatedTarget;
4023
4024 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4025 if (related && current.contains) {
4026 // Use contains for performance
4027 related = current.contains(related);
4028 } else {
4029 while (related && related !== current) {
4030 related = related.parentNode;
4031 }
4032 }
4033
4034 // Fire fake event
4035 if (!related) {
4036 evt = fix(evt || win.event);
4037 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4038 evt.target = current;
4039 executeHandlers(evt, id);
4040 }
4041 };
4042 }
4043 }
4044
4045 // Fake bubbeling of focusin/focusout
4046 if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4047 capture = true;
4048 fakeName = name === "focusin" ? "focus" : "blur";
4049 nativeHandler = function(evt) {
4050 evt = fix(evt || win.event);
4051 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4052 executeHandlers(evt, id);
4053 };
4054 }
4055
4056 // Setup callback list and bind native event
4057 callbackList = events[id][name];
4058 if (!callbackList) {
4059 events[id][name] = callbackList = [{func: callback, scope: scope}];
4060 callbackList.fakeName = fakeName;
4061 callbackList.capture = capture;
4062
4063 // Add the nativeHandler to the callback list so that we can later unbind it
4064 callbackList.nativeHandler = nativeHandler;
4065 if (!w3cEventModel) {
4066 callbackList.proxyHandler = proxy(id);
4067 }
4068
4069 // Check if the target has native events support
4070 if (name === "ready") {
4071 bindOnReady(target, nativeHandler, self);
4072 } else {
4073 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4074 }
4075 } else {
4076 // If it already has an native handler then just push the callback
4077 callbackList.push({func: callback, scope: scope});
4078 }
4079 }
4080
4081 target = callbackList = 0; // Clean memory for IE
4082
4083 return callback;
4084 };
4085
4086 self.unbind = function(target, names, callback) {
4087 var id, callbackList, i, ci, name, eventMap;
4088
4089 // Don't bind to text nodes or comments
4090 if (!target || target.nodeType === 3 || target.nodeType === 8) {
4091 return self;
4092 }
4093
4094 // Unbind event or events if the target has the expando
4095 id = target[expando];
4096 if (id) {
4097 eventMap = events[id];
4098
4099 // Specific callback
4100 if (names) {
4101 names = names.split(' ');
4102 i = names.length;
4103 while (i--) {
4104 name = names[i];
4105 callbackList = eventMap[name];
4106
4107 // Unbind the event if it exists in the map
4108 if (callbackList) {
4109 // Remove specified callback
4110 if (callback) {
4111 ci = callbackList.length;
4112 while (ci--) {
4113 if (callbackList[ci].func === callback) {
4114 callbackList.splice(ci, 1);
4115 }
4116 }
4117 }
4118
4119 // Remove all callbacks if there isn't a specified callback or there is no callbacks left
4120 if (!callback || callbackList.length === 0) {
4121 delete eventMap[name];
4122 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4123 }
4124 }
4125 }
4126 } else {
4127 // All events for a specific element
4128 for (name in eventMap) {
4129 callbackList = eventMap[name];
4130 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4131 }
4132
4133 eventMap = {};
4134 }
4135
4136 // Check if object is empty, if it isn't then we won't remove the expando map
4137 for (name in eventMap) {
4138 return self;
4139 }
4140
4141 // Delete event object
4142 delete events[id];
4143
4144 // Remove expando from target
4145 try {
4146 // IE will fail here since it can't delete properties from window
4147 delete target[expando];
4148 } catch (ex) {
4149 // IE will set it to null
4150 target[expando] = null;
4151 }
4152 }
4153
4154 return self;
4155 };
4156
4157 self.fire = function(target, name, args) {
4158 var id, event;
4159
4160 // Don't bind to text nodes or comments
4161 if (!target || target.nodeType === 3 || target.nodeType === 8) {
4162 return self;
4163 }
4164
4165 // Build event object by patching the args
4166 event = fix(null, args);
4167 event.type = name;
4168
4169 do {
4170 // Found an expando that means there is listeners to execute
4171 id = target[expando];
4172 if (id) {
4173 executeHandlers(event, id);
4174 }
4175
4176 // Walk up the DOM
4177 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4178 } while (target && !event.isPropagationStopped());
4179
4180 return self;
4181 };
4182
4183 self.clean = function(target) {
4184 var i, children, unbind = self.unbind;
4185
4186 // Don't bind to text nodes or comments
4187 if (!target || target.nodeType === 3 || target.nodeType === 8) {
4188 return self;
4189 }
4190
4191 // Unbind any element on the specificed target
4192 if (target[expando]) {
4193 unbind(target);
4194 }
4195
4196 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4197 if (!target.getElementsByTagName) {
4198 target = target.document;
4199 }
4200
4201 // Remove events from each child element
4202 if (target && target.getElementsByTagName) {
4203 unbind(target);
4204
4205 children = target.getElementsByTagName('*');
4206 i = children.length;
4207 while (i--) {
4208 target = children[i];
4209
4210 if (target[expando]) {
4211 unbind(target);
4212 }
4213 }
4214 }
4215
4216 return self;
4217 };
4218
4219 self.callNativeHandler = function(id, evt) {
4220 if (events) {
4221 events[id][evt.type].nativeHandler(evt);
4222 }
4223 };
4224
4225 self.destory = function() {
4226 events = {};
4227 };
4228
4229 // Legacy function calls
4230
4231 self.add = function(target, events, func, scope) {
4232 // Old API supported direct ID assignment
4233 if (typeof(target) === "string") {
4234 target = document.getElementById(target);
4235 }
4236
4237 // Old API supported multiple targets
4238 if (target && target instanceof Array) {
4239 var i = target;
4240
4241 while (i--) {
4242 self.add(target[i], events, func, scope);
4243 }
4244
4245 return;
4246 }
4247
4248 // Old API called ready init
4249 if (events === "init") {
4250 events = "ready";
4251 }
4252
4253 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4254 };
4255
4256 self.remove = function(target, events, func) {
4257 // Old API supported direct ID assignment
4258 if (typeof(target) === "string") {
4259 target = document.getElementById(target);
4260 }
4261
4262 // Old API supported multiple targets
4263 if (target instanceof Array) {
4264 var i = target;
4265
4266 while (i--) {
4267 self.remove(target[i], events, func, scope);
4268 }
4269
4270 return self;
4271 }
4272
4273 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
4274 };
4275
4276 self.clear = function(target) {
4277 // Old API supported direct ID assignment
4278 if (typeof(target) === "string") {
4279 target = document.getElementById(target);
4280 }
4281
4282 return self.clean(target);
4283 };
4284
4285 self.cancel = function(e) {
4286 if (e) {
4287 self.prevent(e);
4288 self.stop(e);
4289 }
4290
4291 return false;
4292 };
4293
4294 self.prevent = function(e) {
4295 e.preventDefault();
4296
4297 return false;
4298 };
4299
4300 self.stop = function(e) {
4301 e.stopPropagation();
4302
4303 return false;
4304 };
4305 }
4306
4307 namespace.EventUtils = EventUtils;
4308
4309 namespace.Event = new EventUtils(function(id) {
4310 return function(evt) {
4311 tinymce.dom.Event.callNativeHandler(id, evt);
4312 };
4313 });
4314
4315 // Bind ready event when tinymce script is loaded
4316 namespace.Event.bind(window, 'ready', function() {});
4317
4318 namespace = 0;
4319 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
4320
4321 (function(tinymce) {
4322 // Shorten names
4323 var each = tinymce.each,
4324 is = tinymce.is,
4325 isWebKit = tinymce.isWebKit,
4326 isIE = tinymce.isIE,
4327 Entities = tinymce.html.Entities,
4328 simpleSelectorRe = /^([a-z0-9],?)+$/i,
4329 whiteSpaceRegExp = /^[ \t\r\n]*$/;
4330
4331 tinymce.create('tinymce.dom.DOMUtils', {
4332 doc : null,
4333 root : null,
4334 files : null,
4335 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
4336 props : {
4337 "for" : "htmlFor",
4338 "class" : "className",
4339 className : "className",
4340 checked : "checked",
4341 disabled : "disabled",
4342 maxlength : "maxLength",
4343 readonly : "readOnly",
4344 selected : "selected",
4345 value : "value",
4346 id : "id",
4347 name : "name",
4348 type : "type"
4349 },
4350
4351 DOMUtils : function(d, s) {
4352 var t = this, globalStyle, name, blockElementsMap;
4353
4354 t.doc = d;
4355 t.win = window;
4356 t.files = {};
4357 t.cssFlicker = false;
4358 t.counter = 0;
4359 t.stdMode = !tinymce.isIE || d.documentMode >= 8;
4360 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
4361 t.hasOuterHTML = "outerHTML" in d.createElement("a");
4362
4363 t.settings = s = tinymce.extend({
4364 keep_values : false,
4365 hex_colors : 1
4366 }, s);
4367
4368 t.schema = s.schema;
4369 t.styles = new tinymce.html.Styles({
4370 url_converter : s.url_converter,
4371 url_converter_scope : s.url_converter_scope
4372 }, s.schema);
4373
4374 // Fix IE6SP2 flicker and check it failed for pre SP2
4375 if (tinymce.isIE6) {
4376 try {
4377 d.execCommand('BackgroundImageCache', false, true);
4378 } catch (e) {
4379 t.cssFlicker = true;
4380 }
4381 }
4382
4383 t.fixDoc(d);
4384 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
4385 tinymce.addUnload(t.destroy, t);
4386 blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
4387
4388 t.isBlock = function(node) {
4389 // This function is called in module pattern style since it might be executed with the wrong this scope
4390 var type = node.nodeType;
4391
4392 // If it's a node then check the type and use the nodeName
4393 if (type)
4394 return !!(type === 1 && blockElementsMap[node.nodeName]);
4395
4396 return !!blockElementsMap[node];
4397 };
4398 },
4399
4400 fixDoc: function(doc) {
4401 var settings = this.settings, name;
4402
4403 if (isIE && settings.schema) {
4404 // Add missing HTML 4/5 elements to IE
4405 ('abbr article aside audio canvas ' +
4406 'details figcaption figure footer ' +
4407 'header hgroup mark menu meter nav ' +
4408 'output progress section summary ' +
4409 'time video').replace(/\w+/g, function(name) {
4410 doc.createElement(name);
4411 });
4412
4413 // Create all custom elements
4414 for (name in settings.schema.getCustomElements()) {
4415 doc.createElement(name);
4416 }
4417 }
4418 },
4419
4420 clone: function(node, deep) {
4421 var self = this, clone, doc;
4422
4423 // TODO: Add feature detection here in the future
4424 if (!isIE || node.nodeType !== 1 || deep) {
4425 return node.cloneNode(deep);
4426 }
4427
4428 doc = self.doc;
4429
4430 // Make a HTML5 safe shallow copy
4431 if (!deep) {
4432 clone = doc.createElement(node.nodeName);
4433
4434 // Copy attribs
4435 each(self.getAttribs(node), function(attr) {
4436 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
4437 });
4438
4439 return clone;
4440 }
4441 /*
4442 // Setup HTML5 patched document fragment
4443 if (!self.frag) {
4444 self.frag = doc.createDocumentFragment();
4445 self.fixDoc(self.frag);
4446 }
4447
4448 // Make a deep copy by adding it to the document fragment then removing it this removed the :section
4449 clone = doc.createElement('div');
4450 self.frag.appendChild(clone);
4451 clone.innerHTML = node.outerHTML;
4452 self.frag.removeChild(clone);
4453 */
4454 return clone.firstChild;
4455 },
4456
4457 getRoot : function() {
4458 var t = this, s = t.settings;
4459
4460 return (s && t.get(s.root_element)) || t.doc.body;
4461 },
4462
4463 getViewPort : function(w) {
4464 var d, b;
4465
4466 w = !w ? this.win : w;
4467 d = w.document;
4468 b = this.boxModel ? d.documentElement : d.body;
4469
4470 // Returns viewport size excluding scrollbars
4471 return {
4472 x : w.pageXOffset || b.scrollLeft,
4473 y : w.pageYOffset || b.scrollTop,
4474 w : w.innerWidth || b.clientWidth,
4475 h : w.innerHeight || b.clientHeight
4476 };
4477 },
4478
4479 getRect : function(e) {
4480 var p, t = this, sr;
4481
4482 e = t.get(e);
4483 p = t.getPos(e);
4484 sr = t.getSize(e);
4485
4486 return {
4487 x : p.x,
4488 y : p.y,
4489 w : sr.w,
4490 h : sr.h
4491 };
4492 },
4493
4494 getSize : function(e) {
4495 var t = this, w, h;
4496
4497 e = t.get(e);
4498 w = t.getStyle(e, 'width');
4499 h = t.getStyle(e, 'height');
4500
4501 // Non pixel value, then force offset/clientWidth
4502 if (w.indexOf('px') === -1)
4503 w = 0;
4504
4505 // Non pixel value, then force offset/clientWidth
4506 if (h.indexOf('px') === -1)
4507 h = 0;
4508
4509 return {
4510 w : parseInt(w) || e.offsetWidth || e.clientWidth,
4511 h : parseInt(h) || e.offsetHeight || e.clientHeight
4512 };
4513 },
4514
4515 getParent : function(n, f, r) {
4516 return this.getParents(n, f, r, false);
4517 },
4518
4519 getParents : function(n, f, r, c) {
4520 var t = this, na, se = t.settings, o = [];
4521
4522 n = t.get(n);
4523 c = c === undefined;
4524
4525 if (se.strict_root)
4526 r = r || t.getRoot();
4527
4528 // Wrap node name as func
4529 if (is(f, 'string')) {
4530 na = f;
4531
4532 if (f === '*') {
4533 f = function(n) {return n.nodeType == 1;};
4534 } else {
4535 f = function(n) {
4536 return t.is(n, na);
4537 };
4538 }
4539 }
4540
4541 while (n) {
4542 if (n == r || !n.nodeType || n.nodeType === 9)
4543 break;
4544
4545 if (!f || f(n)) {
4546 if (c)
4547 o.push(n);
4548 else
4549 return n;
4550 }
4551
4552 n = n.parentNode;
4553 }
4554
4555 return c ? o : null;
4556 },
4557
4558 get : function(e) {
4559 var n;
4560
4561 if (e && this.doc && typeof(e) == 'string') {
4562 n = e;
4563 e = this.doc.getElementById(e);
4564
4565 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
4566 if (e && e.id !== n)
4567 return this.doc.getElementsByName(n)[1];
4568 }
4569
4570 return e;
4571 },
4572
4573 getNext : function(node, selector) {
4574 return this._findSib(node, selector, 'nextSibling');
4575 },
4576
4577 getPrev : function(node, selector) {
4578 return this._findSib(node, selector, 'previousSibling');
4579 },
4580
4581
4582 select : function(pa, s) {
4583 var t = this;
4584
4585 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
4586 },
4587
4588 is : function(n, selector) {
4589 var i;
4590
4591 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
4592 if (n.length === undefined) {
4593 // Simple all selector
4594 if (selector === '*')
4595 return n.nodeType == 1;
4596
4597 // Simple selector just elements
4598 if (simpleSelectorRe.test(selector)) {
4599 selector = selector.toLowerCase().split(/,/);
4600 n = n.nodeName.toLowerCase();
4601
4602 for (i = selector.length - 1; i >= 0; i--) {
4603 if (selector[i] == n)
4604 return true;
4605 }
4606
4607 return false;
4608 }
4609 }
4610
4611 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
4612 },
4613
4614
4615 add : function(p, n, a, h, c) {
4616 var t = this;
4617
4618 return this.run(p, function(p) {
4619 var e, k;
4620
4621 e = is(n, 'string') ? t.doc.createElement(n) : n;
4622 t.setAttribs(e, a);
4623
4624 if (h) {
4625 if (h.nodeType)
4626 e.appendChild(h);
4627 else
4628 t.setHTML(e, h);
4629 }
4630
4631 return !c ? p.appendChild(e) : e;
4632 });
4633 },
4634
4635 create : function(n, a, h) {
4636 return this.add(this.doc.createElement(n), n, a, h, 1);
4637 },
4638
4639 createHTML : function(n, a, h) {
4640 var o = '', t = this, k;
4641
4642 o += '<' + n;
4643
4644 for (k in a) {
4645 if (a.hasOwnProperty(k))
4646 o += ' ' + k + '="' + t.encode(a[k]) + '"';
4647 }
4648
4649 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
4650 if (typeof(h) != "undefined")
4651 return o + '>' + h + '</' + n + '>';
4652
4653 return o + ' />';
4654 },
4655
4656 remove : function(node, keep_children) {
4657 return this.run(node, function(node) {
4658 var child, parent = node.parentNode;
4659
4660 if (!parent)
4661 return null;
4662
4663 if (keep_children) {
4664 while (child = node.firstChild) {
4665 // IE 8 will crash if you don't remove completely empty text nodes
4666 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
4667 parent.insertBefore(child, node);
4668 else
4669 node.removeChild(child);
4670 }
4671 }
4672
4673 return parent.removeChild(node);
4674 });
4675 },
4676
4677 setStyle : function(n, na, v) {
4678 var t = this;
4679
4680 return t.run(n, function(e) {
4681 var s, i;
4682
4683 s = e.style;
4684
4685 // Camelcase it, if needed
4686 na = na.replace(/-(\D)/g, function(a, b){
4687 return b.toUpperCase();
4688 });
4689
4690 // Default px suffix on these
4691 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
4692 v += 'px';
4693
4694 switch (na) {
4695 case 'opacity':
4696 // IE specific opacity
4697 if (isIE) {
4698 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
4699
4700 if (!n.currentStyle || !n.currentStyle.hasLayout)
4701 s.display = 'inline-block';
4702 }
4703
4704 // Fix for older browsers
4705 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
4706 break;
4707
4708 case 'float':
4709 isIE ? s.styleFloat = v : s.cssFloat = v;
4710 break;
4711
4712 default:
4713 s[na] = v || '';
4714 }
4715
4716 // Force update of the style data
4717 if (t.settings.update_styles)
4718 t.setAttrib(e, 'data-mce-style');
4719 });
4720 },
4721
4722 getStyle : function(n, na, c) {
4723 n = this.get(n);
4724
4725 if (!n)
4726 return;
4727
4728 // Gecko
4729 if (this.doc.defaultView && c) {
4730 // Remove camelcase
4731 na = na.replace(/[A-Z]/g, function(a){
4732 return '-' + a;
4733 });
4734
4735 try {
4736 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
4737 } catch (ex) {
4738 // Old safari might fail
4739 return null;
4740 }
4741 }
4742
4743 // Camelcase it, if needed
4744 na = na.replace(/-(\D)/g, function(a, b){
4745 return b.toUpperCase();
4746 });
4747
4748 if (na == 'float')
4749 na = isIE ? 'styleFloat' : 'cssFloat';
4750
4751 // IE & Opera
4752 if (n.currentStyle && c)
4753 return n.currentStyle[na];
4754
4755 return n.style ? n.style[na] : undefined;
4756 },
4757
4758 setStyles : function(e, o) {
4759 var t = this, s = t.settings, ol;
4760
4761 ol = s.update_styles;
4762 s.update_styles = 0;
4763
4764 each(o, function(v, n) {
4765 t.setStyle(e, n, v);
4766 });
4767
4768 // Update style info
4769 s.update_styles = ol;
4770 if (s.update_styles)
4771 t.setAttrib(e, s.cssText);
4772 },
4773
4774 removeAllAttribs: function(e) {
4775 return this.run(e, function(e) {
4776 var i, attrs = e.attributes;
4777 for (i = attrs.length - 1; i >= 0; i--) {
4778 e.removeAttributeNode(attrs.item(i));
4779 }
4780 });
4781 },
4782
4783 setAttrib : function(e, n, v) {
4784 var t = this;
4785
4786 // Whats the point
4787 if (!e || !n)
4788 return;
4789
4790 // Strict XML mode
4791 if (t.settings.strict)
4792 n = n.toLowerCase();
4793
4794 return this.run(e, function(e) {
4795 var s = t.settings;
4796 var originalValue = e.getAttribute(n);
4797 if (v !== null) {
4798 switch (n) {
4799 case "style":
4800 if (!is(v, 'string')) {
4801 each(v, function(v, n) {
4802 t.setStyle(e, n, v);
4803 });
4804
4805 return;
4806 }
4807
4808 // No mce_style for elements with these since they might get resized by the user
4809 if (s.keep_values) {
4810 if (v && !t._isRes(v))
4811 e.setAttribute('data-mce-style', v, 2);
4812 else
4813 e.removeAttribute('data-mce-style', 2);
4814 }
4815
4816 e.style.cssText = v;
4817 break;
4818
4819 case "class":
4820 e.className = v || ''; // Fix IE null bug
4821 break;
4822
4823 case "src":
4824 case "href":
4825 if (s.keep_values) {
4826 if (s.url_converter)
4827 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
4828
4829 t.setAttrib(e, 'data-mce-' + n, v, 2);
4830 }
4831
4832 break;
4833
4834 case "shape":
4835 e.setAttribute('data-mce-style', v);
4836 break;
4837 }
4838 }
4839 if (is(v) && v !== null && v.length !== 0)
4840 e.setAttribute(n, '' + v, 2);
4841 else
4842 e.removeAttribute(n, 2);
4843
4844 // fire onChangeAttrib event for attributes that have changed
4845 if (tinyMCE.activeEditor && originalValue != v) {
4846 var ed = tinyMCE.activeEditor;
4847 ed.onSetAttrib.dispatch(ed, e, n, v);
4848 }
4849 });
4850 },
4851
4852 setAttribs : function(e, o) {
4853 var t = this;
4854
4855 return this.run(e, function(e) {
4856 each(o, function(v, n) {
4857 t.setAttrib(e, n, v);
4858 });
4859 });
4860 },
4861
4862 getAttrib : function(e, n, dv) {
4863 var v, t = this, undef;
4864
4865 e = t.get(e);
4866
4867 if (!e || e.nodeType !== 1)
4868 return dv === undef ? false : dv;
4869
4870 if (!is(dv))
4871 dv = '';
4872
4873 // Try the mce variant for these
4874 if (/^(src|href|style|coords|shape)$/.test(n)) {
4875 v = e.getAttribute("data-mce-" + n);
4876
4877 if (v)
4878 return v;
4879 }
4880
4881 if (isIE && t.props[n]) {
4882 v = e[t.props[n]];
4883 v = v && v.nodeValue ? v.nodeValue : v;
4884 }
4885
4886 if (!v)
4887 v = e.getAttribute(n, 2);
4888
4889 // Check boolean attribs
4890 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
4891 if (e[t.props[n]] === true && v === '')
4892 return n;
4893
4894 return v ? n : '';
4895 }
4896
4897 // Inner input elements will override attributes on form elements
4898 if (e.nodeName === "FORM" && e.getAttributeNode(n))
4899 return e.getAttributeNode(n).nodeValue;
4900
4901 if (n === 'style') {
4902 v = v || e.style.cssText;
4903
4904 if (v) {
4905 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
4906
4907 if (t.settings.keep_values && !t._isRes(v))
4908 e.setAttribute('data-mce-style', v);
4909 }
4910 }
4911
4912 // Remove Apple and WebKit stuff
4913 if (isWebKit && n === "class" && v)
4914 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
4915
4916 // Handle IE issues
4917 if (isIE) {
4918 switch (n) {
4919 case 'rowspan':
4920 case 'colspan':
4921 // IE returns 1 as default value
4922 if (v === 1)
4923 v = '';
4924
4925 break;
4926
4927 case 'size':
4928 // IE returns +0 as default value for size
4929 if (v === '+0' || v === 20 || v === 0)
4930 v = '';
4931
4932 break;
4933
4934 case 'width':
4935 case 'height':
4936 case 'vspace':
4937 case 'checked':
4938 case 'disabled':
4939 case 'readonly':
4940 if (v === 0)
4941 v = '';
4942
4943 break;
4944
4945 case 'hspace':
4946 // IE returns -1 as default value
4947 if (v === -1)
4948 v = '';
4949
4950 break;
4951
4952 case 'maxlength':
4953 case 'tabindex':
4954 // IE returns default value
4955 if (v === 32768 || v === 2147483647 || v === '32768')
4956 v = '';
4957
4958 break;
4959
4960 case 'multiple':
4961 case 'compact':
4962 case 'noshade':
4963 case 'nowrap':
4964 if (v === 65535)
4965 return n;
4966
4967 return dv;
4968
4969 case 'shape':
4970 v = v.toLowerCase();
4971 break;
4972
4973 default:
4974 // IE has odd anonymous function for event attributes
4975 if (n.indexOf('on') === 0 && v)
4976 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
4977 }
4978 }
4979
4980 return (v !== undef && v !== null && v !== '') ? '' + v : dv;
4981 },
4982
4983 getPos : function(n, ro) {
4984 var t = this, x = 0, y = 0, e, d = t.doc, r;
4985
4986 n = t.get(n);
4987 ro = ro || d.body;
4988
4989 if (n) {
4990 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
4991 if (n.getBoundingClientRect) {
4992 n = n.getBoundingClientRect();
4993 e = t.boxModel ? d.documentElement : d.body;
4994
4995 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
4996 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
4997 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
4998 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
4999
5000 return {x : x, y : y};
5001 }
5002
5003 r = n;
5004 while (r && r != ro && r.nodeType) {
5005 x += r.offsetLeft || 0;
5006 y += r.offsetTop || 0;
5007 r = r.offsetParent;
5008 }
5009
5010 r = n.parentNode;
5011 while (r && r != ro && r.nodeType) {
5012 x -= r.scrollLeft || 0;
5013 y -= r.scrollTop || 0;
5014 r = r.parentNode;
5015 }
5016 }
5017
5018 return {x : x, y : y};
5019 },
5020
5021 parseStyle : function(st) {
5022 return this.styles.parse(st);
5023 },
5024
5025 serializeStyle : function(o, name) {
5026 return this.styles.serialize(o, name);
5027 },
5028
5029 loadCSS : function(u) {
5030 var t = this, d = t.doc, head;
5031
5032 if (!u)
5033 u = '';
5034
5035 head = t.select('head')[0];
5036
5037 each(u.split(','), function(u) {
5038 var link;
5039
5040 if (t.files[u])
5041 return;
5042
5043 t.files[u] = true;
5044 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5045
5046 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5047 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5048 // It's ugly but it seems to work fine.
5049 if (isIE && d.documentMode && d.recalc) {
5050 link.onload = function() {
5051 if (d.recalc)
5052 d.recalc();
5053
5054 link.onload = null;
5055 };
5056 }
5057
5058 head.appendChild(link);
5059 });
5060 },
5061
5062 addClass : function(e, c) {
5063 return this.run(e, function(e) {
5064 var o;
5065
5066 if (!c)
5067 return 0;
5068
5069 if (this.hasClass(e, c))
5070 return e.className;
5071
5072 o = this.removeClass(e, c);
5073
5074 return e.className = (o != '' ? (o + ' ') : '') + c;
5075 });
5076 },
5077
5078 removeClass : function(e, c) {
5079 var t = this, re;
5080
5081 return t.run(e, function(e) {
5082 var v;
5083
5084 if (t.hasClass(e, c)) {
5085 if (!re)
5086 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5087
5088 v = e.className.replace(re, ' ');
5089 v = tinymce.trim(v != ' ' ? v : '');
5090
5091 e.className = v;
5092
5093 // Empty class attr
5094 if (!v) {
5095 e.removeAttribute('class');
5096 e.removeAttribute('className');
5097 }
5098
5099 return v;
5100 }
5101
5102 return e.className;
5103 });
5104 },
5105
5106 hasClass : function(n, c) {
5107 n = this.get(n);
5108
5109 if (!n || !c)
5110 return false;
5111
5112 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5113 },
5114
5115 show : function(e) {
5116 return this.setStyle(e, 'display', 'block');
5117 },
5118
5119 hide : function(e) {
5120 return this.setStyle(e, 'display', 'none');
5121 },
5122
5123 isHidden : function(e) {
5124 e = this.get(e);
5125
5126 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5127 },
5128
5129 uniqueId : function(p) {
5130 return (!p ? 'mce_' : p) + (this.counter++);
5131 },
5132
5133 setHTML : function(element, html) {
5134 var self = this;
5135
5136 return self.run(element, function(element) {
5137 if (isIE) {
5138 // Remove all child nodes, IE keeps empty text nodes in DOM
5139 while (element.firstChild)
5140 element.removeChild(element.firstChild);
5141
5142 try {
5143 // IE will remove comments from the beginning
5144 // unless you padd the contents with something
5145 element.innerHTML = '<br />' + html;
5146 element.removeChild(element.firstChild);
5147 } catch (ex) {
5148 // 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
5149 // This seems to fix this problem
5150
5151 // Create new div with HTML contents and a BR infront to keep comments
5152 element = self.create('div');
5153 element.innerHTML = '<br />' + html;
5154
5155 // Add all children from div to target
5156 each (element.childNodes, function(node, i) {
5157 // Skip br element
5158 if (i)
5159 element.appendChild(node);
5160 });
5161 }
5162 } else
5163 element.innerHTML = html;
5164
5165 return html;
5166 });
5167 },
5168
5169 getOuterHTML : function(elm) {
5170 var doc, self = this;
5171
5172 elm = self.get(elm);
5173
5174 if (!elm)
5175 return null;
5176
5177 if (elm.nodeType === 1 && self.hasOuterHTML)
5178 return elm.outerHTML;
5179
5180 doc = (elm.ownerDocument || self.doc).createElement("body");
5181 doc.appendChild(elm.cloneNode(true));
5182
5183 return doc.innerHTML;
5184 },
5185
5186 setOuterHTML : function(e, h, d) {
5187 var t = this;
5188
5189 function setHTML(e, h, d) {
5190 var n, tp;
5191
5192 tp = d.createElement("body");
5193 tp.innerHTML = h;
5194
5195 n = tp.lastChild;
5196 while (n) {
5197 t.insertAfter(n.cloneNode(true), e);
5198 n = n.previousSibling;
5199 }
5200
5201 t.remove(e);
5202 };
5203
5204 return this.run(e, function(e) {
5205 e = t.get(e);
5206
5207 // Only set HTML on elements
5208 if (e.nodeType == 1) {
5209 d = d || e.ownerDocument || t.doc;
5210
5211 if (isIE) {
5212 try {
5213 // Try outerHTML for IE it sometimes produces an unknown runtime error
5214 if (isIE && e.nodeType == 1)
5215 e.outerHTML = h;
5216 else
5217 setHTML(e, h, d);
5218 } catch (ex) {
5219 // Fix for unknown runtime error
5220 setHTML(e, h, d);
5221 }
5222 } else
5223 setHTML(e, h, d);
5224 }
5225 });
5226 },
5227
5228 decode : Entities.decode,
5229
5230 encode : Entities.encodeAllRaw,
5231
5232 insertAfter : function(node, reference_node) {
5233 reference_node = this.get(reference_node);
5234
5235 return this.run(node, function(node) {
5236 var parent, nextSibling;
5237
5238 parent = reference_node.parentNode;
5239 nextSibling = reference_node.nextSibling;
5240
5241 if (nextSibling)
5242 parent.insertBefore(node, nextSibling);
5243 else
5244 parent.appendChild(node);
5245
5246 return node;
5247 });
5248 },
5249
5250 replace : function(n, o, k) {
5251 var t = this;
5252
5253 if (is(o, 'array'))
5254 n = n.cloneNode(true);
5255
5256 return t.run(o, function(o) {
5257 if (k) {
5258 each(tinymce.grep(o.childNodes), function(c) {
5259 n.appendChild(c);
5260 });
5261 }
5262
5263 return o.parentNode.replaceChild(n, o);
5264 });
5265 },
5266
5267 rename : function(elm, name) {
5268 var t = this, newElm;
5269
5270 if (elm.nodeName != name.toUpperCase()) {
5271 // Rename block element
5272 newElm = t.create(name);
5273
5274 // Copy attribs to new block
5275 each(t.getAttribs(elm), function(attr_node) {
5276 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
5277 });
5278
5279 // Replace block
5280 t.replace(newElm, elm, 1);
5281 }
5282
5283 return newElm || elm;
5284 },
5285
5286 findCommonAncestor : function(a, b) {
5287 var ps = a, pe;
5288
5289 while (ps) {
5290 pe = b;
5291
5292 while (pe && ps != pe)
5293 pe = pe.parentNode;
5294
5295 if (ps == pe)
5296 break;
5297
5298 ps = ps.parentNode;
5299 }
5300
5301 if (!ps && a.ownerDocument)
5302 return a.ownerDocument.documentElement;
5303
5304 return ps;
5305 },
5306
5307 toHex : function(s) {
5308 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
5309
5310 function hex(s) {
5311 s = parseInt(s).toString(16);
5312
5313 return s.length > 1 ? s : '0' + s; // 0 -> 00
5314 };
5315
5316 if (c) {
5317 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
5318
5319 return s;
5320 }
5321
5322 return s;
5323 },
5324
5325 getClasses : function() {
5326 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
5327
5328 if (t.classes)
5329 return t.classes;
5330
5331 function addClasses(s) {
5332 // IE style imports
5333 each(s.imports, function(r) {
5334 addClasses(r);
5335 });
5336
5337 each(s.cssRules || s.rules, function(r) {
5338 // Real type or fake it on IE
5339 switch (r.type || 1) {
5340 // Rule
5341 case 1:
5342 if (r.selectorText) {
5343 each(r.selectorText.split(','), function(v) {
5344 v = v.replace(/^\s*|\s*$|^\s\./g, "");
5345
5346 // Is internal or it doesn't contain a class
5347 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
5348 return;
5349
5350 // Remove everything but class name
5351 ov = v;
5352 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
5353
5354 // Filter classes
5355 if (f && !(v = f(v, ov)))
5356 return;
5357
5358 if (!lo[v]) {
5359 cl.push({'class' : v});
5360 lo[v] = 1;
5361 }
5362 });
5363 }
5364 break;
5365
5366 // Import
5367 case 3:
5368 addClasses(r.styleSheet);
5369 break;
5370 }
5371 });
5372 };
5373
5374 try {
5375 each(t.doc.styleSheets, addClasses);
5376 } catch (ex) {
5377 // Ignore
5378 }
5379
5380 if (cl.length > 0)
5381 t.classes = cl;
5382
5383 return cl;
5384 },
5385
5386 run : function(e, f, s) {
5387 var t = this, o;
5388
5389 if (t.doc && typeof(e) === 'string')
5390 e = t.get(e);
5391
5392 if (!e)
5393 return false;
5394
5395 s = s || this;
5396 if (!e.nodeType && (e.length || e.length === 0)) {
5397 o = [];
5398
5399 each(e, function(e, i) {
5400 if (e) {
5401 if (typeof(e) == 'string')
5402 e = t.doc.getElementById(e);
5403
5404 o.push(f.call(s, e, i));
5405 }
5406 });
5407
5408 return o;
5409 }
5410
5411 return f.call(s, e);
5412 },
5413
5414 getAttribs : function(n) {
5415 var o;
5416
5417 n = this.get(n);
5418
5419 if (!n)
5420 return [];
5421
5422 if (isIE) {
5423 o = [];
5424
5425 // Object will throw exception in IE
5426 if (n.nodeName == 'OBJECT')
5427 return n.attributes;
5428
5429 // IE doesn't keep the selected attribute if you clone option elements
5430 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
5431 o.push({specified : 1, nodeName : 'selected'});
5432
5433 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
5434 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
5435 o.push({specified : 1, nodeName : a});
5436 });
5437
5438 return o;
5439 }
5440
5441 return n.attributes;
5442 },
5443
5444 isEmpty : function(node, elements) {
5445 var self = this, i, attributes, type, walker, name, parentNode;
5446
5447 node = node.firstChild;
5448 if (node) {
5449 walker = new tinymce.dom.TreeWalker(node, node.parentNode);
5450 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
5451
5452 do {
5453 type = node.nodeType;
5454
5455 if (type === 1) {
5456 // Ignore bogus elements
5457 if (node.getAttribute('data-mce-bogus'))
5458 continue;
5459
5460 // Keep empty elements like <img />
5461 name = node.nodeName.toLowerCase();
5462 if (elements && elements[name]) {
5463 // Ignore single BR elements in blocks like <p><br /></p>
5464 parentNode = node.parentNode;
5465 if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) {
5466 continue;
5467 }
5468
5469 return false;
5470 }
5471
5472 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
5473 attributes = self.getAttribs(node);
5474 i = node.attributes.length;
5475 while (i--) {
5476 name = node.attributes[i].nodeName;
5477 if (name === "name" || name === 'data-mce-bookmark')
5478 return false;
5479 }
5480 }
5481
5482 // Keep comment nodes
5483 if (type == 8)
5484 return false;
5485
5486 // Keep non whitespace text nodes
5487 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
5488 return false;
5489 } while (node = walker.next());
5490 }
5491
5492 return true;
5493 },
5494
5495 destroy : function(s) {
5496 var t = this;
5497
5498 t.win = t.doc = t.root = t.events = t.frag = null;
5499
5500 // Manual destroy then remove unload handler
5501 if (!s)
5502 tinymce.removeUnload(t.destroy);
5503 },
5504
5505 createRng : function() {
5506 var d = this.doc;
5507
5508 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
5509 },
5510
5511 nodeIndex : function(node, normalized) {
5512 var idx = 0, lastNodeType, lastNode, nodeType;
5513
5514 if (node) {
5515 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
5516 nodeType = node.nodeType;
5517
5518 // Normalize text nodes
5519 if (normalized && nodeType == 3) {
5520 if (nodeType == lastNodeType || !node.nodeValue.length)
5521 continue;
5522 }
5523 idx++;
5524 lastNodeType = nodeType;
5525 }
5526 }
5527
5528 return idx;
5529 },
5530
5531 split : function(pe, e, re) {
5532 var t = this, r = t.createRng(), bef, aft, pa;
5533
5534 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
5535 // but we don't want that in our code since it serves no purpose for the end user
5536 // For example if this is chopped:
5537 // <p>text 1<span><b>CHOP</b></span>text 2</p>
5538 // would produce:
5539 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
5540 // this function will then trim of empty edges and produce:
5541 // <p>text 1</p><b>CHOP</b><p>text 2</p>
5542 function trim(node) {
5543 var i, children = node.childNodes, type = node.nodeType;
5544
5545 function surroundedBySpans(node) {
5546 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
5547 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
5548 return previousIsSpan && nextIsSpan;
5549 }
5550
5551 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
5552 return;
5553
5554 for (i = children.length - 1; i >= 0; i--)
5555 trim(children[i]);
5556
5557 if (type != 9) {
5558 // Keep non whitespace text nodes
5559 if (type == 3 && node.nodeValue.length > 0) {
5560 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
5561 // Also keep text nodes with only spaces if surrounded by spans.
5562 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
5563 var trimmedLength = tinymce.trim(node.nodeValue).length;
5564 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength == 0 && surroundedBySpans(node))
5565 return;
5566 } else if (type == 1) {
5567 // If the only child is a bookmark then move it up
5568 children = node.childNodes;
5569 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
5570 node.parentNode.insertBefore(children[0], node);
5571
5572 // Keep non empty elements or img, hr etc
5573 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
5574 return;
5575 }
5576
5577 t.remove(node);
5578 }
5579
5580 return node;
5581 };
5582
5583 if (pe && e) {
5584 // Get before chunk
5585 r.setStart(pe.parentNode, t.nodeIndex(pe));
5586 r.setEnd(e.parentNode, t.nodeIndex(e));
5587 bef = r.extractContents();
5588
5589 // Get after chunk
5590 r = t.createRng();
5591 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
5592 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
5593 aft = r.extractContents();
5594
5595 // Insert before chunk
5596 pa = pe.parentNode;
5597 pa.insertBefore(trim(bef), pe);
5598
5599 // Insert middle chunk
5600 if (re)
5601 pa.replaceChild(re, e);
5602 else
5603 pa.insertBefore(e, pe);
5604
5605 // Insert after chunk
5606 pa.insertBefore(trim(aft), pe);
5607 t.remove(pe);
5608
5609 return re || e;
5610 }
5611 },
5612
5613 bind : function(target, name, func, scope) {
5614 return this.events.add(target, name, func, scope || this);
5615 },
5616
5617 unbind : function(target, name, func) {
5618 return this.events.remove(target, name, func);
5619 },
5620
5621 fire : function(target, name, evt) {
5622 return this.events.fire(target, name, evt);
5623 },
5624
5625
5626 _findSib : function(node, selector, name) {
5627 var t = this, f = selector;
5628
5629 if (node) {
5630 // If expression make a function of it using is
5631 if (is(f, 'string')) {
5632 f = function(node) {
5633 return t.is(node, selector);
5634 };
5635 }
5636
5637 // Loop all siblings
5638 for (node = node[name]; node; node = node[name]) {
5639 if (f(node))
5640 return node;
5641 }
5642 }
5643
5644 return null;
5645 },
5646
5647 _isRes : function(c) {
5648 // Is live resizble element
5649 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
5650 }
5651
5652 /*
5653 walk : function(n, f, s) {
5654 var d = this.doc, w;
5655
5656 if (d.createTreeWalker) {
5657 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
5658
5659 while ((n = w.nextNode()) != null)
5660 f.call(s || this, n);
5661 } else
5662 tinymce.walk(n, f, 'childNodes', s);
5663 }
5664 */
5665
5666 /*
5667 toRGB : function(s) {
5668 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
5669
5670 if (c) {
5671 // #FFF -> #FFFFFF
5672 if (!is(c[3]))
5673 c[3] = c[2] = c[1];
5674
5675 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
5676 }
5677
5678 return s;
5679 }
5680 */
5681 });
5682
5683 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
5684 })(tinymce);
5685
5686 (function(ns) {
5687 // Range constructor
5688 function Range(dom) {
5689 var t = this,
5690 doc = dom.doc,
5691 EXTRACT = 0,
5692 CLONE = 1,
5693 DELETE = 2,
5694 TRUE = true,
5695 FALSE = false,
5696 START_OFFSET = 'startOffset',
5697 START_CONTAINER = 'startContainer',
5698 END_CONTAINER = 'endContainer',
5699 END_OFFSET = 'endOffset',
5700 extend = tinymce.extend,
5701 nodeIndex = dom.nodeIndex;
5702
5703 extend(t, {
5704 // Inital states
5705 startContainer : doc,
5706 startOffset : 0,
5707 endContainer : doc,
5708 endOffset : 0,
5709 collapsed : TRUE,
5710 commonAncestorContainer : doc,
5711
5712 // Range constants
5713 START_TO_START : 0,
5714 START_TO_END : 1,
5715 END_TO_END : 2,
5716 END_TO_START : 3,
5717
5718 // Public methods
5719 setStart : setStart,
5720 setEnd : setEnd,
5721 setStartBefore : setStartBefore,
5722 setStartAfter : setStartAfter,
5723 setEndBefore : setEndBefore,
5724 setEndAfter : setEndAfter,
5725 collapse : collapse,
5726 selectNode : selectNode,
5727 selectNodeContents : selectNodeContents,
5728 compareBoundaryPoints : compareBoundaryPoints,
5729 deleteContents : deleteContents,
5730 extractContents : extractContents,
5731 cloneContents : cloneContents,
5732 insertNode : insertNode,
5733 surroundContents : surroundContents,
5734 cloneRange : cloneRange
5735 });
5736
5737 function createDocumentFragment() {
5738 return doc.createDocumentFragment();
5739 };
5740
5741 function setStart(n, o) {
5742 _setEndPoint(TRUE, n, o);
5743 };
5744
5745 function setEnd(n, o) {
5746 _setEndPoint(FALSE, n, o);
5747 };
5748
5749 function setStartBefore(n) {
5750 setStart(n.parentNode, nodeIndex(n));
5751 };
5752
5753 function setStartAfter(n) {
5754 setStart(n.parentNode, nodeIndex(n) + 1);
5755 };
5756
5757 function setEndBefore(n) {
5758 setEnd(n.parentNode, nodeIndex(n));
5759 };
5760
5761 function setEndAfter(n) {
5762 setEnd(n.parentNode, nodeIndex(n) + 1);
5763 };
5764
5765 function collapse(ts) {
5766 if (ts) {
5767 t[END_CONTAINER] = t[START_CONTAINER];
5768 t[END_OFFSET] = t[START_OFFSET];
5769 } else {
5770 t[START_CONTAINER] = t[END_CONTAINER];
5771 t[START_OFFSET] = t[END_OFFSET];
5772 }
5773
5774 t.collapsed = TRUE;
5775 };
5776
5777 function selectNode(n) {
5778 setStartBefore(n);
5779 setEndAfter(n);
5780 };
5781
5782 function selectNodeContents(n) {
5783 setStart(n, 0);
5784 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
5785 };
5786
5787 function compareBoundaryPoints(h, r) {
5788 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
5789 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
5790
5791 // Check START_TO_START
5792 if (h === 0)
5793 return _compareBoundaryPoints(sc, so, rsc, rso);
5794
5795 // Check START_TO_END
5796 if (h === 1)
5797 return _compareBoundaryPoints(ec, eo, rsc, rso);
5798
5799 // Check END_TO_END
5800 if (h === 2)
5801 return _compareBoundaryPoints(ec, eo, rec, reo);
5802
5803 // Check END_TO_START
5804 if (h === 3)
5805 return _compareBoundaryPoints(sc, so, rec, reo);
5806 };
5807
5808 function deleteContents() {
5809 _traverse(DELETE);
5810 };
5811
5812 function extractContents() {
5813 return _traverse(EXTRACT);
5814 };
5815
5816 function cloneContents() {
5817 return _traverse(CLONE);
5818 };
5819
5820 function insertNode(n) {
5821 var startContainer = this[START_CONTAINER],
5822 startOffset = this[START_OFFSET], nn, o;
5823
5824 // Node is TEXT_NODE or CDATA
5825 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
5826 if (!startOffset) {
5827 // At the start of text
5828 startContainer.parentNode.insertBefore(n, startContainer);
5829 } else if (startOffset >= startContainer.nodeValue.length) {
5830 // At the end of text
5831 dom.insertAfter(n, startContainer);
5832 } else {
5833 // Middle, need to split
5834 nn = startContainer.splitText(startOffset);
5835 startContainer.parentNode.insertBefore(n, nn);
5836 }
5837 } else {
5838 // Insert element node
5839 if (startContainer.childNodes.length > 0)
5840 o = startContainer.childNodes[startOffset];
5841
5842 if (o)
5843 startContainer.insertBefore(n, o);
5844 else
5845 startContainer.appendChild(n);
5846 }
5847 };
5848
5849 function surroundContents(n) {
5850 var f = t.extractContents();
5851
5852 t.insertNode(n);
5853 n.appendChild(f);
5854 t.selectNode(n);
5855 };
5856
5857 function cloneRange() {
5858 return extend(new Range(dom), {
5859 startContainer : t[START_CONTAINER],
5860 startOffset : t[START_OFFSET],
5861 endContainer : t[END_CONTAINER],
5862 endOffset : t[END_OFFSET],
5863 collapsed : t.collapsed,
5864 commonAncestorContainer : t.commonAncestorContainer
5865 });
5866 };
5867
5868 // Private methods
5869
5870 function _getSelectedNode(container, offset) {
5871 var child;
5872
5873 if (container.nodeType == 3 /* TEXT_NODE */)
5874 return container;
5875
5876 if (offset < 0)
5877 return container;
5878
5879 child = container.firstChild;
5880 while (child && offset > 0) {
5881 --offset;
5882 child = child.nextSibling;
5883 }
5884
5885 if (child)
5886 return child;
5887
5888 return container;
5889 };
5890
5891 function _isCollapsed() {
5892 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
5893 };
5894
5895 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
5896 var c, offsetC, n, cmnRoot, childA, childB;
5897
5898 // In the first case the boundary-points have the same container. A is before B
5899 // if its offset is less than the offset of B, A is equal to B if its offset is
5900 // equal to the offset of B, and A is after B if its offset is greater than the
5901 // offset of B.
5902 if (containerA == containerB) {
5903 if (offsetA == offsetB)
5904 return 0; // equal
5905
5906 if (offsetA < offsetB)
5907 return -1; // before
5908
5909 return 1; // after
5910 }
5911
5912 // In the second case a child node C of the container of A is an ancestor
5913 // container of B. In this case, A is before B if the offset of A is less than or
5914 // equal to the index of the child node C and A is after B otherwise.
5915 c = containerB;
5916 while (c && c.parentNode != containerA)
5917 c = c.parentNode;
5918
5919 if (c) {
5920 offsetC = 0;
5921 n = containerA.firstChild;
5922
5923 while (n != c && offsetC < offsetA) {
5924 offsetC++;
5925 n = n.nextSibling;
5926 }
5927
5928 if (offsetA <= offsetC)
5929 return -1; // before
5930
5931 return 1; // after
5932 }
5933
5934 // In the third case a child node C of the container of B is an ancestor container
5935 // of A. In this case, A is before B if the index of the child node C is less than
5936 // the offset of B and A is after B otherwise.
5937 c = containerA;
5938 while (c && c.parentNode != containerB) {
5939 c = c.parentNode;
5940 }
5941
5942 if (c) {
5943 offsetC = 0;
5944 n = containerB.firstChild;
5945
5946 while (n != c && offsetC < offsetB) {
5947 offsetC++;
5948 n = n.nextSibling;
5949 }
5950
5951 if (offsetC < offsetB)
5952 return -1; // before
5953
5954 return 1; // after
5955 }
5956
5957 // In the fourth case, none of three other cases hold: the containers of A and B
5958 // are siblings or descendants of sibling nodes. In this case, A is before B if
5959 // the container of A is before the container of B in a pre-order traversal of the
5960 // Ranges' context tree and A is after B otherwise.
5961 cmnRoot = dom.findCommonAncestor(containerA, containerB);
5962 childA = containerA;
5963
5964 while (childA && childA.parentNode != cmnRoot)
5965 childA = childA.parentNode;
5966
5967 if (!childA)
5968 childA = cmnRoot;
5969
5970 childB = containerB;
5971 while (childB && childB.parentNode != cmnRoot)
5972 childB = childB.parentNode;
5973
5974 if (!childB)
5975 childB = cmnRoot;
5976
5977 if (childA == childB)
5978 return 0; // equal
5979
5980 n = cmnRoot.firstChild;
5981 while (n) {
5982 if (n == childA)
5983 return -1; // before
5984
5985 if (n == childB)
5986 return 1; // after
5987
5988 n = n.nextSibling;
5989 }
5990 };
5991
5992 function _setEndPoint(st, n, o) {
5993 var ec, sc;
5994
5995 if (st) {
5996 t[START_CONTAINER] = n;
5997 t[START_OFFSET] = o;
5998 } else {
5999 t[END_CONTAINER] = n;
6000 t[END_OFFSET] = o;
6001 }
6002
6003 // If one boundary-point of a Range is set to have a root container
6004 // other than the current one for the Range, the Range is collapsed to
6005 // the new position. This enforces the restriction that both boundary-
6006 // points of a Range must have the same root container.
6007 ec = t[END_CONTAINER];
6008 while (ec.parentNode)
6009 ec = ec.parentNode;
6010
6011 sc = t[START_CONTAINER];
6012 while (sc.parentNode)
6013 sc = sc.parentNode;
6014
6015 if (sc == ec) {
6016 // The start position of a Range is guaranteed to never be after the
6017 // end position. To enforce this restriction, if the start is set to
6018 // be at a position after the end, the Range is collapsed to that
6019 // position.
6020 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6021 t.collapse(st);
6022 } else
6023 t.collapse(st);
6024
6025 t.collapsed = _isCollapsed();
6026 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6027 };
6028
6029 function _traverse(how) {
6030 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6031
6032 if (t[START_CONTAINER] == t[END_CONTAINER])
6033 return _traverseSameContainer(how);
6034
6035 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6036 if (p == t[START_CONTAINER])
6037 return _traverseCommonStartContainer(c, how);
6038
6039 ++endContainerDepth;
6040 }
6041
6042 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6043 if (p == t[END_CONTAINER])
6044 return _traverseCommonEndContainer(c, how);
6045
6046 ++startContainerDepth;
6047 }
6048
6049 depthDiff = startContainerDepth - endContainerDepth;
6050
6051 startNode = t[START_CONTAINER];
6052 while (depthDiff > 0) {
6053 startNode = startNode.parentNode;
6054 depthDiff--;
6055 }
6056
6057 endNode = t[END_CONTAINER];
6058 while (depthDiff < 0) {
6059 endNode = endNode.parentNode;
6060 depthDiff++;
6061 }
6062
6063 // ascend the ancestor hierarchy until we have a common parent.
6064 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6065 startNode = sp;
6066 endNode = ep;
6067 }
6068
6069 return _traverseCommonAncestors(startNode, endNode, how);
6070 };
6071
6072 function _traverseSameContainer(how) {
6073 var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6074
6075 if (how != DELETE)
6076 frag = createDocumentFragment();
6077
6078 // If selection is empty, just return the fragment
6079 if (t[START_OFFSET] == t[END_OFFSET])
6080 return frag;
6081
6082 // Text node needs special case handling
6083 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6084 // get the substring
6085 s = t[START_CONTAINER].nodeValue;
6086 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6087
6088 // set the original text node to its new value
6089 if (how != CLONE) {
6090 n = t[START_CONTAINER];
6091 start = t[START_OFFSET];
6092 len = t[END_OFFSET] - t[START_OFFSET];
6093
6094 if (start === 0 && len >= n.nodeValue.length - 1) {
6095 n.parentNode.removeChild(n);
6096 } else {
6097 n.deleteData(start, len);
6098 }
6099
6100 // Nothing is partially selected, so collapse to start point
6101 t.collapse(TRUE);
6102 }
6103
6104 if (how == DELETE)
6105 return;
6106
6107 if (sub.length > 0) {
6108 frag.appendChild(doc.createTextNode(sub));
6109 }
6110
6111 return frag;
6112 }
6113
6114 // Copy nodes between the start/end offsets.
6115 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6116 cnt = t[END_OFFSET] - t[START_OFFSET];
6117
6118 while (n && cnt > 0) {
6119 sibling = n.nextSibling;
6120 xferNode = _traverseFullySelected(n, how);
6121
6122 if (frag)
6123 frag.appendChild( xferNode );
6124
6125 --cnt;
6126 n = sibling;
6127 }
6128
6129 // Nothing is partially selected, so collapse to start point
6130 if (how != CLONE)
6131 t.collapse(TRUE);
6132
6133 return frag;
6134 };
6135
6136 function _traverseCommonStartContainer(endAncestor, how) {
6137 var frag, n, endIdx, cnt, sibling, xferNode;
6138
6139 if (how != DELETE)
6140 frag = createDocumentFragment();
6141
6142 n = _traverseRightBoundary(endAncestor, how);
6143
6144 if (frag)
6145 frag.appendChild(n);
6146
6147 endIdx = nodeIndex(endAncestor);
6148 cnt = endIdx - t[START_OFFSET];
6149
6150 if (cnt <= 0) {
6151 // Collapse to just before the endAncestor, which
6152 // is partially selected.
6153 if (how != CLONE) {
6154 t.setEndBefore(endAncestor);
6155 t.collapse(FALSE);
6156 }
6157
6158 return frag;
6159 }
6160
6161 n = endAncestor.previousSibling;
6162 while (cnt > 0) {
6163 sibling = n.previousSibling;
6164 xferNode = _traverseFullySelected(n, how);
6165
6166 if (frag)
6167 frag.insertBefore(xferNode, frag.firstChild);
6168
6169 --cnt;
6170 n = sibling;
6171 }
6172
6173 // Collapse to just before the endAncestor, which
6174 // is partially selected.
6175 if (how != CLONE) {
6176 t.setEndBefore(endAncestor);
6177 t.collapse(FALSE);
6178 }
6179
6180 return frag;
6181 };
6182
6183 function _traverseCommonEndContainer(startAncestor, how) {
6184 var frag, startIdx, n, cnt, sibling, xferNode;
6185
6186 if (how != DELETE)
6187 frag = createDocumentFragment();
6188
6189 n = _traverseLeftBoundary(startAncestor, how);
6190 if (frag)
6191 frag.appendChild(n);
6192
6193 startIdx = nodeIndex(startAncestor);
6194 ++startIdx; // Because we already traversed it
6195
6196 cnt = t[END_OFFSET] - startIdx;
6197 n = startAncestor.nextSibling;
6198 while (n && cnt > 0) {
6199 sibling = n.nextSibling;
6200 xferNode = _traverseFullySelected(n, how);
6201
6202 if (frag)
6203 frag.appendChild(xferNode);
6204
6205 --cnt;
6206 n = sibling;
6207 }
6208
6209 if (how != CLONE) {
6210 t.setStartAfter(startAncestor);
6211 t.collapse(TRUE);
6212 }
6213
6214 return frag;
6215 };
6216
6217 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
6218 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
6219
6220 if (how != DELETE)
6221 frag = createDocumentFragment();
6222
6223 n = _traverseLeftBoundary(startAncestor, how);
6224 if (frag)
6225 frag.appendChild(n);
6226
6227 commonParent = startAncestor.parentNode;
6228 startOffset = nodeIndex(startAncestor);
6229 endOffset = nodeIndex(endAncestor);
6230 ++startOffset;
6231
6232 cnt = endOffset - startOffset;
6233 sibling = startAncestor.nextSibling;
6234
6235 while (cnt > 0) {
6236 nextSibling = sibling.nextSibling;
6237 n = _traverseFullySelected(sibling, how);
6238
6239 if (frag)
6240 frag.appendChild(n);
6241
6242 sibling = nextSibling;
6243 --cnt;
6244 }
6245
6246 n = _traverseRightBoundary(endAncestor, how);
6247
6248 if (frag)
6249 frag.appendChild(n);
6250
6251 if (how != CLONE) {
6252 t.setStartAfter(startAncestor);
6253 t.collapse(TRUE);
6254 }
6255
6256 return frag;
6257 };
6258
6259 function _traverseRightBoundary(root, how) {
6260 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
6261
6262 if (next == root)
6263 return _traverseNode(next, isFullySelected, FALSE, how);
6264
6265 parent = next.parentNode;
6266 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
6267
6268 while (parent) {
6269 while (next) {
6270 prevSibling = next.previousSibling;
6271 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
6272
6273 if (how != DELETE)
6274 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
6275
6276 isFullySelected = TRUE;
6277 next = prevSibling;
6278 }
6279
6280 if (parent == root)
6281 return clonedParent;
6282
6283 next = parent.previousSibling;
6284 parent = parent.parentNode;
6285
6286 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
6287
6288 if (how != DELETE)
6289 clonedGrandParent.appendChild(clonedParent);
6290
6291 clonedParent = clonedGrandParent;
6292 }
6293 };
6294
6295 function _traverseLeftBoundary(root, how) {
6296 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
6297
6298 if (next == root)
6299 return _traverseNode(next, isFullySelected, TRUE, how);
6300
6301 parent = next.parentNode;
6302 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
6303
6304 while (parent) {
6305 while (next) {
6306 nextSibling = next.nextSibling;
6307 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
6308
6309 if (how != DELETE)
6310 clonedParent.appendChild(clonedChild);
6311
6312 isFullySelected = TRUE;
6313 next = nextSibling;
6314 }
6315
6316 if (parent == root)
6317 return clonedParent;
6318
6319 next = parent.nextSibling;
6320 parent = parent.parentNode;
6321
6322 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
6323
6324 if (how != DELETE)
6325 clonedGrandParent.appendChild(clonedParent);
6326
6327 clonedParent = clonedGrandParent;
6328 }
6329 };
6330
6331 function _traverseNode(n, isFullySelected, isLeft, how) {
6332 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
6333
6334 if (isFullySelected)
6335 return _traverseFullySelected(n, how);
6336
6337 if (n.nodeType == 3 /* TEXT_NODE */) {
6338 txtValue = n.nodeValue;
6339
6340 if (isLeft) {
6341 offset = t[START_OFFSET];
6342 newNodeValue = txtValue.substring(offset);
6343 oldNodeValue = txtValue.substring(0, offset);
6344 } else {
6345 offset = t[END_OFFSET];
6346 newNodeValue = txtValue.substring(0, offset);
6347 oldNodeValue = txtValue.substring(offset);
6348 }
6349
6350 if (how != CLONE)
6351 n.nodeValue = oldNodeValue;
6352
6353 if (how == DELETE)
6354 return;
6355
6356 newNode = dom.clone(n, FALSE);
6357 newNode.nodeValue = newNodeValue;
6358
6359 return newNode;
6360 }
6361
6362 if (how == DELETE)
6363 return;
6364
6365 return dom.clone(n, FALSE);
6366 };
6367
6368 function _traverseFullySelected(n, how) {
6369 if (how != DELETE)
6370 return how == CLONE ? dom.clone(n, TRUE) : n;
6371
6372 n.parentNode.removeChild(n);
6373 };
6374 };
6375
6376 ns.Range = Range;
6377 })(tinymce.dom);
6378
6379 (function() {
6380 function Selection(selection) {
6381 var self = this, dom = selection.dom, TRUE = true, FALSE = false;
6382
6383 function getPosition(rng, start) {
6384 var checkRng, startIndex = 0, endIndex, inside,
6385 children, child, offset, index, position = -1, parent;
6386
6387 // Setup test range, collapse it and get the parent
6388 checkRng = rng.duplicate();
6389 checkRng.collapse(start);
6390 parent = checkRng.parentElement();
6391
6392 // Check if the selection is within the right document
6393 if (parent.ownerDocument !== selection.dom.doc)
6394 return;
6395
6396 // IE will report non editable elements as it's parent so look for an editable one
6397 while (parent.contentEditable === "false") {
6398 parent = parent.parentNode;
6399 }
6400
6401 // If parent doesn't have any children then return that we are inside the element
6402 if (!parent.hasChildNodes()) {
6403 return {node : parent, inside : 1};
6404 }
6405
6406 // Setup node list and endIndex
6407 children = parent.children;
6408 endIndex = children.length - 1;
6409
6410 // Perform a binary search for the position
6411 while (startIndex <= endIndex) {
6412 index = Math.floor((startIndex + endIndex) / 2);
6413
6414 // Move selection to node and compare the ranges
6415 child = children[index];
6416 checkRng.moveToElementText(child);
6417 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
6418
6419 // Before/after or an exact match
6420 if (position > 0) {
6421 endIndex = index - 1;
6422 } else if (position < 0) {
6423 startIndex = index + 1;
6424 } else {
6425 return {node : child};
6426 }
6427 }
6428
6429 // Check if child position is before or we didn't find a position
6430 if (position < 0) {
6431 // No element child was found use the parent element and the offset inside that
6432 if (!child) {
6433 checkRng.moveToElementText(parent);
6434 checkRng.collapse(true);
6435 child = parent;
6436 inside = true;
6437 } else
6438 checkRng.collapse(false);
6439
6440 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
6441
6442 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
6443 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
6444 checkRng = rng.duplicate();
6445 checkRng.collapse(start);
6446
6447 offset = -1;
6448 while (parent == checkRng.parentElement()) {
6449 if (checkRng.move('character', -1) == 0)
6450 break;
6451
6452 offset++;
6453 }
6454 }
6455
6456 offset = offset || checkRng.text.replace('\r\n', ' ').length;
6457 } else {
6458 // Child position is after the selection endpoint
6459 checkRng.collapse(true);
6460 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
6461
6462 // Get the length of the text to find where the endpoint is relative to it's container
6463 offset = checkRng.text.replace('\r\n', ' ').length;
6464 }
6465
6466 return {node : child, position : position, offset : offset, inside : inside};
6467 };
6468
6469 // Returns a W3C DOM compatible range object by using the IE Range API
6470 function getRange() {
6471 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
6472
6473 // If selection is outside the current document just return an empty range
6474 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
6475 if (element.ownerDocument != dom.doc)
6476 return domRange;
6477
6478 collapsed = selection.isCollapsed();
6479
6480 // Handle control selection
6481 if (ieRange.item) {
6482 domRange.setStart(element.parentNode, dom.nodeIndex(element));
6483 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
6484
6485 return domRange;
6486 }
6487
6488 function findEndPoint(start) {
6489 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
6490
6491 container = endPoint.node;
6492 offset = endPoint.offset;
6493
6494 if (endPoint.inside && !container.hasChildNodes()) {
6495 domRange[start ? 'setStart' : 'setEnd'](container, 0);
6496 return;
6497 }
6498
6499 if (offset === undef) {
6500 domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
6501 return;
6502 }
6503
6504 if (endPoint.position < 0) {
6505 sibling = endPoint.inside ? container.firstChild : container.nextSibling;
6506
6507 if (!sibling) {
6508 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
6509 return;
6510 }
6511
6512 if (!offset) {
6513 if (sibling.nodeType == 3)
6514 domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
6515 else
6516 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
6517
6518 return;
6519 }
6520
6521 // Find the text node and offset
6522 while (sibling) {
6523 nodeValue = sibling.nodeValue;
6524 textNodeOffset += nodeValue.length;
6525
6526 // We are at or passed the position we where looking for
6527 if (textNodeOffset >= offset) {
6528 container = sibling;
6529 textNodeOffset -= offset;
6530 textNodeOffset = nodeValue.length - textNodeOffset;
6531 break;
6532 }
6533
6534 sibling = sibling.nextSibling;
6535 }
6536 } else {
6537 // Find the text node and offset
6538 sibling = container.previousSibling;
6539
6540 if (!sibling)
6541 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
6542
6543 // If there isn't any text to loop then use the first position
6544 if (!offset) {
6545 if (container.nodeType == 3)
6546 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
6547 else
6548 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
6549
6550 return;
6551 }
6552
6553 while (sibling) {
6554 textNodeOffset += sibling.nodeValue.length;
6555
6556 // We are at or passed the position we where looking for
6557 if (textNodeOffset >= offset) {
6558 container = sibling;
6559 textNodeOffset -= offset;
6560 break;
6561 }
6562
6563 sibling = sibling.previousSibling;
6564 }
6565 }
6566
6567 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
6568 };
6569
6570 try {
6571 // Find start point
6572 findEndPoint(true);
6573
6574 // Find end point if needed
6575 if (!collapsed)
6576 findEndPoint();
6577 } catch (ex) {
6578 // IE has a nasty bug where text nodes might throw "invalid argument" when you
6579 // access the nodeValue or other properties of text nodes. This seems to happend when
6580 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
6581 if (ex.number == -2147024809) {
6582 // Get the current selection
6583 bookmark = self.getBookmark(2);
6584
6585 // Get start element
6586 tmpRange = ieRange.duplicate();
6587 tmpRange.collapse(true);
6588 element = tmpRange.parentElement();
6589
6590 // Get end element
6591 if (!collapsed) {
6592 tmpRange = ieRange.duplicate();
6593 tmpRange.collapse(false);
6594 element2 = tmpRange.parentElement();
6595 element2.innerHTML = element2.innerHTML;
6596 }
6597
6598 // Remove the broken elements
6599 element.innerHTML = element.innerHTML;
6600
6601 // Restore the selection
6602 self.moveToBookmark(bookmark);
6603
6604 // Since the range has moved we need to re-get it
6605 ieRange = selection.getRng();
6606
6607 // Find start point
6608 findEndPoint(true);
6609
6610 // Find end point if needed
6611 if (!collapsed)
6612 findEndPoint();
6613 } else
6614 throw ex; // Throw other errors
6615 }
6616
6617 return domRange;
6618 };
6619
6620 this.getBookmark = function(type) {
6621 var rng = selection.getRng(), start, end, bookmark = {};
6622
6623 function getIndexes(node) {
6624 var node, parent, root, children, i, indexes = [];
6625
6626 parent = node.parentNode;
6627 root = dom.getRoot().parentNode;
6628
6629 while (parent != root && parent.nodeType !== 9) {
6630 children = parent.children;
6631
6632 i = children.length;
6633 while (i--) {
6634 if (node === children[i]) {
6635 indexes.push(i);
6636 break;
6637 }
6638 }
6639
6640 node = parent;
6641 parent = parent.parentNode;
6642 }
6643
6644 return indexes;
6645 };
6646
6647 function getBookmarkEndPoint(start) {
6648 var position;
6649
6650 position = getPosition(rng, start);
6651 if (position) {
6652 return {
6653 position : position.position,
6654 offset : position.offset,
6655 indexes : getIndexes(position.node),
6656 inside : position.inside
6657 };
6658 }
6659 };
6660
6661 // Non ubstructive bookmark
6662 if (type === 2) {
6663 // Handle text selection
6664 if (!rng.item) {
6665 bookmark.start = getBookmarkEndPoint(true);
6666
6667 if (!selection.isCollapsed())
6668 bookmark.end = getBookmarkEndPoint();
6669 } else
6670 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
6671 }
6672
6673 return bookmark;
6674 };
6675
6676 this.moveToBookmark = function(bookmark) {
6677 var rng, body = dom.doc.body;
6678
6679 function resolveIndexes(indexes) {
6680 var node, i, idx, children;
6681
6682 node = dom.getRoot();
6683 for (i = indexes.length - 1; i >= 0; i--) {
6684 children = node.children;
6685 idx = indexes[i];
6686
6687 if (idx <= children.length - 1) {
6688 node = children[idx];
6689 }
6690 }
6691
6692 return node;
6693 };
6694
6695 function setBookmarkEndPoint(start) {
6696 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
6697
6698 if (endPoint) {
6699 moveLeft = endPoint.position > 0;
6700
6701 moveRng = body.createTextRange();
6702 moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
6703
6704 offset = endPoint.offset;
6705 if (offset !== undef) {
6706 moveRng.collapse(endPoint.inside || moveLeft);
6707 moveRng.moveStart('character', moveLeft ? -offset : offset);
6708 } else
6709 moveRng.collapse(start);
6710
6711 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
6712
6713 if (start)
6714 rng.collapse(true);
6715 }
6716 };
6717
6718 if (bookmark.start) {
6719 if (bookmark.start.ctrl) {
6720 rng = body.createControlRange();
6721 rng.addElement(resolveIndexes(bookmark.start.indexes));
6722 rng.select();
6723 } else {
6724 rng = body.createTextRange();
6725 setBookmarkEndPoint(true);
6726 setBookmarkEndPoint();
6727 rng.select();
6728 }
6729 }
6730 };
6731
6732 this.addRange = function(rng) {
6733 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
6734
6735 function setEndPoint(start) {
6736 var container, offset, marker, tmpRng, nodes;
6737
6738 marker = dom.create('a');
6739 container = start ? startContainer : endContainer;
6740 offset = start ? startOffset : endOffset;
6741 tmpRng = ieRng.duplicate();
6742
6743 if (container == doc || container == doc.documentElement) {
6744 container = body;
6745 offset = 0;
6746 }
6747
6748 if (container.nodeType == 3) {
6749 container.parentNode.insertBefore(marker, container);
6750 tmpRng.moveToElementText(marker);
6751 tmpRng.moveStart('character', offset);
6752 dom.remove(marker);
6753 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
6754 } else {
6755 nodes = container.childNodes;
6756
6757 if (nodes.length) {
6758 if (offset >= nodes.length) {
6759 dom.insertAfter(marker, nodes[nodes.length - 1]);
6760 } else {
6761 container.insertBefore(marker, nodes[offset]);
6762 }
6763
6764 tmpRng.moveToElementText(marker);
6765 } else if (container.canHaveHTML) {
6766 // Empty node selection for example <div>|</div>
6767 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
6768 container.innerHTML = '<span>\uFEFF</span>';
6769 marker = container.firstChild;
6770 tmpRng.moveToElementText(marker);
6771 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
6772 }
6773
6774 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
6775 dom.remove(marker);
6776 }
6777 }
6778
6779 // Setup some shorter versions
6780 startContainer = rng.startContainer;
6781 startOffset = rng.startOffset;
6782 endContainer = rng.endContainer;
6783 endOffset = rng.endOffset;
6784 ieRng = body.createTextRange();
6785
6786 // If single element selection then try making a control selection out of it
6787 if (startContainer == endContainer && startContainer.nodeType == 1) {
6788 // Trick to place the caret inside an empty block element like <p></p>
6789 if (!startContainer.hasChildNodes()) {
6790 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
6791 ieRng.moveToElementText(startContainer.lastChild);
6792 ieRng.select();
6793 dom.doc.selection.clear();
6794 startContainer.innerHTML = '';
6795 return;
6796 }
6797
6798 if (startOffset == endOffset - 1) {
6799 try {
6800 ctrlRng = body.createControlRange();
6801 ctrlRng.addElement(startContainer.childNodes[startOffset]);
6802 ctrlRng.select();
6803 return;
6804 } catch (ex) {
6805 // Ignore
6806 }
6807 }
6808 }
6809
6810 // Set start/end point of selection
6811 setEndPoint(true);
6812 setEndPoint();
6813
6814 // Select the new range and scroll it into view
6815 ieRng.select();
6816 };
6817
6818 // Expose range method
6819 this.getRangeAt = getRange;
6820 };
6821
6822 // Expose the selection object
6823 tinymce.dom.TridentSelection = Selection;
6824 })();
6825
6826
6827 /*
6828 * Sizzle CSS Selector Engine - v1.0
6829 * Copyright 2009, The Dojo Foundation
6830 * Released under the MIT, BSD, and GPL Licenses.
6831 * More information: http://sizzlejs.com/
6832 */
6833 (function(){
6834
6835 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
6836 done = 0,
6837 toString = Object.prototype.toString,
6838 hasDuplicate = false,
6839 baseHasDuplicate = true;
6840
6841 // Here we check if the JavaScript engine is using some sort of
6842 // optimization where it does not always call our comparision
6843 // function. If that is the case, discard the hasDuplicate value.
6844 // Thus far that includes Google Chrome.
6845 [0, 0].sort(function(){
6846 baseHasDuplicate = false;
6847 return 0;
6848 });
6849
6850 var Sizzle = function(selector, context, results, seed) {
6851 results = results || [];
6852 context = context || document;
6853
6854 var origContext = context;
6855
6856 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
6857 return [];
6858 }
6859
6860 if ( !selector || typeof selector !== "string" ) {
6861 return results;
6862 }
6863
6864 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
6865 soFar = selector, ret, cur, pop, i;
6866
6867 // Reset the position of the chunker regexp (start from head)
6868 do {
6869 chunker.exec("");
6870 m = chunker.exec(soFar);
6871
6872 if ( m ) {
6873 soFar = m[3];
6874
6875 parts.push( m[1] );
6876
6877 if ( m[2] ) {
6878 extra = m[3];
6879 break;
6880 }
6881 }
6882 } while ( m );
6883
6884 if ( parts.length > 1 && origPOS.exec( selector ) ) {
6885 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
6886 set = posProcess( parts[0] + parts[1], context );
6887 } else {
6888 set = Expr.relative[ parts[0] ] ?
6889 [ context ] :
6890 Sizzle( parts.shift(), context );
6891
6892 while ( parts.length ) {
6893 selector = parts.shift();
6894
6895 if ( Expr.relative[ selector ] ) {
6896 selector += parts.shift();
6897 }
6898
6899 set = posProcess( selector, set );
6900 }
6901 }
6902 } else {
6903 // Take a shortcut and set the context if the root selector is an ID
6904 // (but not if it'll be faster if the inner selector is an ID)
6905 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
6906 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
6907 ret = Sizzle.find( parts.shift(), context, contextXML );
6908 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
6909 }
6910
6911 if ( context ) {
6912 ret = seed ?
6913 { expr: parts.pop(), set: makeArray(seed) } :
6914 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
6915 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
6916
6917 if ( parts.length > 0 ) {
6918 checkSet = makeArray(set);
6919 } else {
6920 prune = false;
6921 }
6922
6923 while ( parts.length ) {
6924 cur = parts.pop();
6925 pop = cur;
6926
6927 if ( !Expr.relative[ cur ] ) {
6928 cur = "";
6929 } else {
6930 pop = parts.pop();
6931 }
6932
6933 if ( pop == null ) {
6934 pop = context;
6935 }
6936
6937 Expr.relative[ cur ]( checkSet, pop, contextXML );
6938 }
6939 } else {
6940 checkSet = parts = [];
6941 }
6942 }
6943
6944 if ( !checkSet ) {
6945 checkSet = set;
6946 }
6947
6948 if ( !checkSet ) {
6949 Sizzle.error( cur || selector );
6950 }
6951
6952 if ( toString.call(checkSet) === "[object Array]" ) {
6953 if ( !prune ) {
6954 results.push.apply( results, checkSet );
6955 } else if ( context && context.nodeType === 1 ) {
6956 for ( i = 0; checkSet[i] != null; i++ ) {
6957 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
6958 results.push( set[i] );
6959 }
6960 }
6961 } else {
6962 for ( i = 0; checkSet[i] != null; i++ ) {
6963 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
6964 results.push( set[i] );
6965 }
6966 }
6967 }
6968 } else {
6969 makeArray( checkSet, results );
6970 }
6971
6972 if ( extra ) {
6973 Sizzle( extra, origContext, results, seed );
6974 Sizzle.uniqueSort( results );
6975 }
6976
6977 return results;
6978 };
6979
6980 Sizzle.uniqueSort = function(results){
6981 if ( sortOrder ) {
6982 hasDuplicate = baseHasDuplicate;
6983 results.sort(sortOrder);
6984
6985 if ( hasDuplicate ) {
6986 for ( var i = 1; i < results.length; i++ ) {
6987 if ( results[i] === results[i-1] ) {
6988 results.splice(i--, 1);
6989 }
6990 }
6991 }
6992 }
6993
6994 return results;
6995 };
6996
6997 Sizzle.matches = function(expr, set){
6998 return Sizzle(expr, null, null, set);
6999 };
7000
7001 Sizzle.find = function(expr, context, isXML){
7002 var set;
7003
7004 if ( !expr ) {
7005 return [];
7006 }
7007
7008 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
7009 var type = Expr.order[i], match;
7010
7011 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7012 var left = match[1];
7013 match.splice(1,1);
7014
7015 if ( left.substr( left.length - 1 ) !== "\\" ) {
7016 match[1] = (match[1] || "").replace(/\\/g, "");
7017 set = Expr.find[ type ]( match, context, isXML );
7018 if ( set != null ) {
7019 expr = expr.replace( Expr.match[ type ], "" );
7020 break;
7021 }
7022 }
7023 }
7024 }
7025
7026 if ( !set ) {
7027 set = context.getElementsByTagName("*");
7028 }
7029
7030 return {set: set, expr: expr};
7031 };
7032
7033 Sizzle.filter = function(expr, set, inplace, not){
7034 var old = expr, result = [], curLoop = set, match, anyFound,
7035 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
7036
7037 while ( expr && set.length ) {
7038 for ( var type in Expr.filter ) {
7039 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
7040 var filter = Expr.filter[ type ], found, item, left = match[1];
7041 anyFound = false;
7042
7043 match.splice(1,1);
7044
7045 if ( left.substr( left.length - 1 ) === "\\" ) {
7046 continue;
7047 }
7048
7049 if ( curLoop === result ) {
7050 result = [];
7051 }
7052
7053 if ( Expr.preFilter[ type ] ) {
7054 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
7055
7056 if ( !match ) {
7057 anyFound = found = true;
7058 } else if ( match === true ) {
7059 continue;
7060 }
7061 }
7062
7063 if ( match ) {
7064 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
7065 if ( item ) {
7066 found = filter( item, match, i, curLoop );
7067 var pass = not ^ !!found;
7068
7069 if ( inplace && found != null ) {
7070 if ( pass ) {
7071 anyFound = true;
7072 } else {
7073 curLoop[i] = false;
7074 }
7075 } else if ( pass ) {
7076 result.push( item );
7077 anyFound = true;
7078 }
7079 }
7080 }
7081 }
7082
7083 if ( found !== undefined ) {
7084 if ( !inplace ) {
7085 curLoop = result;
7086 }
7087
7088 expr = expr.replace( Expr.match[ type ], "" );
7089
7090 if ( !anyFound ) {
7091 return [];
7092 }
7093
7094 break;
7095 }
7096 }
7097 }
7098
7099 // Improper expression
7100 if ( expr === old ) {
7101 if ( anyFound == null ) {
7102 Sizzle.error( expr );
7103 } else {
7104 break;
7105 }
7106 }
7107
7108 old = expr;
7109 }
7110
7111 return curLoop;
7112 };
7113
7114 Sizzle.error = function( msg ) {
7115 throw "Syntax error, unrecognized expression: " + msg;
7116 };
7117
7118 var Expr = Sizzle.selectors = {
7119 order: [ "ID", "NAME", "TAG" ],
7120 match: {
7121 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7122 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7123 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
7124 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
7125 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
7126 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
7127 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
7128 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
7129 },
7130 leftMatch: {},
7131 attrMap: {
7132 "class": "className",
7133 "for": "htmlFor"
7134 },
7135 attrHandle: {
7136 href: function(elem){
7137 return elem.getAttribute("href");
7138 }
7139 },
7140 relative: {
7141 "+": function(checkSet, part){
7142 var isPartStr = typeof part === "string",
7143 isTag = isPartStr && !/\W/.test(part),
7144 isPartStrNotTag = isPartStr && !isTag;
7145
7146 if ( isTag ) {
7147 part = part.toLowerCase();
7148 }
7149
7150 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
7151 if ( (elem = checkSet[i]) ) {
7152 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
7153
7154 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
7155 elem || false :
7156 elem === part;
7157 }
7158 }
7159
7160 if ( isPartStrNotTag ) {
7161 Sizzle.filter( part, checkSet, true );
7162 }
7163 },
7164 ">": function(checkSet, part){
7165 var isPartStr = typeof part === "string",
7166 elem, i = 0, l = checkSet.length;
7167
7168 if ( isPartStr && !/\W/.test(part) ) {
7169 part = part.toLowerCase();
7170
7171 for ( ; i < l; i++ ) {
7172 elem = checkSet[i];
7173 if ( elem ) {
7174 var parent = elem.parentNode;
7175 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
7176 }
7177 }
7178 } else {
7179 for ( ; i < l; i++ ) {
7180 elem = checkSet[i];
7181 if ( elem ) {
7182 checkSet[i] = isPartStr ?
7183 elem.parentNode :
7184 elem.parentNode === part;
7185 }
7186 }
7187
7188 if ( isPartStr ) {
7189 Sizzle.filter( part, checkSet, true );
7190 }
7191 }
7192 },
7193 "": function(checkSet, part, isXML){
7194 var doneName = done++, checkFn = dirCheck, nodeCheck;
7195
7196 if ( typeof part === "string" && !/\W/.test(part) ) {
7197 part = part.toLowerCase();
7198 nodeCheck = part;
7199 checkFn = dirNodeCheck;
7200 }
7201
7202 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
7203 },
7204 "~": function(checkSet, part, isXML){
7205 var doneName = done++, checkFn = dirCheck, nodeCheck;
7206
7207 if ( typeof part === "string" && !/\W/.test(part) ) {
7208 part = part.toLowerCase();
7209 nodeCheck = part;
7210 checkFn = dirNodeCheck;
7211 }
7212
7213 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
7214 }
7215 },
7216 find: {
7217 ID: function(match, context, isXML){
7218 if ( typeof context.getElementById !== "undefined" && !isXML ) {
7219 var m = context.getElementById(match[1]);
7220 return m ? [m] : [];
7221 }
7222 },
7223 NAME: function(match, context){
7224 if ( typeof context.getElementsByName !== "undefined" ) {
7225 var ret = [], results = context.getElementsByName(match[1]);
7226
7227 for ( var i = 0, l = results.length; i < l; i++ ) {
7228 if ( results[i].getAttribute("name") === match[1] ) {
7229 ret.push( results[i] );
7230 }
7231 }
7232
7233 return ret.length === 0 ? null : ret;
7234 }
7235 },
7236 TAG: function(match, context){
7237 return context.getElementsByTagName(match[1]);
7238 }
7239 },
7240 preFilter: {
7241 CLASS: function(match, curLoop, inplace, result, not, isXML){
7242 match = " " + match[1].replace(/\\/g, "") + " ";
7243
7244 if ( isXML ) {
7245 return match;
7246 }
7247
7248 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
7249 if ( elem ) {
7250 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
7251 if ( !inplace ) {
7252 result.push( elem );
7253 }
7254 } else if ( inplace ) {
7255 curLoop[i] = false;
7256 }
7257 }
7258 }
7259
7260 return false;
7261 },
7262 ID: function(match){
7263 return match[1].replace(/\\/g, "");
7264 },
7265 TAG: function(match, curLoop){
7266 return match[1].toLowerCase();
7267 },
7268 CHILD: function(match){
7269 if ( match[1] === "nth" ) {
7270 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
7271 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
7272 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
7273 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
7274
7275 // calculate the numbers (first)n+(last) including if they are negative
7276 match[2] = (test[1] + (test[2] || 1)) - 0;
7277 match[3] = test[3] - 0;
7278 }
7279
7280 // TODO: Move to normal caching system
7281 match[0] = done++;
7282
7283 return match;
7284 },
7285 ATTR: function(match, curLoop, inplace, result, not, isXML){
7286 var name = match[1].replace(/\\/g, "");
7287
7288 if ( !isXML && Expr.attrMap[name] ) {
7289 match[1] = Expr.attrMap[name];
7290 }
7291
7292 if ( match[2] === "~=" ) {
7293 match[4] = " " + match[4] + " ";
7294 }
7295
7296 return match;
7297 },
7298 PSEUDO: function(match, curLoop, inplace, result, not){
7299 if ( match[1] === "not" ) {
7300 // If we're dealing with a complex expression, or a simple one
7301 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
7302 match[3] = Sizzle(match[3], null, null, curLoop);
7303 } else {
7304 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
7305 if ( !inplace ) {
7306 result.push.apply( result, ret );
7307 }
7308 return false;
7309 }
7310 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
7311 return true;
7312 }
7313
7314 return match;
7315 },
7316 POS: function(match){
7317 match.unshift( true );
7318 return match;
7319 }
7320 },
7321 filters: {
7322 enabled: function(elem){
7323 return elem.disabled === false && elem.type !== "hidden";
7324 },
7325 disabled: function(elem){
7326 return elem.disabled === true;
7327 },
7328 checked: function(elem){
7329 return elem.checked === true;
7330 },
7331 selected: function(elem){
7332 // Accessing this property makes selected-by-default
7333 // options in Safari work properly
7334 elem.parentNode.selectedIndex;
7335 return elem.selected === true;
7336 },
7337 parent: function(elem){
7338 return !!elem.firstChild;
7339 },
7340 empty: function(elem){
7341 return !elem.firstChild;
7342 },
7343 has: function(elem, i, match){
7344 return !!Sizzle( match[3], elem ).length;
7345 },
7346 header: function(elem){
7347 return (/h\d/i).test( elem.nodeName );
7348 },
7349 text: function(elem){
7350 return "text" === elem.type;
7351 },
7352 radio: function(elem){
7353 return "radio" === elem.type;
7354 },
7355 checkbox: function(elem){
7356 return "checkbox" === elem.type;
7357 },
7358 file: function(elem){
7359 return "file" === elem.type;
7360 },
7361 password: function(elem){
7362 return "password" === elem.type;
7363 },
7364 submit: function(elem){
7365 return "submit" === elem.type;
7366 },
7367 image: function(elem){
7368 return "image" === elem.type;
7369 },
7370 reset: function(elem){
7371 return "reset" === elem.type;
7372 },
7373 button: function(elem){
7374 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
7375 },
7376 input: function(elem){
7377 return (/input|select|textarea|button/i).test(elem.nodeName);
7378 }
7379 },
7380 setFilters: {
7381 first: function(elem, i){
7382 return i === 0;
7383 },
7384 last: function(elem, i, match, array){
7385 return i === array.length - 1;
7386 },
7387 even: function(elem, i){
7388 return i % 2 === 0;
7389 },
7390 odd: function(elem, i){
7391 return i % 2 === 1;
7392 },
7393 lt: function(elem, i, match){
7394 return i < match[3] - 0;
7395 },
7396 gt: function(elem, i, match){
7397 return i > match[3] - 0;
7398 },
7399 nth: function(elem, i, match){
7400 return match[3] - 0 === i;
7401 },
7402 eq: function(elem, i, match){
7403 return match[3] - 0 === i;
7404 }
7405 },
7406 filter: {
7407 PSEUDO: function(elem, match, i, array){
7408 var name = match[1], filter = Expr.filters[ name ];
7409
7410 if ( filter ) {
7411 return filter( elem, i, match, array );
7412 } else if ( name === "contains" ) {
7413 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
7414 } else if ( name === "not" ) {
7415 var not = match[3];
7416
7417 for ( var j = 0, l = not.length; j < l; j++ ) {
7418 if ( not[j] === elem ) {
7419 return false;
7420 }
7421 }
7422
7423 return true;
7424 } else {
7425 Sizzle.error( "Syntax error, unrecognized expression: " + name );
7426 }
7427 },
7428 CHILD: function(elem, match){
7429 var type = match[1], node = elem;
7430 switch (type) {
7431 case 'only':
7432 case 'first':
7433 while ( (node = node.previousSibling) ) {
7434 if ( node.nodeType === 1 ) {
7435 return false;
7436 }
7437 }
7438 if ( type === "first" ) {
7439 return true;
7440 }
7441 node = elem;
7442 case 'last':
7443 while ( (node = node.nextSibling) ) {
7444 if ( node.nodeType === 1 ) {
7445 return false;
7446 }
7447 }
7448 return true;
7449 case 'nth':
7450 var first = match[2], last = match[3];
7451
7452 if ( first === 1 && last === 0 ) {
7453 return true;
7454 }
7455
7456 var doneName = match[0],
7457 parent = elem.parentNode;
7458
7459 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
7460 var count = 0;
7461 for ( node = parent.firstChild; node; node = node.nextSibling ) {
7462 if ( node.nodeType === 1 ) {
7463 node.nodeIndex = ++count;
7464 }
7465 }
7466 parent.sizcache = doneName;
7467 }
7468
7469 var diff = elem.nodeIndex - last;
7470 if ( first === 0 ) {
7471 return diff === 0;
7472 } else {
7473 return ( diff % first === 0 && diff / first >= 0 );
7474 }
7475 }
7476 },
7477 ID: function(elem, match){
7478 return elem.nodeType === 1 && elem.getAttribute("id") === match;
7479 },
7480 TAG: function(elem, match){
7481 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
7482 },
7483 CLASS: function(elem, match){
7484 return (" " + (elem.className || elem.getAttribute("class")) + " ")
7485 .indexOf( match ) > -1;
7486 },
7487 ATTR: function(elem, match){
7488 var name = match[1],
7489 result = Expr.attrHandle[ name ] ?
7490 Expr.attrHandle[ name ]( elem ) :
7491 elem[ name ] != null ?
7492 elem[ name ] :
7493 elem.getAttribute( name ),
7494 value = result + "",
7495 type = match[2],
7496 check = match[4];
7497
7498 return result == null ?
7499 type === "!=" :
7500 type === "=" ?
7501 value === check :
7502 type === "*=" ?
7503 value.indexOf(check) >= 0 :
7504 type === "~=" ?
7505 (" " + value + " ").indexOf(check) >= 0 :
7506 !check ?
7507 value && result !== false :
7508 type === "!=" ?
7509 value !== check :
7510 type === "^=" ?
7511 value.indexOf(check) === 0 :
7512 type === "$=" ?
7513 value.substr(value.length - check.length) === check :
7514 type === "|=" ?
7515 value === check || value.substr(0, check.length + 1) === check + "-" :
7516 false;
7517 },
7518 POS: function(elem, match, i, array){
7519 var name = match[2], filter = Expr.setFilters[ name ];
7520
7521 if ( filter ) {
7522 return filter( elem, i, match, array );
7523 }
7524 }
7525 }
7526 };
7527
7528 var origPOS = Expr.match.POS,
7529 fescape = function(all, num){
7530 return "\\" + (num - 0 + 1);
7531 };
7532
7533 for ( var type in Expr.match ) {
7534 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
7535 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
7536 }
7537
7538 var makeArray = function(array, results) {
7539 array = Array.prototype.slice.call( array, 0 );
7540
7541 if ( results ) {
7542 results.push.apply( results, array );
7543 return results;
7544 }
7545
7546 return array;
7547 };
7548
7549 // Perform a simple check to determine if the browser is capable of
7550 // converting a NodeList to an array using builtin methods.
7551 // Also verifies that the returned array holds DOM nodes
7552 // (which is not the case in the Blackberry browser)
7553 try {
7554 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
7555
7556 // Provide a fallback method if it does not work
7557 } catch(e){
7558 makeArray = function(array, results) {
7559 var ret = results || [], i = 0;
7560
7561 if ( toString.call(array) === "[object Array]" ) {
7562 Array.prototype.push.apply( ret, array );
7563 } else {
7564 if ( typeof array.length === "number" ) {
7565 for ( var l = array.length; i < l; i++ ) {
7566 ret.push( array[i] );
7567 }
7568 } else {
7569 for ( ; array[i]; i++ ) {
7570 ret.push( array[i] );
7571 }
7572 }
7573 }
7574
7575 return ret;
7576 };
7577 }
7578
7579 var sortOrder;
7580
7581 if ( document.documentElement.compareDocumentPosition ) {
7582 sortOrder = function( a, b ) {
7583 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
7584 if ( a == b ) {
7585 hasDuplicate = true;
7586 }
7587 return a.compareDocumentPosition ? -1 : 1;
7588 }
7589
7590 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
7591 if ( ret === 0 ) {
7592 hasDuplicate = true;
7593 }
7594 return ret;
7595 };
7596 } else if ( "sourceIndex" in document.documentElement ) {
7597 sortOrder = function( a, b ) {
7598 if ( !a.sourceIndex || !b.sourceIndex ) {
7599 if ( a == b ) {
7600 hasDuplicate = true;
7601 }
7602 return a.sourceIndex ? -1 : 1;
7603 }
7604
7605 var ret = a.sourceIndex - b.sourceIndex;
7606 if ( ret === 0 ) {
7607 hasDuplicate = true;
7608 }
7609 return ret;
7610 };
7611 } else if ( document.createRange ) {
7612 sortOrder = function( a, b ) {
7613 if ( !a.ownerDocument || !b.ownerDocument ) {
7614 if ( a == b ) {
7615 hasDuplicate = true;
7616 }
7617 return a.ownerDocument ? -1 : 1;
7618 }
7619
7620 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
7621 aRange.setStart(a, 0);
7622 aRange.setEnd(a, 0);
7623 bRange.setStart(b, 0);
7624 bRange.setEnd(b, 0);
7625 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
7626 if ( ret === 0 ) {
7627 hasDuplicate = true;
7628 }
7629 return ret;
7630 };
7631 }
7632
7633 // Utility function for retreiving the text value of an array of DOM nodes
7634 Sizzle.getText = function( elems ) {
7635 var ret = "", elem;
7636
7637 for ( var i = 0; elems[i]; i++ ) {
7638 elem = elems[i];
7639
7640 // Get the text from text nodes and CDATA nodes
7641 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
7642 ret += elem.nodeValue;
7643
7644 // Traverse everything else, except comment nodes
7645 } else if ( elem.nodeType !== 8 ) {
7646 ret += Sizzle.getText( elem.childNodes );
7647 }
7648 }
7649
7650 return ret;
7651 };
7652
7653 // Check to see if the browser returns elements by name when
7654 // querying by getElementById (and provide a workaround)
7655 (function(){
7656 // We're going to inject a fake input element with a specified name
7657 var form = document.createElement("div"),
7658 id = "script" + (new Date()).getTime();
7659 form.innerHTML = "<a name='" + id + "'/>";
7660
7661 // Inject it into the root element, check its status, and remove it quickly
7662 var root = document.documentElement;
7663 root.insertBefore( form, root.firstChild );
7664
7665 // The workaround has to do additional checks after a getElementById
7666 // Which slows things down for other browsers (hence the branching)
7667 if ( document.getElementById( id ) ) {
7668 Expr.find.ID = function(match, context, isXML){
7669 if ( typeof context.getElementById !== "undefined" && !isXML ) {
7670 var m = context.getElementById(match[1]);
7671 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
7672 }
7673 };
7674
7675 Expr.filter.ID = function(elem, match){
7676 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
7677 return elem.nodeType === 1 && node && node.nodeValue === match;
7678 };
7679 }
7680
7681 root.removeChild( form );
7682 root = form = null; // release memory in IE
7683 })();
7684
7685 (function(){
7686 // Check to see if the browser returns only elements
7687 // when doing getElementsByTagName("*")
7688
7689 // Create a fake element
7690 var div = document.createElement("div");
7691 div.appendChild( document.createComment("") );
7692
7693 // Make sure no comments are found
7694 if ( div.getElementsByTagName("*").length > 0 ) {
7695 Expr.find.TAG = function(match, context){
7696 var results = context.getElementsByTagName(match[1]);
7697
7698 // Filter out possible comments
7699 if ( match[1] === "*" ) {
7700 var tmp = [];
7701
7702 for ( var i = 0; results[i]; i++ ) {
7703 if ( results[i].nodeType === 1 ) {
7704 tmp.push( results[i] );
7705 }
7706 }
7707
7708 results = tmp;
7709 }
7710
7711 return results;
7712 };
7713 }
7714
7715 // Check to see if an attribute returns normalized href attributes
7716 div.innerHTML = "<a href='#'></a>";
7717 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
7718 div.firstChild.getAttribute("href") !== "#" ) {
7719 Expr.attrHandle.href = function(elem){
7720 return elem.getAttribute("href", 2);
7721 };
7722 }
7723
7724 div = null; // release memory in IE
7725 })();
7726
7727 if ( document.querySelectorAll ) {
7728 (function(){
7729 var oldSizzle = Sizzle, div = document.createElement("div");
7730 div.innerHTML = "<p class='TEST'></p>";
7731
7732 // Safari can't handle uppercase or unicode characters when
7733 // in quirks mode.
7734 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
7735 return;
7736 }
7737
7738 Sizzle = function(query, context, extra, seed){
7739 context = context || document;
7740
7741 // Only use querySelectorAll on non-XML documents
7742 // (ID selectors don't work in non-HTML documents)
7743 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
7744 try {
7745 return makeArray( context.querySelectorAll(query), extra );
7746 } catch(e){}
7747 }
7748
7749 return oldSizzle(query, context, extra, seed);
7750 };
7751
7752 for ( var prop in oldSizzle ) {
7753 Sizzle[ prop ] = oldSizzle[ prop ];
7754 }
7755
7756 div = null; // release memory in IE
7757 })();
7758 }
7759
7760 (function(){
7761 var div = document.createElement("div");
7762
7763 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
7764
7765 // Opera can't find a second classname (in 9.6)
7766 // Also, make sure that getElementsByClassName actually exists
7767 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
7768 return;
7769 }
7770
7771 // Safari caches class attributes, doesn't catch changes (in 3.2)
7772 div.lastChild.className = "e";
7773
7774 if ( div.getElementsByClassName("e").length === 1 ) {
7775 return;
7776 }
7777
7778 Expr.order.splice(1, 0, "CLASS");
7779 Expr.find.CLASS = function(match, context, isXML) {
7780 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
7781 return context.getElementsByClassName(match[1]);
7782 }
7783 };
7784
7785 div = null; // release memory in IE
7786 })();
7787
7788 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
7789 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
7790 var elem = checkSet[i];
7791 if ( elem ) {
7792 elem = elem[dir];
7793 var match = false;
7794
7795 while ( elem ) {
7796 if ( elem.sizcache === doneName ) {
7797 match = checkSet[elem.sizset];
7798 break;
7799 }
7800
7801 if ( elem.nodeType === 1 && !isXML ){
7802 elem.sizcache = doneName;
7803 elem.sizset = i;
7804 }
7805
7806 if ( elem.nodeName.toLowerCase() === cur ) {
7807 match = elem;
7808 break;
7809 }
7810
7811 elem = elem[dir];
7812 }
7813
7814 checkSet[i] = match;
7815 }
7816 }
7817 }
7818
7819 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
7820 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
7821 var elem = checkSet[i];
7822 if ( elem ) {
7823 elem = elem[dir];
7824 var match = false;
7825
7826 while ( elem ) {
7827 if ( elem.sizcache === doneName ) {
7828 match = checkSet[elem.sizset];
7829 break;
7830 }
7831
7832 if ( elem.nodeType === 1 ) {
7833 if ( !isXML ) {
7834 elem.sizcache = doneName;
7835 elem.sizset = i;
7836 }
7837 if ( typeof cur !== "string" ) {
7838 if ( elem === cur ) {
7839 match = true;
7840 break;
7841 }
7842
7843 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
7844 match = elem;
7845 break;
7846 }
7847 }
7848
7849 elem = elem[dir];
7850 }
7851
7852 checkSet[i] = match;
7853 }
7854 }
7855 }
7856
7857 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
7858 return !!(a.compareDocumentPosition(b) & 16);
7859 } : function(a, b){
7860 return a !== b && (a.contains ? a.contains(b) : true);
7861 };
7862
7863 Sizzle.isXML = function(elem){
7864 // documentElement is verified for cases where it doesn't yet exist
7865 // (such as loading iframes in IE - #4833)
7866 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
7867 return documentElement ? documentElement.nodeName !== "HTML" : false;
7868 };
7869
7870 var posProcess = function(selector, context){
7871 var tmpSet = [], later = "", match,
7872 root = context.nodeType ? [context] : context;
7873
7874 // Position selectors must be done after the filter
7875 // And so must :not(positional) so we move all PSEUDOs to the end
7876 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
7877 later += match[0];
7878 selector = selector.replace( Expr.match.PSEUDO, "" );
7879 }
7880
7881 selector = Expr.relative[selector] ? selector + "*" : selector;
7882
7883 for ( var i = 0, l = root.length; i < l; i++ ) {
7884 Sizzle( selector, root[i], tmpSet );
7885 }
7886
7887 return Sizzle.filter( later, tmpSet );
7888 };
7889
7890 // EXPOSE
7891
7892 window.tinymce.dom.Sizzle = Sizzle;
7893
7894 })();
7895
7896
7897 (function(tinymce) {
7898 tinymce.dom.Element = function(id, settings) {
7899 var t = this, dom, el;
7900
7901 t.settings = settings = settings || {};
7902 t.id = id;
7903 t.dom = dom = settings.dom || tinymce.DOM;
7904
7905 // Only IE leaks DOM references, this is a lot faster
7906 if (!tinymce.isIE)
7907 el = dom.get(t.id);
7908
7909 tinymce.each(
7910 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
7911 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
7912 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
7913 'isHidden,setHTML,get').split(/,/)
7914 , function(k) {
7915 t[k] = function() {
7916 var a = [id], i;
7917
7918 for (i = 0; i < arguments.length; i++)
7919 a.push(arguments[i]);
7920
7921 a = dom[k].apply(dom, a);
7922 t.update(k);
7923
7924 return a;
7925 };
7926 });
7927
7928 tinymce.extend(t, {
7929 on : function(n, f, s) {
7930 return tinymce.dom.Event.add(t.id, n, f, s);
7931 },
7932
7933 getXY : function() {
7934 return {
7935 x : parseInt(t.getStyle('left')),
7936 y : parseInt(t.getStyle('top'))
7937 };
7938 },
7939
7940 getSize : function() {
7941 var n = dom.get(t.id);
7942
7943 return {
7944 w : parseInt(t.getStyle('width') || n.clientWidth),
7945 h : parseInt(t.getStyle('height') || n.clientHeight)
7946 };
7947 },
7948
7949 moveTo : function(x, y) {
7950 t.setStyles({left : x, top : y});
7951 },
7952
7953 moveBy : function(x, y) {
7954 var p = t.getXY();
7955
7956 t.moveTo(p.x + x, p.y + y);
7957 },
7958
7959 resizeTo : function(w, h) {
7960 t.setStyles({width : w, height : h});
7961 },
7962
7963 resizeBy : function(w, h) {
7964 var s = t.getSize();
7965
7966 t.resizeTo(s.w + w, s.h + h);
7967 },
7968
7969 update : function(k) {
7970 var b;
7971
7972 if (tinymce.isIE6 && settings.blocker) {
7973 k = k || '';
7974
7975 // Ignore getters
7976 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
7977 return;
7978
7979 // Remove blocker on remove
7980 if (k == 'remove') {
7981 dom.remove(t.blocker);
7982 return;
7983 }
7984
7985 if (!t.blocker) {
7986 t.blocker = dom.uniqueId();
7987 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
7988 dom.setStyle(b, 'opacity', 0);
7989 } else
7990 b = dom.get(t.blocker);
7991
7992 dom.setStyles(b, {
7993 left : t.getStyle('left', 1),
7994 top : t.getStyle('top', 1),
7995 width : t.getStyle('width', 1),
7996 height : t.getStyle('height', 1),
7997 display : t.getStyle('display', 1),
7998 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
7999 });
8000 }
8001 }
8002 });
8003 };
8004 })(tinymce);
8005
8006 (function(tinymce) {
8007 function trimNl(s) {
8008 return s.replace(/[\n\r]+/g, '');
8009 };
8010
8011 // Shorten names
8012 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
8013
8014 tinymce.create('tinymce.dom.Selection', {
8015 Selection : function(dom, win, serializer) {
8016 var t = this;
8017
8018 t.dom = dom;
8019 t.win = win;
8020 t.serializer = serializer;
8021
8022 // Add events
8023 each([
8024 'onBeforeSetContent',
8025
8026 'onBeforeGetContent',
8027
8028 'onSetContent',
8029
8030 'onGetContent'
8031 ], function(e) {
8032 t[e] = new tinymce.util.Dispatcher(t);
8033 });
8034
8035 // No W3C Range support
8036 if (!t.win.getSelection)
8037 t.tridentSel = new tinymce.dom.TridentSelection(t);
8038
8039 if (tinymce.isIE && dom.boxModel)
8040 this._fixIESelection();
8041
8042 // Prevent leaks
8043 tinymce.addUnload(t.destroy, t);
8044 },
8045
8046 setCursorLocation: function(node, offset) {
8047 var t = this; var r = t.dom.createRng();
8048 r.setStart(node, offset);
8049 r.setEnd(node, offset);
8050 t.setRng(r);
8051 t.collapse(false);
8052 },
8053 getContent : function(s) {
8054 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
8055
8056 s = s || {};
8057 wb = wa = '';
8058 s.get = true;
8059 s.format = s.format || 'html';
8060 s.forced_root_block = '';
8061 t.onBeforeGetContent.dispatch(t, s);
8062
8063 if (s.format == 'text')
8064 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
8065
8066 if (r.cloneContents) {
8067 n = r.cloneContents();
8068
8069 if (n)
8070 e.appendChild(n);
8071 } else if (is(r.item) || is(r.htmlText)) {
8072 // IE will produce invalid markup if elements are present that
8073 // it doesn't understand like custom elements or HTML5 elements.
8074 // Adding a BR in front of the contents and then remoiving it seems to fix it though.
8075 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
8076 e.removeChild(e.firstChild);
8077 } else
8078 e.innerHTML = r.toString();
8079
8080 // Keep whitespace before and after
8081 if (/^\s/.test(e.innerHTML))
8082 wb = ' ';
8083
8084 if (/\s+$/.test(e.innerHTML))
8085 wa = ' ';
8086
8087 s.getInner = true;
8088
8089 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
8090 t.onGetContent.dispatch(t, s);
8091
8092 return s.content;
8093 },
8094
8095 setContent : function(content, args) {
8096 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
8097
8098 args = args || {format : 'html'};
8099 args.set = true;
8100 content = args.content = content;
8101
8102 // Dispatch before set content event
8103 if (!args.no_events)
8104 self.onBeforeSetContent.dispatch(self, args);
8105
8106 content = args.content;
8107
8108 if (rng.insertNode) {
8109 // Make caret marker since insertNode places the caret in the beginning of text after insert
8110 content += '<span id="__caret">_</span>';
8111
8112 // Delete and insert new node
8113 if (rng.startContainer == doc && rng.endContainer == doc) {
8114 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
8115 doc.body.innerHTML = content;
8116 } else {
8117 rng.deleteContents();
8118
8119 if (doc.body.childNodes.length == 0) {
8120 doc.body.innerHTML = content;
8121 } else {
8122 // createContextualFragment doesn't exists in IE 9 DOMRanges
8123 if (rng.createContextualFragment) {
8124 rng.insertNode(rng.createContextualFragment(content));
8125 } else {
8126 // Fake createContextualFragment call in IE 9
8127 frag = doc.createDocumentFragment();
8128 temp = doc.createElement('div');
8129
8130 frag.appendChild(temp);
8131 temp.outerHTML = content;
8132
8133 rng.insertNode(frag);
8134 }
8135 }
8136 }
8137
8138 // Move to caret marker
8139 caretNode = self.dom.get('__caret');
8140
8141 // Make sure we wrap it compleatly, Opera fails with a simple select call
8142 rng = doc.createRange();
8143 rng.setStartBefore(caretNode);
8144 rng.setEndBefore(caretNode);
8145 self.setRng(rng);
8146
8147 // Remove the caret position
8148 self.dom.remove('__caret');
8149
8150 try {
8151 self.setRng(rng);
8152 } catch (ex) {
8153 // Might fail on Opera for some odd reason
8154 }
8155 } else {
8156 if (rng.item) {
8157 // Delete content and get caret text selection
8158 doc.execCommand('Delete', false, null);
8159 rng = self.getRng();
8160 }
8161
8162 // Explorer removes spaces from the beginning of pasted contents
8163 if (/^\s+/.test(content)) {
8164 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
8165 self.dom.remove('__mce_tmp');
8166 } else
8167 rng.pasteHTML(content);
8168 }
8169
8170 // Dispatch set content event
8171 if (!args.no_events)
8172 self.onSetContent.dispatch(self, args);
8173 },
8174
8175 getStart : function() {
8176 var rng = this.getRng(), startElement, parentElement, checkRng, node;
8177
8178 if (rng.duplicate || rng.item) {
8179 // Control selection, return first item
8180 if (rng.item)
8181 return rng.item(0);
8182
8183 // Get start element
8184 checkRng = rng.duplicate();
8185 checkRng.collapse(1);
8186 startElement = checkRng.parentElement();
8187
8188 // Check if range parent is inside the start element, then return the inner parent element
8189 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
8190 parentElement = node = rng.parentElement();
8191 while (node = node.parentNode) {
8192 if (node == startElement) {
8193 startElement = parentElement;
8194 break;
8195 }
8196 }
8197
8198 return startElement;
8199 } else {
8200 startElement = rng.startContainer;
8201
8202 if (startElement.nodeType == 1 && startElement.hasChildNodes())
8203 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
8204
8205 if (startElement && startElement.nodeType == 3)
8206 return startElement.parentNode;
8207
8208 return startElement;
8209 }
8210 },
8211
8212 getEnd : function() {
8213 var t = this, r = t.getRng(), e, eo;
8214
8215 if (r.duplicate || r.item) {
8216 if (r.item)
8217 return r.item(0);
8218
8219 r = r.duplicate();
8220 r.collapse(0);
8221 e = r.parentElement();
8222
8223 if (e && e.nodeName == 'BODY')
8224 return e.lastChild || e;
8225
8226 return e;
8227 } else {
8228 e = r.endContainer;
8229 eo = r.endOffset;
8230
8231 if (e.nodeType == 1 && e.hasChildNodes())
8232 e = e.childNodes[eo > 0 ? eo - 1 : eo];
8233
8234 if (e && e.nodeType == 3)
8235 return e.parentNode;
8236
8237 return e;
8238 }
8239 },
8240
8241 getBookmark : function(type, normalized) {
8242 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
8243
8244 function findIndex(name, element) {
8245 var index = 0;
8246
8247 each(dom.select(name), function(node, i) {
8248 if (node == element)
8249 index = i;
8250 });
8251
8252 return index;
8253 };
8254
8255 if (type == 2) {
8256 function getLocation() {
8257 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
8258
8259 function getPoint(rng, start) {
8260 var container = rng[start ? 'startContainer' : 'endContainer'],
8261 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
8262
8263 if (container.nodeType == 3) {
8264 if (normalized) {
8265 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
8266 offset += node.nodeValue.length;
8267 }
8268
8269 point.push(offset);
8270 } else {
8271 childNodes = container.childNodes;
8272
8273 if (offset >= childNodes.length && childNodes.length) {
8274 after = 1;
8275 offset = Math.max(0, childNodes.length - 1);
8276 }
8277
8278 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
8279 }
8280
8281 for (; container && container != root; container = container.parentNode)
8282 point.push(t.dom.nodeIndex(container, normalized));
8283
8284 return point;
8285 };
8286
8287 bookmark.start = getPoint(rng, true);
8288
8289 if (!t.isCollapsed())
8290 bookmark.end = getPoint(rng);
8291
8292 return bookmark;
8293 };
8294
8295 if (t.tridentSel)
8296 return t.tridentSel.getBookmark(type);
8297
8298 return getLocation();
8299 }
8300
8301 // Handle simple range
8302 if (type)
8303 return {rng : t.getRng()};
8304
8305 rng = t.getRng();
8306 id = dom.uniqueId();
8307 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
8308 styles = 'overflow:hidden;line-height:0px';
8309
8310 // Explorer method
8311 if (rng.duplicate || rng.item) {
8312 // Text selection
8313 if (!rng.item) {
8314 rng2 = rng.duplicate();
8315
8316 try {
8317 // Insert start marker
8318 rng.collapse();
8319 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
8320
8321 // Insert end marker
8322 if (!collapsed) {
8323 rng2.collapse(false);
8324
8325 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
8326 rng.moveToElementText(rng2.parentElement());
8327 if (rng.compareEndPoints('StartToEnd', rng2) == 0)
8328 rng2.move('character', -1);
8329
8330 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
8331 }
8332 } catch (ex) {
8333 // IE might throw unspecified error so lets ignore it
8334 return null;
8335 }
8336 } else {
8337 // Control selection
8338 element = rng.item(0);
8339 name = element.nodeName;
8340
8341 return {name : name, index : findIndex(name, element)};
8342 }
8343 } else {
8344 element = t.getNode();
8345 name = element.nodeName;
8346 if (name == 'IMG')
8347 return {name : name, index : findIndex(name, element)};
8348
8349 // W3C method
8350 rng2 = rng.cloneRange();
8351
8352 // Insert end marker
8353 if (!collapsed) {
8354 rng2.collapse(false);
8355 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
8356 }
8357
8358 rng.collapse(true);
8359 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
8360 }
8361
8362 t.moveToBookmark({id : id, keep : 1});
8363
8364 return {id : id};
8365 },
8366
8367 moveToBookmark : function(bookmark) {
8368 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
8369
8370 if (bookmark) {
8371 if (bookmark.start) {
8372 rng = dom.createRng();
8373 root = dom.getRoot();
8374
8375 function setEndPoint(start) {
8376 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
8377
8378 if (point) {
8379 offset = point[0];
8380
8381 // Find container node
8382 for (node = root, i = point.length - 1; i >= 1; i--) {
8383 children = node.childNodes;
8384
8385 if (point[i] > children.length - 1)
8386 return;
8387
8388 node = children[point[i]];
8389 }
8390
8391 // Move text offset to best suitable location
8392 if (node.nodeType === 3)
8393 offset = Math.min(point[0], node.nodeValue.length);
8394
8395 // Move element offset to best suitable location
8396 if (node.nodeType === 1)
8397 offset = Math.min(point[0], node.childNodes.length);
8398
8399 // Set offset within container node
8400 if (start)
8401 rng.setStart(node, offset);
8402 else
8403 rng.setEnd(node, offset);
8404 }
8405
8406 return true;
8407 };
8408
8409 if (t.tridentSel)
8410 return t.tridentSel.moveToBookmark(bookmark);
8411
8412 if (setEndPoint(true) && setEndPoint()) {
8413 t.setRng(rng);
8414 }
8415 } else if (bookmark.id) {
8416 function restoreEndPoint(suffix) {
8417 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
8418
8419 if (marker) {
8420 node = marker.parentNode;
8421
8422 if (suffix == 'start') {
8423 if (!keep) {
8424 idx = dom.nodeIndex(marker);
8425 } else {
8426 node = marker.firstChild;
8427 idx = 1;
8428 }
8429
8430 startContainer = endContainer = node;
8431 startOffset = endOffset = idx;
8432 } else {
8433 if (!keep) {
8434 idx = dom.nodeIndex(marker);
8435 } else {
8436 node = marker.firstChild;
8437 idx = 1;
8438 }
8439
8440 endContainer = node;
8441 endOffset = idx;
8442 }
8443
8444 if (!keep) {
8445 prev = marker.previousSibling;
8446 next = marker.nextSibling;
8447
8448 // Remove all marker text nodes
8449 each(tinymce.grep(marker.childNodes), function(node) {
8450 if (node.nodeType == 3)
8451 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
8452 });
8453
8454 // Remove marker but keep children if for example contents where inserted into the marker
8455 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
8456 while (marker = dom.get(bookmark.id + '_' + suffix))
8457 dom.remove(marker, 1);
8458
8459 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
8460 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
8461 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
8462 idx = prev.nodeValue.length;
8463 prev.appendData(next.nodeValue);
8464 dom.remove(next);
8465
8466 if (suffix == 'start') {
8467 startContainer = endContainer = prev;
8468 startOffset = endOffset = idx;
8469 } else {
8470 endContainer = prev;
8471 endOffset = idx;
8472 }
8473 }
8474 }
8475 }
8476 };
8477
8478 function addBogus(node) {
8479 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
8480 if (dom.isBlock(node) && !node.innerHTML)
8481 node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
8482
8483 return node;
8484 };
8485
8486 // Restore start/end points
8487 restoreEndPoint('start');
8488 restoreEndPoint('end');
8489
8490 if (startContainer) {
8491 rng = dom.createRng();
8492 rng.setStart(addBogus(startContainer), startOffset);
8493 rng.setEnd(addBogus(endContainer), endOffset);
8494 t.setRng(rng);
8495 }
8496 } else if (bookmark.name) {
8497 t.select(dom.select(bookmark.name)[bookmark.index]);
8498 } else if (bookmark.rng)
8499 t.setRng(bookmark.rng);
8500 }
8501 },
8502
8503 select : function(node, content) {
8504 var t = this, dom = t.dom, rng = dom.createRng(), idx;
8505
8506 if (node) {
8507 idx = dom.nodeIndex(node);
8508 rng.setStart(node.parentNode, idx);
8509 rng.setEnd(node.parentNode, idx + 1);
8510
8511 // Find first/last text node or BR element
8512 if (content) {
8513 function setPoint(node, start) {
8514 var walker = new tinymce.dom.TreeWalker(node, node);
8515
8516 do {
8517 // Text node
8518 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
8519 if (start)
8520 rng.setStart(node, 0);
8521 else
8522 rng.setEnd(node, node.nodeValue.length);
8523
8524 return;
8525 }
8526
8527 // BR element
8528 if (node.nodeName == 'BR') {
8529 if (start)
8530 rng.setStartBefore(node);
8531 else
8532 rng.setEndBefore(node);
8533
8534 return;
8535 }
8536 } while (node = (start ? walker.next() : walker.prev()));
8537 };
8538
8539 setPoint(node, 1);
8540 setPoint(node);
8541 }
8542
8543 t.setRng(rng);
8544 }
8545
8546 return node;
8547 },
8548
8549 isCollapsed : function() {
8550 var t = this, r = t.getRng(), s = t.getSel();
8551
8552 if (!r || r.item)
8553 return false;
8554
8555 if (r.compareEndPoints)
8556 return r.compareEndPoints('StartToEnd', r) === 0;
8557
8558 return !s || r.collapsed;
8559 },
8560
8561 collapse : function(to_start) {
8562 var self = this, rng = self.getRng(), node;
8563
8564 // Control range on IE
8565 if (rng.item) {
8566 node = rng.item(0);
8567 rng = self.win.document.body.createTextRange();
8568 rng.moveToElementText(node);
8569 }
8570
8571 rng.collapse(!!to_start);
8572 self.setRng(rng);
8573 },
8574
8575 getSel : function() {
8576 var t = this, w = this.win;
8577
8578 return w.getSelection ? w.getSelection() : w.document.selection;
8579 },
8580
8581 getRng : function(w3c) {
8582 var t = this, s, r, elm, doc = t.win.document;
8583
8584 // Found tridentSel object then we need to use that one
8585 if (w3c && t.tridentSel)
8586 return t.tridentSel.getRangeAt(0);
8587
8588 try {
8589 if (s = t.getSel())
8590 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
8591 } catch (ex) {
8592 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
8593 }
8594
8595 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
8596 if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
8597 elm = doc.selection.createRange().item(0);
8598 r = doc.createRange();
8599 r.setStartBefore(elm);
8600 r.setEndAfter(elm);
8601 }
8602
8603 // No range found then create an empty one
8604 // This can occur when the editor is placed in a hidden container element on Gecko
8605 // Or on IE when there was an exception
8606 if (!r)
8607 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
8608
8609 if (t.selectedRange && t.explicitRange) {
8610 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
8611 // Safari, Opera and Chrome only ever select text which causes the range to change.
8612 // This lets us use the originally set range if the selection hasn't been changed by the user.
8613 r = t.explicitRange;
8614 } else {
8615 t.selectedRange = null;
8616 t.explicitRange = null;
8617 }
8618 }
8619
8620 return r;
8621 },
8622
8623 setRng : function(r) {
8624 var s, t = this;
8625
8626 if (!t.tridentSel) {
8627 s = t.getSel();
8628
8629 if (s) {
8630 t.explicitRange = r;
8631
8632 try {
8633 s.removeAllRanges();
8634 } catch (ex) {
8635 // IE9 might throw errors here don't know why
8636 }
8637
8638 s.addRange(r);
8639 // adding range isn't always successful so we need to check range count otherwise an exception can occur
8640 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
8641 }
8642 } else {
8643 // Is W3C Range
8644 if (r.cloneRange) {
8645 try {
8646 t.tridentSel.addRange(r);
8647 return;
8648 } catch (ex) {
8649 //IE9 throws an error here if called before selection is placed in the editor
8650 }
8651 }
8652
8653 // Is IE specific range
8654 try {
8655 r.select();
8656 } catch (ex) {
8657 // Needed for some odd IE bug #1843306
8658 }
8659 }
8660 },
8661
8662 setNode : function(n) {
8663 var t = this;
8664
8665 t.setContent(t.dom.getOuterHTML(n));
8666
8667 return n;
8668 },
8669
8670 getNode : function() {
8671 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
8672
8673 // Range maybe lost after the editor is made visible again
8674 if (!rng)
8675 return t.dom.getRoot();
8676
8677 if (rng.setStart) {
8678 elm = rng.commonAncestorContainer;
8679
8680 // Handle selection a image or other control like element such as anchors
8681 if (!rng.collapsed) {
8682 if (rng.startContainer == rng.endContainer) {
8683 if (rng.endOffset - rng.startOffset < 2) {
8684 if (rng.startContainer.hasChildNodes())
8685 elm = rng.startContainer.childNodes[rng.startOffset];
8686 }
8687 }
8688
8689 // If the anchor node is a element instead of a text node then return this element
8690 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
8691 // return sel.anchorNode.childNodes[sel.anchorOffset];
8692
8693 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
8694 // This happens when you double click an underlined word in FireFox.
8695 if (start.nodeType === 3 && end.nodeType === 3) {
8696 function skipEmptyTextNodes(n, forwards) {
8697 var orig = n;
8698 while (n && n.nodeType === 3 && n.length === 0) {
8699 n = forwards ? n.nextSibling : n.previousSibling;
8700 }
8701 return n || orig;
8702 }
8703 if (start.length === rng.startOffset) {
8704 start = skipEmptyTextNodes(start.nextSibling, true);
8705 } else {
8706 start = start.parentNode;
8707 }
8708 if (rng.endOffset === 0) {
8709 end = skipEmptyTextNodes(end.previousSibling, false);
8710 } else {
8711 end = end.parentNode;
8712 }
8713
8714 if (start && start === end)
8715 return start;
8716 }
8717 }
8718
8719 if (elm && elm.nodeType == 3)
8720 return elm.parentNode;
8721
8722 return elm;
8723 }
8724
8725 return rng.item ? rng.item(0) : rng.parentElement();
8726 },
8727
8728 getSelectedBlocks : function(st, en) {
8729 var t = this, dom = t.dom, sb, eb, n, bl = [];
8730
8731 sb = dom.getParent(st || t.getStart(), dom.isBlock);
8732 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
8733
8734 if (sb)
8735 bl.push(sb);
8736
8737 if (sb && eb && sb != eb) {
8738 n = sb;
8739
8740 var walker = new tinymce.dom.TreeWalker(sb, dom.getRoot());
8741 while ((n = walker.next()) && n != eb) {
8742 if (dom.isBlock(n))
8743 bl.push(n);
8744 }
8745 }
8746
8747 if (eb && sb != eb)
8748 bl.push(eb);
8749
8750 return bl;
8751 },
8752
8753 normalize : function() {
8754 var self = this, rng, normalized;
8755
8756 // TODO:
8757 // Retain selection direction.
8758 // Lean left/right on Gecko for inline elements.
8759 // Run this on mouse up/key up when the user manually moves the selection
8760
8761 // Normalize only on non IE browsers for now
8762 if (tinymce.isIE)
8763 return;
8764
8765 function normalizeEndPoint(start) {
8766 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;
8767
8768 container = rng[(start ? 'start' : 'end') + 'Container'];
8769 offset = rng[(start ? 'start' : 'end') + 'Offset'];
8770
8771 // If the container is a document move it to the body element
8772 if (container.nodeType === 9) {
8773 container = container.body;
8774 offset = 0;
8775 }
8776
8777 // If the container is body try move it into the closest text node or position
8778 // TODO: Add more logic here to handle element selection cases
8779 if (container === body) {
8780 // Resolve the index
8781 if (container.hasChildNodes()) {
8782 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
8783 offset = 0;
8784
8785 // Don't walk into elements that doesn't have any child nodes like a IMG
8786 if (container.hasChildNodes()) {
8787 // Walk the DOM to find a text node to place the caret at or a BR
8788 node = container;
8789 walker = new tinymce.dom.TreeWalker(container, body);
8790 do {
8791 // Found a text node use that position
8792 if (node.nodeType === 3) {
8793 offset = start ? 0 : node.nodeValue.length - 1;
8794 container = node;
8795 normalized = true;
8796 break;
8797 }
8798
8799 // Found a BR/IMG element that we can place the caret before
8800 if (/^(BR|IMG)$/.test(node.nodeName)) {
8801 offset = dom.nodeIndex(node);
8802 container = node.parentNode;
8803
8804 // Put caret after image when moving the end point
8805 if (node.nodeName == "IMG" && !start) {
8806 offset++;
8807 }
8808
8809 normalized = true;
8810 break;
8811 }
8812 } while (node = (start ? walker.next() : walker.prev()));
8813 }
8814 }
8815 }
8816
8817 // Set endpoint if it was normalized
8818 if (normalized)
8819 rng['set' + (start ? 'Start' : 'End')](container, offset);
8820 };
8821
8822 rng = self.getRng();
8823
8824 // Normalize the end points
8825 normalizeEndPoint(true);
8826
8827 if (!rng.collapsed)
8828 normalizeEndPoint();
8829
8830 // Set the selection if it was normalized
8831 if (normalized) {
8832 //console.log(self.dom.dumpRng(rng));
8833 self.setRng(rng);
8834 }
8835 },
8836
8837 destroy : function(s) {
8838 var t = this;
8839
8840 t.win = null;
8841
8842 // Manual destroy then remove unload handler
8843 if (!s)
8844 tinymce.removeUnload(t.destroy);
8845 },
8846
8847 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
8848 _fixIESelection : function() {
8849 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
8850
8851 // Make HTML element unselectable since we are going to handle selection by hand
8852 doc.documentElement.unselectable = true;
8853
8854 // Return range from point or null if it failed
8855 function rngFromPoint(x, y) {
8856 var rng = body.createTextRange();
8857
8858 try {
8859 rng.moveToPoint(x, y);
8860 } catch (ex) {
8861 // IE sometimes throws and exception, so lets just ignore it
8862 rng = null;
8863 }
8864
8865 return rng;
8866 };
8867
8868 // Fires while the selection is changing
8869 function selectionChange(e) {
8870 var pointRng;
8871
8872 // Check if the button is down or not
8873 if (e.button) {
8874 // Create range from mouse position
8875 pointRng = rngFromPoint(e.x, e.y);
8876
8877 if (pointRng) {
8878 // Check if pointRange is before/after selection then change the endPoint
8879 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
8880 pointRng.setEndPoint('StartToStart', startRng);
8881 else
8882 pointRng.setEndPoint('EndToEnd', startRng);
8883
8884 pointRng.select();
8885 }
8886 } else
8887 endSelection();
8888 }
8889
8890 // Removes listeners
8891 function endSelection() {
8892 var rng = doc.selection.createRange();
8893
8894 // If the range is collapsed then use the last start range
8895 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
8896 startRng.select();
8897
8898 dom.unbind(doc, 'mouseup', endSelection);
8899 dom.unbind(doc, 'mousemove', selectionChange);
8900 startRng = started = 0;
8901 };
8902
8903 // Detect when user selects outside BODY
8904 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
8905 if (e.target.nodeName === 'HTML') {
8906 if (started)
8907 endSelection();
8908
8909 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
8910 htmlElm = doc.documentElement;
8911 if (htmlElm.scrollHeight > htmlElm.clientHeight)
8912 return;
8913
8914 started = 1;
8915 // Setup start position
8916 startRng = rngFromPoint(e.x, e.y);
8917 if (startRng) {
8918 // Listen for selection change events
8919 dom.bind(doc, 'mouseup', endSelection);
8920 dom.bind(doc, 'mousemove', selectionChange);
8921
8922 dom.win.focus();
8923 startRng.select();
8924 }
8925 }
8926 });
8927 }
8928 });
8929 })(tinymce);
8930
8931 (function(tinymce) {
8932 tinymce.dom.Serializer = function(settings, dom, schema) {
8933 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
8934
8935 // Support the old apply_source_formatting option
8936 if (!settings.apply_source_formatting)
8937 settings.indent = false;
8938
8939 // Default DOM and Schema if they are undefined
8940 dom = dom || tinymce.DOM;
8941 schema = schema || new tinymce.html.Schema(settings);
8942 settings.entity_encoding = settings.entity_encoding || 'named';
8943 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
8944
8945 onPreProcess = new tinymce.util.Dispatcher(self);
8946
8947 onPostProcess = new tinymce.util.Dispatcher(self);
8948
8949 htmlParser = new tinymce.html.DomParser(settings, schema);
8950
8951 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
8952 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
8953 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
8954
8955 while (i--) {
8956 node = nodes[i];
8957
8958 value = node.attributes.map[internalName];
8959 if (value !== undef) {
8960 // Set external name to internal value and remove internal
8961 node.attr(name, value.length > 0 ? value : null);
8962 node.attr(internalName, null);
8963 } else {
8964 // No internal attribute found then convert the value we have in the DOM
8965 value = node.attributes.map[name];
8966
8967 if (name === "style")
8968 value = dom.serializeStyle(dom.parseStyle(value), node.name);
8969 else if (urlConverter)
8970 value = urlConverter.call(urlConverterScope, value, name, node.name);
8971
8972 node.attr(name, value.length > 0 ? value : null);
8973 }
8974 }
8975 });
8976
8977 // Remove internal classes mceItem<..>
8978 htmlParser.addAttributeFilter('class', function(nodes, name) {
8979 var i = nodes.length, node, value;
8980
8981 while (i--) {
8982 node = nodes[i];
8983 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
8984 node.attr('class', value.length > 0 ? value : null);
8985 }
8986 });
8987
8988 // Remove bookmark elements
8989 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
8990 var i = nodes.length, node;
8991
8992 while (i--) {
8993 node = nodes[i];
8994
8995 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
8996 node.remove();
8997 }
8998 });
8999
9000 // Remove expando attributes
9001 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
9002 var i = nodes.length;
9003
9004 while (i--) {
9005 nodes[i].attr(name, null);
9006 }
9007 });
9008
9009 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
9010 htmlParser.addNodeFilter('script,style', function(nodes, name) {
9011 var i = nodes.length, node, value;
9012
9013 function trim(value) {
9014 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
9015 .replace(/^[\r\n]*|[\r\n]*$/g, '')
9016 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
9017 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
9018 };
9019
9020 while (i--) {
9021 node = nodes[i];
9022 value = node.firstChild ? node.firstChild.value : '';
9023
9024 if (name === "script") {
9025 // Remove mce- prefix from script elements
9026 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
9027
9028 if (value.length > 0)
9029 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
9030 } else {
9031 if (value.length > 0)
9032 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
9033 }
9034 }
9035 });
9036
9037 // Convert comments to cdata and handle protected comments
9038 htmlParser.addNodeFilter('#comment', function(nodes, name) {
9039 var i = nodes.length, node;
9040
9041 while (i--) {
9042 node = nodes[i];
9043
9044 if (node.value.indexOf('[CDATA[') === 0) {
9045 node.name = '#cdata';
9046 node.type = 4;
9047 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
9048 } else if (node.value.indexOf('mce:protected ') === 0) {
9049 node.name = "#text";
9050 node.type = 3;
9051 node.raw = true;
9052 node.value = unescape(node.value).substr(14);
9053 }
9054 }
9055 });
9056
9057 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
9058 var i = nodes.length, node;
9059
9060 while (i--) {
9061 node = nodes[i];
9062 if (node.type === 7)
9063 node.remove();
9064 else if (node.type === 1) {
9065 if (name === "input" && !("type" in node.attributes.map))
9066 node.attr('type', 'text');
9067 }
9068 }
9069 });
9070
9071 // Fix list elements, TODO: Replace this later
9072 if (settings.fix_list_elements) {
9073 htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
9074 var i = nodes.length, node, parentNode;
9075
9076 while (i--) {
9077 node = nodes[i];
9078 parentNode = node.parent;
9079
9080 if (parentNode.name === 'ul' || parentNode.name === 'ol') {
9081 if (node.prev && node.prev.name === 'li') {
9082 node.prev.append(node);
9083 }
9084 }
9085 }
9086 });
9087 }
9088
9089 // Remove internal data attributes
9090 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
9091 var i = nodes.length;
9092
9093 while (i--) {
9094 nodes[i].attr(name, null);
9095 }
9096 });
9097
9098 // Return public methods
9099 return {
9100 schema : schema,
9101
9102 addNodeFilter : htmlParser.addNodeFilter,
9103
9104 addAttributeFilter : htmlParser.addAttributeFilter,
9105
9106 onPreProcess : onPreProcess,
9107
9108 onPostProcess : onPostProcess,
9109
9110 serialize : function(node, args) {
9111 var impl, doc, oldDoc, htmlSerializer, content;
9112
9113 // Explorer won't clone contents of script and style and the
9114 // selected index of select elements are cleared on a clone operation.
9115 if (isIE && dom.select('script,style,select,map').length > 0) {
9116 content = node.innerHTML;
9117 node = node.cloneNode(false);
9118 dom.setHTML(node, content);
9119 } else
9120 node = node.cloneNode(true);
9121
9122 // Nodes needs to be attached to something in WebKit/Opera
9123 // Older builds of Opera crashes if you attach the node to an document created dynamically
9124 // and since we can't feature detect a crash we need to sniff the acutal build number
9125 // This fix will make DOM ranges and make Sizzle happy!
9126 impl = node.ownerDocument.implementation;
9127 if (impl.createHTMLDocument) {
9128 // Create an empty HTML document
9129 doc = impl.createHTMLDocument("");
9130
9131 // Add the element or it's children if it's a body element to the new document
9132 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
9133 doc.body.appendChild(doc.importNode(node, true));
9134 });
9135
9136 // Grab first child or body element for serialization
9137 if (node.nodeName != 'BODY')
9138 node = doc.body.firstChild;
9139 else
9140 node = doc.body;
9141
9142 // set the new document in DOMUtils so createElement etc works
9143 oldDoc = dom.doc;
9144 dom.doc = doc;
9145 }
9146
9147 args = args || {};
9148 args.format = args.format || 'html';
9149
9150 // Pre process
9151 if (!args.no_events) {
9152 args.node = node;
9153 onPreProcess.dispatch(self, args);
9154 }
9155
9156 // Setup serializer
9157 htmlSerializer = new tinymce.html.Serializer(settings, schema);
9158
9159 // Parse and serialize HTML
9160 args.content = htmlSerializer.serialize(
9161 htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
9162 );
9163
9164 // Replace all BOM characters for now until we can find a better solution
9165 if (!args.cleanup)
9166 args.content = args.content.replace(/\uFEFF|\u200B/g, '');
9167
9168 // Post process
9169 if (!args.no_events)
9170 onPostProcess.dispatch(self, args);
9171
9172 // Restore the old document if it was changed
9173 if (oldDoc)
9174 dom.doc = oldDoc;
9175
9176 args.node = null;
9177
9178 return args.content;
9179 },
9180
9181 addRules : function(rules) {
9182 schema.addValidElements(rules);
9183 },
9184
9185 setRules : function(rules) {
9186 schema.setValidElements(rules);
9187 }
9188 };
9189 };
9190 })(tinymce);
9191 (function(tinymce) {
9192 tinymce.dom.ScriptLoader = function(settings) {
9193 var QUEUED = 0,
9194 LOADING = 1,
9195 LOADED = 2,
9196 states = {},
9197 queue = [],
9198 scriptLoadedCallbacks = {},
9199 queueLoadedCallbacks = [],
9200 loading = 0,
9201 undefined;
9202
9203 function loadScript(url, callback) {
9204 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
9205
9206 // Execute callback when script is loaded
9207 function done() {
9208 dom.remove(id);
9209
9210 if (elm)
9211 elm.onreadystatechange = elm.onload = elm = null;
9212
9213 callback();
9214 };
9215
9216 function error() {
9217 // Report the error so it's easier for people to spot loading errors
9218 if (typeof(console) !== "undefined" && console.log)
9219 console.log("Failed to load: " + url);
9220
9221 // We can't mark it as done if there is a load error since
9222 // A) We don't want to produce 404 errors on the server and
9223 // B) the onerror event won't fire on all browsers.
9224 // done();
9225 };
9226
9227 id = dom.uniqueId();
9228
9229 if (tinymce.isIE6) {
9230 uri = new tinymce.util.URI(url);
9231 loc = location;
9232
9233 // If script is from same domain and we
9234 // use IE 6 then use XHR since it's more reliable
9235 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
9236 tinymce.util.XHR.send({
9237 url : tinymce._addVer(uri.getURI()),
9238 success : function(content) {
9239 // Create new temp script element
9240 var script = dom.create('script', {
9241 type : 'text/javascript'
9242 });
9243
9244 // Evaluate script in global scope
9245 script.text = content;
9246 document.getElementsByTagName('head')[0].appendChild(script);
9247 dom.remove(script);
9248
9249 done();
9250 },
9251
9252 error : error
9253 });
9254
9255 return;
9256 }
9257 }
9258
9259 // Create new script element
9260 elm = dom.create('script', {
9261 id : id,
9262 type : 'text/javascript',
9263 src : tinymce._addVer(url)
9264 });
9265
9266 // Add onload listener for non IE browsers since IE9
9267 // fires onload event before the script is parsed and executed
9268 if (!tinymce.isIE)
9269 elm.onload = done;
9270
9271 // Add onerror event will get fired on some browsers but not all of them
9272 elm.onerror = error;
9273
9274 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
9275 if (!tinymce.isOpera) {
9276 elm.onreadystatechange = function() {
9277 var state = elm.readyState;
9278
9279 // Loaded state is passed on IE 6 however there
9280 // are known issues with this method but we can't use
9281 // XHR in a cross domain loading
9282 if (state == 'complete' || state == 'loaded')
9283 done();
9284 };
9285 }
9286
9287 // Most browsers support this feature so we report errors
9288 // for those at least to help users track their missing plugins etc
9289 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
9290 /*elm.onerror = function() {
9291 alert('Failed to load: ' + url);
9292 };*/
9293
9294 // Add script to document
9295 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
9296 };
9297
9298 this.isDone = function(url) {
9299 return states[url] == LOADED;
9300 };
9301
9302 this.markDone = function(url) {
9303 states[url] = LOADED;
9304 };
9305
9306 this.add = this.load = function(url, callback, scope) {
9307 var item, state = states[url];
9308
9309 // Add url to load queue
9310 if (state == undefined) {
9311 queue.push(url);
9312 states[url] = QUEUED;
9313 }
9314
9315 if (callback) {
9316 // Store away callback for later execution
9317 if (!scriptLoadedCallbacks[url])
9318 scriptLoadedCallbacks[url] = [];
9319
9320 scriptLoadedCallbacks[url].push({
9321 func : callback,
9322 scope : scope || this
9323 });
9324 }
9325 };
9326
9327 this.loadQueue = function(callback, scope) {
9328 this.loadScripts(queue, callback, scope);
9329 };
9330
9331 this.loadScripts = function(scripts, callback, scope) {
9332 var loadScripts;
9333
9334 function execScriptLoadedCallbacks(url) {
9335 // Execute URL callback functions
9336 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
9337 callback.func.call(callback.scope);
9338 });
9339
9340 scriptLoadedCallbacks[url] = undefined;
9341 };
9342
9343 queueLoadedCallbacks.push({
9344 func : callback,
9345 scope : scope || this
9346 });
9347
9348 loadScripts = function() {
9349 var loadingScripts = tinymce.grep(scripts);
9350
9351 // Current scripts has been handled
9352 scripts.length = 0;
9353
9354 // Load scripts that needs to be loaded
9355 tinymce.each(loadingScripts, function(url) {
9356 // Script is already loaded then execute script callbacks directly
9357 if (states[url] == LOADED) {
9358 execScriptLoadedCallbacks(url);
9359 return;
9360 }
9361
9362 // Is script not loading then start loading it
9363 if (states[url] != LOADING) {
9364 states[url] = LOADING;
9365 loading++;
9366
9367 loadScript(url, function() {
9368 states[url] = LOADED;
9369 loading--;
9370
9371 execScriptLoadedCallbacks(url);
9372
9373 // Load more scripts if they where added by the recently loaded script
9374 loadScripts();
9375 });
9376 }
9377 });
9378
9379 // No scripts are currently loading then execute all pending queue loaded callbacks
9380 if (!loading) {
9381 tinymce.each(queueLoadedCallbacks, function(callback) {
9382 callback.func.call(callback.scope);
9383 });
9384
9385 queueLoadedCallbacks.length = 0;
9386 }
9387 };
9388
9389 loadScripts();
9390 };
9391 };
9392
9393 // Global script loader
9394 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
9395 })(tinymce);
9396
9397 tinymce.dom.TreeWalker = function(start_node, root_node) {
9398 var node = start_node;
9399
9400 function findSibling(node, start_name, sibling_name, shallow) {
9401 var sibling, parent;
9402
9403 if (node) {
9404 // Walk into nodes if it has a start
9405 if (!shallow && node[start_name])
9406 return node[start_name];
9407
9408 // Return the sibling if it has one
9409 if (node != root_node) {
9410 sibling = node[sibling_name];
9411 if (sibling)
9412 return sibling;
9413
9414 // Walk up the parents to look for siblings
9415 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
9416 sibling = parent[sibling_name];
9417 if (sibling)
9418 return sibling;
9419 }
9420 }
9421 }
9422 };
9423
9424 this.current = function() {
9425 return node;
9426 };
9427
9428 this.next = function(shallow) {
9429 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
9430 };
9431
9432 this.prev = function(shallow) {
9433 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
9434 };
9435 };
9436
9437 (function(tinymce) {
9438 tinymce.dom.RangeUtils = function(dom) {
9439 var INVISIBLE_CHAR = '\uFEFF';
9440
9441 this.walk = function(rng, callback) {
9442 var startContainer = rng.startContainer,
9443 startOffset = rng.startOffset,
9444 endContainer = rng.endContainer,
9445 endOffset = rng.endOffset,
9446 ancestor, startPoint,
9447 endPoint, node, parent, siblings, nodes;
9448
9449 // Handle table cell selection the table plugin enables
9450 // you to fake select table cells and perform formatting actions on them
9451 nodes = dom.select('td.mceSelected,th.mceSelected');
9452 if (nodes.length > 0) {
9453 tinymce.each(nodes, function(node) {
9454 callback([node]);
9455 });
9456
9457 return;
9458 }
9459
9460 function exclude(nodes) {
9461 var node;
9462
9463 // First node is excluded
9464 node = nodes[0];
9465 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
9466 nodes.splice(0, 1);
9467 }
9468
9469 // Last node is excluded
9470 node = nodes[nodes.length - 1];
9471 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
9472 nodes.splice(nodes.length - 1, 1);
9473 }
9474
9475 return nodes;
9476 };
9477
9478 function collectSiblings(node, name, end_node) {
9479 var siblings = [];
9480
9481 for (; node && node != end_node; node = node[name])
9482 siblings.push(node);
9483
9484 return siblings;
9485 };
9486
9487 function findEndPoint(node, root) {
9488 do {
9489 if (node.parentNode == root)
9490 return node;
9491
9492 node = node.parentNode;
9493 } while(node);
9494 };
9495
9496 function walkBoundary(start_node, end_node, next) {
9497 var siblingName = next ? 'nextSibling' : 'previousSibling';
9498
9499 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
9500 parent = node.parentNode;
9501 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
9502
9503 if (siblings.length) {
9504 if (!next)
9505 siblings.reverse();
9506
9507 callback(exclude(siblings));
9508 }
9509 }
9510 };
9511
9512 // If index based start position then resolve it
9513 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
9514 startContainer = startContainer.childNodes[startOffset];
9515
9516 // If index based end position then resolve it
9517 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
9518 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
9519
9520 // Same container
9521 if (startContainer == endContainer)
9522 return callback(exclude([startContainer]));
9523
9524 // Find common ancestor and end points
9525 ancestor = dom.findCommonAncestor(startContainer, endContainer);
9526
9527 // Process left side
9528 for (node = startContainer; node; node = node.parentNode) {
9529 if (node === endContainer)
9530 return walkBoundary(startContainer, ancestor, true);
9531
9532 if (node === ancestor)
9533 break;
9534 }
9535
9536 // Process right side
9537 for (node = endContainer; node; node = node.parentNode) {
9538 if (node === startContainer)
9539 return walkBoundary(endContainer, ancestor);
9540
9541 if (node === ancestor)
9542 break;
9543 }
9544
9545 // Find start/end point
9546 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
9547 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
9548
9549 // Walk left leaf
9550 walkBoundary(startContainer, startPoint, true);
9551
9552 // Walk the middle from start to end point
9553 siblings = collectSiblings(
9554 startPoint == startContainer ? startPoint : startPoint.nextSibling,
9555 'nextSibling',
9556 endPoint == endContainer ? endPoint.nextSibling : endPoint
9557 );
9558
9559 if (siblings.length)
9560 callback(exclude(siblings));
9561
9562 // Walk right leaf
9563 walkBoundary(endContainer, endPoint);
9564 };
9565
9566 this.split = function(rng) {
9567 var startContainer = rng.startContainer,
9568 startOffset = rng.startOffset,
9569 endContainer = rng.endContainer,
9570 endOffset = rng.endOffset;
9571
9572 function splitText(node, offset) {
9573 return node.splitText(offset);
9574 };
9575
9576 // Handle single text node
9577 if (startContainer == endContainer && startContainer.nodeType == 3) {
9578 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9579 endContainer = splitText(startContainer, startOffset);
9580 startContainer = endContainer.previousSibling;
9581
9582 if (endOffset > startOffset) {
9583 endOffset = endOffset - startOffset;
9584 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
9585 endOffset = endContainer.nodeValue.length;
9586 startOffset = 0;
9587 } else {
9588 endOffset = 0;
9589 }
9590 }
9591 } else {
9592 // Split startContainer text node if needed
9593 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9594 startContainer = splitText(startContainer, startOffset);
9595 startOffset = 0;
9596 }
9597
9598 // Split endContainer text node if needed
9599 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
9600 endContainer = splitText(endContainer, endOffset).previousSibling;
9601 endOffset = endContainer.nodeValue.length;
9602 }
9603 }
9604
9605 return {
9606 startContainer : startContainer,
9607 startOffset : startOffset,
9608 endContainer : endContainer,
9609 endOffset : endOffset
9610 };
9611 };
9612
9613 };
9614
9615 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
9616 if (rng1 && rng2) {
9617 // Compare native IE ranges
9618 if (rng1.item || rng1.duplicate) {
9619 // Both are control ranges and the selected element matches
9620 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
9621 return true;
9622
9623 // Both are text ranges and the range matches
9624 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
9625 return true;
9626 } else {
9627 // Compare w3c ranges
9628 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
9629 }
9630 }
9631
9632 return false;
9633 };
9634 })(tinymce);
9635
9636 (function(tinymce) {
9637 var Event = tinymce.dom.Event, each = tinymce.each;
9638
9639 tinymce.create('tinymce.ui.KeyboardNavigation', {
9640 KeyboardNavigation: function(settings, dom) {
9641 var t = this, root = settings.root, items = settings.items,
9642 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
9643 excludeFromTabOrder = settings.excludeFromTabOrder,
9644 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
9645
9646 dom = dom || tinymce.DOM;
9647
9648 itemFocussed = function(evt) {
9649 focussedId = evt.target.id;
9650 };
9651
9652 itemBlurred = function(evt) {
9653 dom.setAttrib(evt.target.id, 'tabindex', '-1');
9654 };
9655
9656 rootFocussed = function(evt) {
9657 var item = dom.get(focussedId);
9658 dom.setAttrib(item, 'tabindex', '0');
9659 item.focus();
9660 };
9661
9662 t.focus = function() {
9663 dom.get(focussedId).focus();
9664 };
9665
9666 t.destroy = function() {
9667 each(items, function(item) {
9668 dom.unbind(dom.get(item.id), 'focus', itemFocussed);
9669 dom.unbind(dom.get(item.id), 'blur', itemBlurred);
9670 });
9671
9672 dom.unbind(dom.get(root), 'focus', rootFocussed);
9673 dom.unbind(dom.get(root), 'keydown', rootKeydown);
9674
9675 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
9676 t.destroy = function() {};
9677 };
9678
9679 t.moveFocus = function(dir, evt) {
9680 var idx = -1, controls = t.controls, newFocus;
9681
9682 if (!focussedId)
9683 return;
9684
9685 each(items, function(item, index) {
9686 if (item.id === focussedId) {
9687 idx = index;
9688 return false;
9689 }
9690 });
9691
9692 idx += dir;
9693 if (idx < 0) {
9694 idx = items.length - 1;
9695 } else if (idx >= items.length) {
9696 idx = 0;
9697 }
9698
9699 newFocus = items[idx];
9700 dom.setAttrib(focussedId, 'tabindex', '-1');
9701 dom.setAttrib(newFocus.id, 'tabindex', '0');
9702 dom.get(newFocus.id).focus();
9703
9704 if (settings.actOnFocus) {
9705 settings.onAction(newFocus.id);
9706 }
9707
9708 if (evt)
9709 Event.cancel(evt);
9710 };
9711
9712 rootKeydown = function(evt) {
9713 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
9714
9715 switch (evt.keyCode) {
9716 case DOM_VK_LEFT:
9717 if (enableLeftRight) t.moveFocus(-1);
9718 break;
9719
9720 case DOM_VK_RIGHT:
9721 if (enableLeftRight) t.moveFocus(1);
9722 break;
9723
9724 case DOM_VK_UP:
9725 if (enableUpDown) t.moveFocus(-1);
9726 break;
9727
9728 case DOM_VK_DOWN:
9729 if (enableUpDown) t.moveFocus(1);
9730 break;
9731
9732 case DOM_VK_ESCAPE:
9733 if (settings.onCancel) {
9734 settings.onCancel();
9735 Event.cancel(evt);
9736 }
9737 break;
9738
9739 case DOM_VK_ENTER:
9740 case DOM_VK_RETURN:
9741 case DOM_VK_SPACE:
9742 if (settings.onAction) {
9743 settings.onAction(focussedId);
9744 Event.cancel(evt);
9745 }
9746 break;
9747 }
9748 };
9749
9750 // Set up state and listeners for each item.
9751 each(items, function(item, idx) {
9752 var tabindex;
9753
9754 if (!item.id) {
9755 item.id = dom.uniqueId('_mce_item_');
9756 }
9757
9758 if (excludeFromTabOrder) {
9759 dom.bind(item.id, 'blur', itemBlurred);
9760 tabindex = '-1';
9761 } else {
9762 tabindex = (idx === 0 ? '0' : '-1');
9763 }
9764
9765 dom.setAttrib(item.id, 'tabindex', tabindex);
9766 dom.bind(dom.get(item.id), 'focus', itemFocussed);
9767 });
9768
9769 // Setup initial state for root element.
9770 if (items[0]){
9771 focussedId = items[0].id;
9772 }
9773
9774 dom.setAttrib(root, 'tabindex', '-1');
9775
9776 // Setup listeners for root element.
9777 dom.bind(dom.get(root), 'focus', rootFocussed);
9778 dom.bind(dom.get(root), 'keydown', rootKeydown);
9779 }
9780 });
9781 })(tinymce);
9782
9783 (function(tinymce) {
9784 // Shorten class names
9785 var DOM = tinymce.DOM, is = tinymce.is;
9786
9787 tinymce.create('tinymce.ui.Control', {
9788 Control : function(id, s, editor) {
9789 this.id = id;
9790 this.settings = s = s || {};
9791 this.rendered = false;
9792 this.onRender = new tinymce.util.Dispatcher(this);
9793 this.classPrefix = '';
9794 this.scope = s.scope || this;
9795 this.disabled = 0;
9796 this.active = 0;
9797 this.editor = editor;
9798 },
9799
9800 setAriaProperty : function(property, value) {
9801 var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
9802 if (element) {
9803 DOM.setAttrib(element, 'aria-' + property, !!value);
9804 }
9805 },
9806
9807 focus : function() {
9808 DOM.get(this.id).focus();
9809 },
9810
9811 setDisabled : function(s) {
9812 if (s != this.disabled) {
9813 this.setAriaProperty('disabled', s);
9814
9815 this.setState('Disabled', s);
9816 this.setState('Enabled', !s);
9817 this.disabled = s;
9818 }
9819 },
9820
9821 isDisabled : function() {
9822 return this.disabled;
9823 },
9824
9825 setActive : function(s) {
9826 if (s != this.active) {
9827 this.setState('Active', s);
9828 this.active = s;
9829 this.setAriaProperty('pressed', s);
9830 }
9831 },
9832
9833 isActive : function() {
9834 return this.active;
9835 },
9836
9837 setState : function(c, s) {
9838 var n = DOM.get(this.id);
9839
9840 c = this.classPrefix + c;
9841
9842 if (s)
9843 DOM.addClass(n, c);
9844 else
9845 DOM.removeClass(n, c);
9846 },
9847
9848 isRendered : function() {
9849 return this.rendered;
9850 },
9851
9852 renderHTML : function() {
9853 },
9854
9855 renderTo : function(n) {
9856 DOM.setHTML(n, this.renderHTML());
9857 },
9858
9859 postRender : function() {
9860 var t = this, b;
9861
9862 // Set pending states
9863 if (is(t.disabled)) {
9864 b = t.disabled;
9865 t.disabled = -1;
9866 t.setDisabled(b);
9867 }
9868
9869 if (is(t.active)) {
9870 b = t.active;
9871 t.active = -1;
9872 t.setActive(b);
9873 }
9874 },
9875
9876 remove : function() {
9877 DOM.remove(this.id);
9878 this.destroy();
9879 },
9880
9881 destroy : function() {
9882 tinymce.dom.Event.clear(this.id);
9883 }
9884 });
9885 })(tinymce);
9886 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
9887 Container : function(id, s, editor) {
9888 this.parent(id, s, editor);
9889
9890 this.controls = [];
9891
9892 this.lookup = {};
9893 },
9894
9895 add : function(c) {
9896 this.lookup[c.id] = c;
9897 this.controls.push(c);
9898
9899 return c;
9900 },
9901
9902 get : function(n) {
9903 return this.lookup[n];
9904 }
9905 });
9906
9907
9908 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9909 Separator : function(id, s) {
9910 this.parent(id, s);
9911 this.classPrefix = 'mceSeparator';
9912 this.setDisabled(true);
9913 },
9914
9915 renderHTML : function() {
9916 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
9917 }
9918 });
9919
9920 (function(tinymce) {
9921 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9922
9923 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
9924 MenuItem : function(id, s) {
9925 this.parent(id, s);
9926 this.classPrefix = 'mceMenuItem';
9927 },
9928
9929 setSelected : function(s) {
9930 this.setState('Selected', s);
9931 this.setAriaProperty('checked', !!s);
9932 this.selected = s;
9933 },
9934
9935 isSelected : function() {
9936 return this.selected;
9937 },
9938
9939 postRender : function() {
9940 var t = this;
9941
9942 t.parent();
9943
9944 // Set pending state
9945 if (is(t.selected))
9946 t.setSelected(t.selected);
9947 }
9948 });
9949 })(tinymce);
9950
9951 (function(tinymce) {
9952 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9953
9954 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
9955 Menu : function(id, s) {
9956 var t = this;
9957
9958 t.parent(id, s);
9959 t.items = {};
9960 t.collapsed = false;
9961 t.menuCount = 0;
9962 t.onAddItem = new tinymce.util.Dispatcher(this);
9963 },
9964
9965 expand : function(d) {
9966 var t = this;
9967
9968 if (d) {
9969 walk(t, function(o) {
9970 if (o.expand)
9971 o.expand();
9972 }, 'items', t);
9973 }
9974
9975 t.collapsed = false;
9976 },
9977
9978 collapse : function(d) {
9979 var t = this;
9980
9981 if (d) {
9982 walk(t, function(o) {
9983 if (o.collapse)
9984 o.collapse();
9985 }, 'items', t);
9986 }
9987
9988 t.collapsed = true;
9989 },
9990
9991 isCollapsed : function() {
9992 return this.collapsed;
9993 },
9994
9995 add : function(o) {
9996 if (!o.settings)
9997 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
9998
9999 this.onAddItem.dispatch(this, o);
10000
10001 return this.items[o.id] = o;
10002 },
10003
10004 addSeparator : function() {
10005 return this.add({separator : true});
10006 },
10007
10008 addMenu : function(o) {
10009 if (!o.collapse)
10010 o = this.createMenu(o);
10011
10012 this.menuCount++;
10013
10014 return this.add(o);
10015 },
10016
10017 hasMenus : function() {
10018 return this.menuCount !== 0;
10019 },
10020
10021 remove : function(o) {
10022 delete this.items[o.id];
10023 },
10024
10025 removeAll : function() {
10026 var t = this;
10027
10028 walk(t, function(o) {
10029 if (o.removeAll)
10030 o.removeAll();
10031 else
10032 o.remove();
10033
10034 o.destroy();
10035 }, 'items', t);
10036
10037 t.items = {};
10038 },
10039
10040 createMenu : function(o) {
10041 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
10042
10043 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
10044
10045 return m;
10046 }
10047 });
10048 })(tinymce);
10049 (function(tinymce) {
10050 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
10051
10052 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
10053 DropMenu : function(id, s) {
10054 s = s || {};
10055 s.container = s.container || DOM.doc.body;
10056 s.offset_x = s.offset_x || 0;
10057 s.offset_y = s.offset_y || 0;
10058 s.vp_offset_x = s.vp_offset_x || 0;
10059 s.vp_offset_y = s.vp_offset_y || 0;
10060
10061 if (is(s.icons) && !s.icons)
10062 s['class'] += ' mceNoIcons';
10063
10064 this.parent(id, s);
10065 this.onShowMenu = new tinymce.util.Dispatcher(this);
10066 this.onHideMenu = new tinymce.util.Dispatcher(this);
10067 this.classPrefix = 'mceMenu';
10068 },
10069
10070 createMenu : function(s) {
10071 var t = this, cs = t.settings, m;
10072
10073 s.container = s.container || cs.container;
10074 s.parent = t;
10075 s.constrain = s.constrain || cs.constrain;
10076 s['class'] = s['class'] || cs['class'];
10077 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
10078 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
10079 s.keyboard_focus = cs.keyboard_focus;
10080 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
10081
10082 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
10083
10084 return m;
10085 },
10086
10087 focus : function() {
10088 var t = this;
10089 if (t.keyboardNav) {
10090 t.keyboardNav.focus();
10091 }
10092 },
10093
10094 update : function() {
10095 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
10096
10097 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
10098 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
10099
10100 if (!DOM.boxModel)
10101 t.element.setStyles({width : tw + 2, height : th + 2});
10102 else
10103 t.element.setStyles({width : tw, height : th});
10104
10105 if (s.max_width)
10106 DOM.setStyle(co, 'width', tw);
10107
10108 if (s.max_height) {
10109 DOM.setStyle(co, 'height', th);
10110
10111 if (tb.clientHeight < s.max_height)
10112 DOM.setStyle(co, 'overflow', 'hidden');
10113 }
10114 },
10115
10116 showMenu : function(x, y, px) {
10117 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
10118
10119 t.collapse(1);
10120
10121 if (t.isMenuVisible)
10122 return;
10123
10124 if (!t.rendered) {
10125 co = DOM.add(t.settings.container, t.renderNode());
10126
10127 each(t.items, function(o) {
10128 o.postRender();
10129 });
10130
10131 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10132 } else
10133 co = DOM.get('menu_' + t.id);
10134
10135 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
10136 if (!tinymce.isOpera)
10137 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
10138
10139 DOM.show(co);
10140 t.update();
10141
10142 x += s.offset_x || 0;
10143 y += s.offset_y || 0;
10144 vp.w -= 4;
10145 vp.h -= 4;
10146
10147 // Move inside viewport if not submenu
10148 if (s.constrain) {
10149 w = co.clientWidth - ot;
10150 h = co.clientHeight - ot;
10151 mx = vp.x + vp.w;
10152 my = vp.y + vp.h;
10153
10154 if ((x + s.vp_offset_x + w) > mx)
10155 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
10156
10157 if ((y + s.vp_offset_y + h) > my)
10158 y = Math.max(0, (my - s.vp_offset_y) - h);
10159 }
10160
10161 DOM.setStyles(co, {left : x , top : y});
10162 t.element.update();
10163
10164 t.isMenuVisible = 1;
10165 t.mouseClickFunc = Event.add(co, 'click', function(e) {
10166 var m;
10167
10168 e = e.target;
10169
10170 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
10171 m = t.items[e.id];
10172
10173 if (m.isDisabled())
10174 return;
10175
10176 dm = t;
10177
10178 while (dm) {
10179 if (dm.hideMenu)
10180 dm.hideMenu();
10181
10182 dm = dm.settings.parent;
10183 }
10184
10185 if (m.settings.onclick)
10186 m.settings.onclick(e);
10187
10188 return false; // Cancel to fix onbeforeunload problem
10189 }
10190 });
10191
10192 if (t.hasMenus()) {
10193 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
10194 var m, r, mi;
10195
10196 e = e.target;
10197 if (e && (e = DOM.getParent(e, 'tr'))) {
10198 m = t.items[e.id];
10199
10200 if (t.lastMenu)
10201 t.lastMenu.collapse(1);
10202
10203 if (m.isDisabled())
10204 return;
10205
10206 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
10207 //p = DOM.getPos(s.container);
10208 r = DOM.getRect(e);
10209 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
10210 t.lastMenu = m;
10211 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
10212 }
10213 }
10214 });
10215 }
10216
10217 Event.add(co, 'keydown', t._keyHandler, t);
10218
10219 t.onShowMenu.dispatch(t);
10220
10221 if (s.keyboard_focus) {
10222 t._setupKeyboardNav();
10223 }
10224 },
10225
10226 hideMenu : function(c) {
10227 var t = this, co = DOM.get('menu_' + t.id), e;
10228
10229 if (!t.isMenuVisible)
10230 return;
10231
10232 if (t.keyboardNav) t.keyboardNav.destroy();
10233 Event.remove(co, 'mouseover', t.mouseOverFunc);
10234 Event.remove(co, 'click', t.mouseClickFunc);
10235 Event.remove(co, 'keydown', t._keyHandler);
10236 DOM.hide(co);
10237 t.isMenuVisible = 0;
10238
10239 if (!c)
10240 t.collapse(1);
10241
10242 if (t.element)
10243 t.element.hide();
10244
10245 if (e = DOM.get(t.id))
10246 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
10247
10248 t.onHideMenu.dispatch(t);
10249 },
10250
10251 add : function(o) {
10252 var t = this, co;
10253
10254 o = t.parent(o);
10255
10256 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
10257 t._add(DOM.select('tbody', co)[0], o);
10258
10259 return o;
10260 },
10261
10262 collapse : function(d) {
10263 this.parent(d);
10264 this.hideMenu(1);
10265 },
10266
10267 remove : function(o) {
10268 DOM.remove(o.id);
10269 this.destroy();
10270
10271 return this.parent(o);
10272 },
10273
10274 destroy : function() {
10275 var t = this, co = DOM.get('menu_' + t.id);
10276
10277 if (t.keyboardNav) t.keyboardNav.destroy();
10278 Event.remove(co, 'mouseover', t.mouseOverFunc);
10279 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
10280 Event.remove(co, 'click', t.mouseClickFunc);
10281 Event.remove(co, 'keydown', t._keyHandler);
10282
10283 if (t.element)
10284 t.element.remove();
10285
10286 DOM.remove(co);
10287 },
10288
10289 renderNode : function() {
10290 var t = this, s = t.settings, n, tb, co, w;
10291
10292 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
10293 if (t.settings.parent) {
10294 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
10295 }
10296 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
10297 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10298
10299 if (s.menu_line)
10300 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
10301
10302 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
10303 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
10304 tb = DOM.add(n, 'tbody');
10305
10306 each(t.items, function(o) {
10307 t._add(tb, o);
10308 });
10309
10310 t.rendered = true;
10311
10312 return w;
10313 },
10314
10315 // Internal functions
10316 _setupKeyboardNav : function(){
10317 var contextMenu, menuItems, t=this;
10318 contextMenu = DOM.get('menu_' + t.id);
10319 menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
10320 menuItems.splice(0,0,contextMenu);
10321 t.keyboardNav = new tinymce.ui.KeyboardNavigation({
10322 root: 'menu_' + t.id,
10323 items: menuItems,
10324 onCancel: function() {
10325 t.hideMenu();
10326 },
10327 enableUpDown: true
10328 });
10329 contextMenu.focus();
10330 },
10331
10332 _keyHandler : function(evt) {
10333 var t = this, e;
10334 switch (evt.keyCode) {
10335 case 37: // Left
10336 if (t.settings.parent) {
10337 t.hideMenu();
10338 t.settings.parent.focus();
10339 Event.cancel(evt);
10340 }
10341 break;
10342 case 39: // Right
10343 if (t.mouseOverFunc)
10344 t.mouseOverFunc(evt);
10345 break;
10346 }
10347 },
10348
10349 _add : function(tb, o) {
10350 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
10351
10352 if (s.separator) {
10353 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
10354 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
10355
10356 if (n = ro.previousSibling)
10357 DOM.addClass(n, 'mceLast');
10358
10359 return;
10360 }
10361
10362 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
10363 n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
10364 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
10365
10366 if (s.parent) {
10367 DOM.setAttrib(a, 'aria-haspopup', 'true');
10368 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
10369 }
10370
10371 DOM.addClass(it, s['class']);
10372 // n = DOM.add(n, 'span', {'class' : 'item'});
10373
10374 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
10375
10376 if (s.icon_src)
10377 DOM.add(ic, 'img', {src : s.icon_src});
10378
10379 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
10380
10381 if (o.settings.style) {
10382 if (typeof o.settings.style == "function")
10383 o.settings.style = o.settings.style();
10384
10385 DOM.setAttrib(n, 'style', o.settings.style);
10386 }
10387
10388 if (tb.childNodes.length == 1)
10389 DOM.addClass(ro, 'mceFirst');
10390
10391 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
10392 DOM.addClass(ro, 'mceFirst');
10393
10394 if (o.collapse)
10395 DOM.addClass(ro, cp + 'ItemSub');
10396
10397 if (n = ro.previousSibling)
10398 DOM.removeClass(n, 'mceLast');
10399
10400 DOM.addClass(ro, 'mceLast');
10401 }
10402 });
10403 })(tinymce);
10404 (function(tinymce) {
10405 var DOM = tinymce.DOM;
10406
10407 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
10408 Button : function(id, s, ed) {
10409 this.parent(id, s, ed);
10410 this.classPrefix = 'mceButton';
10411 },
10412
10413 renderHTML : function() {
10414 var cp = this.classPrefix, s = this.settings, h, l;
10415
10416 l = DOM.encode(s.label || '');
10417 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
10418 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) )
10419 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
10420 else
10421 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
10422
10423 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
10424 h += '</a>';
10425 return h;
10426 },
10427
10428 postRender : function() {
10429 var t = this, s = t.settings, imgBookmark;
10430
10431 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
10432 // need to keep the selection in case the selection is lost
10433 if (tinymce.isIE && t.editor) {
10434 tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
10435 var nodeName = t.editor.selection.getNode().nodeName;
10436 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
10437 });
10438 }
10439 tinymce.dom.Event.add(t.id, 'click', function(e) {
10440 if (!t.isDisabled()) {
10441 // restore the selection in case the selection is lost in IE
10442 if (tinymce.isIE && t.editor && imgBookmark !== null) {
10443 t.editor.selection.moveToBookmark(imgBookmark);
10444 }
10445 return s.onclick.call(s.scope, e);
10446 }
10447 });
10448 tinymce.dom.Event.add(t.id, 'keyup', function(e) {
10449 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
10450 return s.onclick.call(s.scope, e);
10451 });
10452 }
10453 });
10454 })(tinymce);
10455
10456 (function(tinymce) {
10457 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
10458
10459 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
10460 ListBox : function(id, s, ed) {
10461 var t = this;
10462
10463 t.parent(id, s, ed);
10464
10465 t.items = [];
10466
10467 t.onChange = new Dispatcher(t);
10468
10469 t.onPostRender = new Dispatcher(t);
10470
10471 t.onAdd = new Dispatcher(t);
10472
10473 t.onRenderMenu = new tinymce.util.Dispatcher(this);
10474
10475 t.classPrefix = 'mceListBox';
10476 t.marked = {};
10477 },
10478
10479 select : function(va) {
10480 var t = this, fv, f;
10481
10482 t.marked = {};
10483
10484 if (va == undefined)
10485 return t.selectByIndex(-1);
10486
10487 // Is string or number make function selector
10488 if (va && typeof(va)=="function")
10489 f = va;
10490 else {
10491 f = function(v) {
10492 return v == va;
10493 };
10494 }
10495
10496 // Do we need to do something?
10497 if (va != t.selectedValue) {
10498 // Find item
10499 each(t.items, function(o, i) {
10500 if (f(o.value)) {
10501 fv = 1;
10502 t.selectByIndex(i);
10503 return false;
10504 }
10505 });
10506
10507 if (!fv)
10508 t.selectByIndex(-1);
10509 }
10510 },
10511
10512 selectByIndex : function(idx) {
10513 var t = this, e, o, label;
10514
10515 t.marked = {};
10516
10517 if (idx != t.selectedIndex) {
10518 e = DOM.get(t.id + '_text');
10519 label = DOM.get(t.id + '_voiceDesc');
10520 o = t.items[idx];
10521
10522 if (o) {
10523 t.selectedValue = o.value;
10524 t.selectedIndex = idx;
10525 DOM.setHTML(e, DOM.encode(o.title));
10526 DOM.setHTML(label, t.settings.title + " - " + o.title);
10527 DOM.removeClass(e, 'mceTitle');
10528 DOM.setAttrib(t.id, 'aria-valuenow', o.title);
10529 } else {
10530 DOM.setHTML(e, DOM.encode(t.settings.title));
10531 DOM.setHTML(label, DOM.encode(t.settings.title));
10532 DOM.addClass(e, 'mceTitle');
10533 t.selectedValue = t.selectedIndex = null;
10534 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
10535 }
10536 e = 0;
10537 }
10538 },
10539
10540 mark : function(value) {
10541 this.marked[value] = true;
10542 },
10543
10544 add : function(n, v, o) {
10545 var t = this;
10546
10547 o = o || {};
10548 o = tinymce.extend(o, {
10549 title : n,
10550 value : v
10551 });
10552
10553 t.items.push(o);
10554 t.onAdd.dispatch(t, o);
10555 },
10556
10557 getLength : function() {
10558 return this.items.length;
10559 },
10560
10561 renderHTML : function() {
10562 var h = '', t = this, s = t.settings, cp = t.classPrefix;
10563
10564 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
10565 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
10566 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
10567 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
10568 h += '</tr></tbody></table></span>';
10569
10570 return h;
10571 },
10572
10573 showMenu : function() {
10574 var t = this, p2, e = DOM.get(this.id), m;
10575
10576 if (t.isDisabled() || t.items.length == 0)
10577 return;
10578
10579 if (t.menu && t.menu.isMenuVisible)
10580 return t.hideMenu();
10581
10582 if (!t.isMenuRendered) {
10583 t.renderMenu();
10584 t.isMenuRendered = true;
10585 }
10586
10587 p2 = DOM.getPos(e);
10588
10589 m = t.menu;
10590 m.settings.offset_x = p2.x;
10591 m.settings.offset_y = p2.y;
10592 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
10593
10594 // Select in menu
10595 each(t.items, function(o) {
10596 if (m.items[o.id]) {
10597 m.items[o.id].setSelected(0);
10598 }
10599 });
10600
10601 each(t.items, function(o) {
10602 if (m.items[o.id] && t.marked[o.value]) {
10603 m.items[o.id].setSelected(1);
10604 }
10605
10606 if (o.value === t.selectedValue) {
10607 m.items[o.id].setSelected(1);
10608 }
10609 });
10610
10611 m.showMenu(0, e.clientHeight);
10612
10613 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10614 DOM.addClass(t.id, t.classPrefix + 'Selected');
10615
10616 //DOM.get(t.id + '_text').focus();
10617 },
10618
10619 hideMenu : function(e) {
10620 var t = this;
10621
10622 if (t.menu && t.menu.isMenuVisible) {
10623 DOM.removeClass(t.id, t.classPrefix + 'Selected');
10624
10625 // Prevent double toogles by canceling the mouse click event to the button
10626 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
10627 return;
10628
10629 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10630 DOM.removeClass(t.id, t.classPrefix + 'Selected');
10631 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10632 t.menu.hideMenu();
10633 }
10634 }
10635 },
10636
10637 renderMenu : function() {
10638 var t = this, m;
10639
10640 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10641 menu_line : 1,
10642 'class' : t.classPrefix + 'Menu mceNoIcons',
10643 max_width : 150,
10644 max_height : 150
10645 });
10646
10647 m.onHideMenu.add(function() {
10648 t.hideMenu();
10649 t.focus();
10650 });
10651
10652 m.add({
10653 title : t.settings.title,
10654 'class' : 'mceMenuItemTitle',
10655 onclick : function() {
10656 if (t.settings.onselect('') !== false)
10657 t.select(''); // Must be runned after
10658 }
10659 });
10660
10661 each(t.items, function(o) {
10662 // No value then treat it as a title
10663 if (o.value === undefined) {
10664 m.add({
10665 title : o.title,
10666 role : "option",
10667 'class' : 'mceMenuItemTitle',
10668 onclick : function() {
10669 if (t.settings.onselect('') !== false)
10670 t.select(''); // Must be runned after
10671 }
10672 });
10673 } else {
10674 o.id = DOM.uniqueId();
10675 o.role= "option";
10676 o.onclick = function() {
10677 if (t.settings.onselect(o.value) !== false)
10678 t.select(o.value); // Must be runned after
10679 };
10680
10681 m.add(o);
10682 }
10683 });
10684
10685 t.onRenderMenu.dispatch(t, m);
10686 t.menu = m;
10687 },
10688
10689 postRender : function() {
10690 var t = this, cp = t.classPrefix;
10691
10692 Event.add(t.id, 'click', t.showMenu, t);
10693 Event.add(t.id, 'keydown', function(evt) {
10694 if (evt.keyCode == 32) { // Space
10695 t.showMenu(evt);
10696 Event.cancel(evt);
10697 }
10698 });
10699 Event.add(t.id, 'focus', function() {
10700 if (!t._focused) {
10701 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
10702 if (e.keyCode == 40) {
10703 t.showMenu();
10704 Event.cancel(e);
10705 }
10706 });
10707 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
10708 var v;
10709 if (e.keyCode == 13) {
10710 // Fake select on enter
10711 v = t.selectedValue;
10712 t.selectedValue = null; // Needs to be null to fake change
10713 Event.cancel(e);
10714 t.settings.onselect(v);
10715 }
10716 });
10717 }
10718
10719 t._focused = 1;
10720 });
10721 Event.add(t.id, 'blur', function() {
10722 Event.remove(t.id, 'keydown', t.keyDownHandler);
10723 Event.remove(t.id, 'keypress', t.keyPressHandler);
10724 t._focused = 0;
10725 });
10726
10727 // Old IE doesn't have hover on all elements
10728 if (tinymce.isIE6 || !DOM.boxModel) {
10729 Event.add(t.id, 'mouseover', function() {
10730 if (!DOM.hasClass(t.id, cp + 'Disabled'))
10731 DOM.addClass(t.id, cp + 'Hover');
10732 });
10733
10734 Event.add(t.id, 'mouseout', function() {
10735 if (!DOM.hasClass(t.id, cp + 'Disabled'))
10736 DOM.removeClass(t.id, cp + 'Hover');
10737 });
10738 }
10739
10740 t.onPostRender.dispatch(t, DOM.get(t.id));
10741 },
10742
10743 destroy : function() {
10744 this.parent();
10745
10746 Event.clear(this.id + '_text');
10747 Event.clear(this.id + '_open');
10748 }
10749 });
10750 })(tinymce);
10751
10752 (function(tinymce) {
10753 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
10754
10755 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
10756 NativeListBox : function(id, s) {
10757 this.parent(id, s);
10758 this.classPrefix = 'mceNativeListBox';
10759 },
10760
10761 setDisabled : function(s) {
10762 DOM.get(this.id).disabled = s;
10763 this.setAriaProperty('disabled', s);
10764 },
10765
10766 isDisabled : function() {
10767 return DOM.get(this.id).disabled;
10768 },
10769
10770 select : function(va) {
10771 var t = this, fv, f;
10772
10773 if (va == undefined)
10774 return t.selectByIndex(-1);
10775
10776 // Is string or number make function selector
10777 if (va && typeof(va)=="function")
10778 f = va;
10779 else {
10780 f = function(v) {
10781 return v == va;
10782 };
10783 }
10784
10785 // Do we need to do something?
10786 if (va != t.selectedValue) {
10787 // Find item
10788 each(t.items, function(o, i) {
10789 if (f(o.value)) {
10790 fv = 1;
10791 t.selectByIndex(i);
10792 return false;
10793 }
10794 });
10795
10796 if (!fv)
10797 t.selectByIndex(-1);
10798 }
10799 },
10800
10801 selectByIndex : function(idx) {
10802 DOM.get(this.id).selectedIndex = idx + 1;
10803 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
10804 },
10805
10806 add : function(n, v, a) {
10807 var o, t = this;
10808
10809 a = a || {};
10810 a.value = v;
10811
10812 if (t.isRendered())
10813 DOM.add(DOM.get(this.id), 'option', a, n);
10814
10815 o = {
10816 title : n,
10817 value : v,
10818 attribs : a
10819 };
10820
10821 t.items.push(o);
10822 t.onAdd.dispatch(t, o);
10823 },
10824
10825 getLength : function() {
10826 return this.items.length;
10827 },
10828
10829 renderHTML : function() {
10830 var h, t = this;
10831
10832 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
10833
10834 each(t.items, function(it) {
10835 h += DOM.createHTML('option', {value : it.value}, it.title);
10836 });
10837
10838 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
10839 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
10840 return h;
10841 },
10842
10843 postRender : function() {
10844 var t = this, ch, changeListenerAdded = true;
10845
10846 t.rendered = true;
10847
10848 function onChange(e) {
10849 var v = t.items[e.target.selectedIndex - 1];
10850
10851 if (v && (v = v.value)) {
10852 t.onChange.dispatch(t, v);
10853
10854 if (t.settings.onselect)
10855 t.settings.onselect(v);
10856 }
10857 };
10858
10859 Event.add(t.id, 'change', onChange);
10860
10861 // Accessibility keyhandler
10862 Event.add(t.id, 'keydown', function(e) {
10863 var bf;
10864
10865 Event.remove(t.id, 'change', ch);
10866 changeListenerAdded = false;
10867
10868 bf = Event.add(t.id, 'blur', function() {
10869 if (changeListenerAdded) return;
10870 changeListenerAdded = true;
10871 Event.add(t.id, 'change', onChange);
10872 Event.remove(t.id, 'blur', bf);
10873 });
10874
10875 //prevent default left and right keys on chrome - so that the keyboard navigation is used.
10876 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
10877 return Event.prevent(e);
10878 }
10879
10880 if (e.keyCode == 13 || e.keyCode == 32) {
10881 onChange(e);
10882 return Event.cancel(e);
10883 }
10884 });
10885
10886 t.onPostRender.dispatch(t, DOM.get(t.id));
10887 }
10888 });
10889 })(tinymce);
10890
10891 (function(tinymce) {
10892 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10893
10894 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
10895 MenuButton : function(id, s, ed) {
10896 this.parent(id, s, ed);
10897
10898 this.onRenderMenu = new tinymce.util.Dispatcher(this);
10899
10900 s.menu_container = s.menu_container || DOM.doc.body;
10901 },
10902
10903 showMenu : function() {
10904 var t = this, p1, p2, e = DOM.get(t.id), m;
10905
10906 if (t.isDisabled())
10907 return;
10908
10909 if (!t.isMenuRendered) {
10910 t.renderMenu();
10911 t.isMenuRendered = true;
10912 }
10913
10914 if (t.isMenuVisible)
10915 return t.hideMenu();
10916
10917 p1 = DOM.getPos(t.settings.menu_container);
10918 p2 = DOM.getPos(e);
10919
10920 m = t.menu;
10921 m.settings.offset_x = p2.x;
10922 m.settings.offset_y = p2.y;
10923 m.settings.vp_offset_x = p2.x;
10924 m.settings.vp_offset_y = p2.y;
10925 m.settings.keyboard_focus = t._focused;
10926 m.showMenu(0, e.clientHeight);
10927
10928 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10929 t.setState('Selected', 1);
10930
10931 t.isMenuVisible = 1;
10932 },
10933
10934 renderMenu : function() {
10935 var t = this, m;
10936
10937 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10938 menu_line : 1,
10939 'class' : this.classPrefix + 'Menu',
10940 icons : t.settings.icons
10941 });
10942
10943 m.onHideMenu.add(function() {
10944 t.hideMenu();
10945 t.focus();
10946 });
10947
10948 t.onRenderMenu.dispatch(t, m);
10949 t.menu = m;
10950 },
10951
10952 hideMenu : function(e) {
10953 var t = this;
10954
10955 // Prevent double toogles by canceling the mouse click event to the button
10956 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
10957 return;
10958
10959 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10960 t.setState('Selected', 0);
10961 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10962 if (t.menu)
10963 t.menu.hideMenu();
10964 }
10965
10966 t.isMenuVisible = 0;
10967 },
10968
10969 postRender : function() {
10970 var t = this, s = t.settings;
10971
10972 Event.add(t.id, 'click', function() {
10973 if (!t.isDisabled()) {
10974 if (s.onclick)
10975 s.onclick(t.value);
10976
10977 t.showMenu();
10978 }
10979 });
10980 }
10981 });
10982 })(tinymce);
10983
10984 (function(tinymce) {
10985 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10986
10987 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
10988 SplitButton : function(id, s, ed) {
10989 this.parent(id, s, ed);
10990 this.classPrefix = 'mceSplitButton';
10991 },
10992
10993 renderHTML : function() {
10994 var h, t = this, s = t.settings, h1;
10995
10996 h = '<tbody><tr>';
10997
10998 if (s.image)
10999 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
11000 else
11001 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
11002
11003 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
11004 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
11005
11006 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
11007 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
11008
11009 h += '</tr></tbody>';
11010 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
11011 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
11012 },
11013
11014 postRender : function() {
11015 var t = this, s = t.settings, activate;
11016
11017 if (s.onclick) {
11018 activate = function(evt) {
11019 if (!t.isDisabled()) {
11020 s.onclick(t.value);
11021 Event.cancel(evt);
11022 }
11023 };
11024 Event.add(t.id + '_action', 'click', activate);
11025 Event.add(t.id, ['click', 'keydown'], function(evt) {
11026 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
11027 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
11028 activate();
11029 Event.cancel(evt);
11030 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
11031 t.showMenu();
11032 Event.cancel(evt);
11033 }
11034 });
11035 }
11036
11037 Event.add(t.id + '_open', 'click', function (evt) {
11038 t.showMenu();
11039 Event.cancel(evt);
11040 });
11041 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
11042 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
11043
11044 // Old IE doesn't have hover on all elements
11045 if (tinymce.isIE6 || !DOM.boxModel) {
11046 Event.add(t.id, 'mouseover', function() {
11047 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11048 DOM.addClass(t.id, 'mceSplitButtonHover');
11049 });
11050
11051 Event.add(t.id, 'mouseout', function() {
11052 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11053 DOM.removeClass(t.id, 'mceSplitButtonHover');
11054 });
11055 }
11056 },
11057
11058 destroy : function() {
11059 this.parent();
11060
11061 Event.clear(this.id + '_action');
11062 Event.clear(this.id + '_open');
11063 Event.clear(this.id);
11064 }
11065 });
11066 })(tinymce);
11067
11068 (function(tinymce) {
11069 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
11070
11071 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
11072 ColorSplitButton : function(id, s, ed) {
11073 var t = this;
11074
11075 t.parent(id, s, ed);
11076
11077 t.settings = s = tinymce.extend({
11078 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',
11079 grid_width : 8,
11080 default_color : '#888888'
11081 }, t.settings);
11082
11083 t.onShowMenu = new tinymce.util.Dispatcher(t);
11084
11085 t.onHideMenu = new tinymce.util.Dispatcher(t);
11086
11087 t.value = s.default_color;
11088 },
11089
11090 showMenu : function() {
11091 var t = this, r, p, e, p2;
11092
11093 if (t.isDisabled())
11094 return;
11095
11096 if (!t.isMenuRendered) {
11097 t.renderMenu();
11098 t.isMenuRendered = true;
11099 }
11100
11101 if (t.isMenuVisible)
11102 return t.hideMenu();
11103
11104 e = DOM.get(t.id);
11105 DOM.show(t.id + '_menu');
11106 DOM.addClass(e, 'mceSplitButtonSelected');
11107 p2 = DOM.getPos(e);
11108 DOM.setStyles(t.id + '_menu', {
11109 left : p2.x,
11110 top : p2.y + e.clientHeight,
11111 zIndex : 200000
11112 });
11113 e = 0;
11114
11115 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11116 t.onShowMenu.dispatch(t);
11117
11118 if (t._focused) {
11119 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
11120 if (e.keyCode == 27)
11121 t.hideMenu();
11122 });
11123
11124 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
11125 }
11126
11127 t.isMenuVisible = 1;
11128 },
11129
11130 hideMenu : function(e) {
11131 var t = this;
11132
11133 if (t.isMenuVisible) {
11134 // Prevent double toogles by canceling the mouse click event to the button
11135 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
11136 return;
11137
11138 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
11139 DOM.removeClass(t.id, 'mceSplitButtonSelected');
11140 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11141 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
11142 DOM.hide(t.id + '_menu');
11143 }
11144
11145 t.isMenuVisible = 0;
11146 t.onHideMenu.dispatch();
11147 }
11148 },
11149
11150 renderMenu : function() {
11151 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
11152
11153 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
11154 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
11155 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
11156
11157 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
11158 tb = DOM.add(n, 'tbody');
11159
11160 // Generate color grid
11161 i = 0;
11162 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
11163 c = c.replace(/^#/, '');
11164
11165 if (!i--) {
11166 tr = DOM.add(tb, 'tr');
11167 i = s.grid_width - 1;
11168 }
11169
11170 n = DOM.add(tr, 'td');
11171 var settings = {
11172 href : 'javascript:;',
11173 style : {
11174 backgroundColor : '#' + c
11175 },
11176 'title': t.editor.getLang('colors.' + c, c),
11177 'data-mce-color' : '#' + c
11178 };
11179
11180 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
11181 if (!tinymce.isIE ) {
11182 settings['role']= 'option';
11183 }
11184
11185 n = DOM.add(n, 'a', settings);
11186
11187 if (t.editor.forcedHighContrastMode) {
11188 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
11189 if (n.getContext && (context = n.getContext("2d"))) {
11190 context.fillStyle = '#' + c;
11191 context.fillRect(0, 0, 16, 16);
11192 } else {
11193 // No point leaving a canvas element around if it's not supported for drawing on anyway.
11194 DOM.remove(n);
11195 }
11196 }
11197 });
11198
11199 if (s.more_colors_func) {
11200 n = DOM.add(tb, 'tr');
11201 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
11202 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
11203
11204 Event.add(n, 'click', function(e) {
11205 s.more_colors_func.call(s.more_colors_scope || this);
11206 return Event.cancel(e); // Cancel to fix onbeforeunload problem
11207 });
11208 }
11209
11210 DOM.addClass(m, 'mceColorSplitMenu');
11211
11212 new tinymce.ui.KeyboardNavigation({
11213 root: t.id + '_menu',
11214 items: DOM.select('a', t.id + '_menu'),
11215 onCancel: function() {
11216 t.hideMenu();
11217 t.focus();
11218 }
11219 });
11220
11221 // Prevent IE from scrolling and hindering click to occur #4019
11222 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
11223
11224 Event.add(t.id + '_menu', 'click', function(e) {
11225 var c;
11226
11227 e = DOM.getParent(e.target, 'a', tb);
11228
11229 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
11230 t.setColor(c);
11231
11232 return false; // Prevent IE auto save warning
11233 });
11234
11235 return w;
11236 },
11237
11238 setColor : function(c) {
11239 this.displayColor(c);
11240 this.hideMenu();
11241 this.settings.onselect(c);
11242 },
11243
11244 displayColor : function(c) {
11245 var t = this;
11246
11247 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
11248
11249 t.value = c;
11250 },
11251
11252 postRender : function() {
11253 var t = this, id = t.id;
11254
11255 t.parent();
11256 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
11257 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
11258 },
11259
11260 destroy : function() {
11261 this.parent();
11262
11263 Event.clear(this.id + '_menu');
11264 Event.clear(this.id + '_more');
11265 DOM.remove(this.id + '_menu');
11266 }
11267 });
11268 })(tinymce);
11269
11270 (function(tinymce) {
11271 // Shorten class names
11272 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
11273 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
11274 renderHTML : function() {
11275 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
11276
11277 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
11278 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
11279 h.push("<span role='application'>");
11280 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
11281 each(controls, function(toolbar) {
11282 h.push(toolbar.renderHTML());
11283 });
11284 h.push("</span>");
11285 h.push('</div>');
11286
11287 return h.join('');
11288 },
11289
11290 focus : function() {
11291 var t = this;
11292 dom.get(t.id).focus();
11293 },
11294
11295 postRender : function() {
11296 var t = this, items = [];
11297
11298 each(t.controls, function(toolbar) {
11299 each (toolbar.controls, function(control) {
11300 if (control.id) {
11301 items.push(control);
11302 }
11303 });
11304 });
11305
11306 t.keyNav = new tinymce.ui.KeyboardNavigation({
11307 root: t.id,
11308 items: items,
11309 onCancel: function() {
11310 //Move focus if webkit so that navigation back will read the item.
11311 if (tinymce.isWebKit) {
11312 dom.get(t.editor.id+"_ifr").focus();
11313 }
11314 t.editor.focus();
11315 },
11316 excludeFromTabOrder: !t.settings.tab_focus_toolbar
11317 });
11318 },
11319
11320 destroy : function() {
11321 var self = this;
11322
11323 self.parent();
11324 self.keyNav.destroy();
11325 Event.clear(self.id);
11326 }
11327 });
11328 })(tinymce);
11329
11330 (function(tinymce) {
11331 // Shorten class names
11332 var dom = tinymce.DOM, each = tinymce.each;
11333 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11334 renderHTML : function() {
11335 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
11336
11337 cl = t.controls;
11338 for (i=0; i<cl.length; i++) {
11339 // Get current control, prev control, next control and if the control is a list box or not
11340 co = cl[i];
11341 pr = cl[i - 1];
11342 nx = cl[i + 1];
11343
11344 // Add toolbar start
11345 if (i === 0) {
11346 c = 'mceToolbarStart';
11347
11348 if (co.Button)
11349 c += ' mceToolbarStartButton';
11350 else if (co.SplitButton)
11351 c += ' mceToolbarStartSplitButton';
11352 else if (co.ListBox)
11353 c += ' mceToolbarStartListBox';
11354
11355 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11356 }
11357
11358 // Add toolbar end before list box and after the previous button
11359 // This is to fix the o2k7 editor skins
11360 if (pr && co.ListBox) {
11361 if (pr.Button || pr.SplitButton)
11362 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
11363 }
11364
11365 // Render control HTML
11366
11367 // IE 8 quick fix, needed to propertly generate a hit area for anchors
11368 if (dom.stdMode)
11369 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
11370 else
11371 h += '<td>' + co.renderHTML() + '</td>';
11372
11373 // Add toolbar start after list box and before the next button
11374 // This is to fix the o2k7 editor skins
11375 if (nx && co.ListBox) {
11376 if (nx.Button || nx.SplitButton)
11377 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
11378 }
11379 }
11380
11381 c = 'mceToolbarEnd';
11382
11383 if (co.Button)
11384 c += ' mceToolbarEndButton';
11385 else if (co.SplitButton)
11386 c += ' mceToolbarEndSplitButton';
11387 else if (co.ListBox)
11388 c += ' mceToolbarEndListBox';
11389
11390 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11391
11392 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
11393 }
11394 });
11395 })(tinymce);
11396
11397 (function(tinymce) {
11398 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
11399
11400 tinymce.create('tinymce.AddOnManager', {
11401 AddOnManager : function() {
11402 var self = this;
11403
11404 self.items = [];
11405 self.urls = {};
11406 self.lookup = {};
11407 self.onAdd = new Dispatcher(self);
11408 },
11409
11410 get : function(n) {
11411 if (this.lookup[n]) {
11412 return this.lookup[n].instance;
11413 } else {
11414 return undefined;
11415 }
11416 },
11417
11418 dependencies : function(n) {
11419 var result;
11420 if (this.lookup[n]) {
11421 result = this.lookup[n].dependencies;
11422 }
11423 return result || [];
11424 },
11425
11426 requireLangPack : function(n) {
11427 var s = tinymce.settings;
11428
11429 if (s && s.language && s.language_load !== false)
11430 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
11431 },
11432
11433 add : function(id, o, dependencies) {
11434 this.items.push(o);
11435 this.lookup[id] = {instance:o, dependencies:dependencies};
11436 this.onAdd.dispatch(this, id, o);
11437
11438 return o;
11439 },
11440 createUrl: function(baseUrl, dep) {
11441 if (typeof dep === "object") {
11442 return dep
11443 } else {
11444 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
11445 }
11446 },
11447
11448 addComponents: function(pluginName, scripts) {
11449 var pluginUrl = this.urls[pluginName];
11450 tinymce.each(scripts, function(script){
11451 tinymce.ScriptLoader.add(pluginUrl+"/"+script);
11452 });
11453 },
11454
11455 load : function(n, u, cb, s) {
11456 var t = this, url = u;
11457
11458 function loadDependencies() {
11459 var dependencies = t.dependencies(n);
11460 tinymce.each(dependencies, function(dep) {
11461 var newUrl = t.createUrl(u, dep);
11462 t.load(newUrl.resource, newUrl, undefined, undefined);
11463 });
11464 if (cb) {
11465 if (s) {
11466 cb.call(s);
11467 } else {
11468 cb.call(tinymce.ScriptLoader);
11469 }
11470 }
11471 }
11472
11473 if (t.urls[n])
11474 return;
11475 if (typeof u === "object")
11476 url = u.prefix + u.resource + u.suffix;
11477
11478 if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
11479 url = tinymce.baseURL + '/' + url;
11480
11481 t.urls[n] = url.substring(0, url.lastIndexOf('/'));
11482
11483 if (t.lookup[n]) {
11484 loadDependencies();
11485 } else {
11486 tinymce.ScriptLoader.add(url, loadDependencies, s);
11487 }
11488 }
11489 });
11490
11491 // Create plugin and theme managers
11492 tinymce.PluginManager = new tinymce.AddOnManager();
11493 tinymce.ThemeManager = new tinymce.AddOnManager();
11494 }(tinymce));
11495
11496 (function(tinymce) {
11497 // Shorten names
11498 var each = tinymce.each, extend = tinymce.extend,
11499 DOM = tinymce.DOM, Event = tinymce.dom.Event,
11500 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11501 explode = tinymce.explode,
11502 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
11503
11504 // Setup some URLs where the editor API is located and where the document is
11505 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
11506 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
11507 tinymce.documentBaseURL += '/';
11508
11509 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
11510
11511 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
11512
11513 // Add before unload listener
11514 // This was required since IE was leaking memory if you added and removed beforeunload listeners
11515 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
11516 tinymce.onBeforeUnload = new Dispatcher(tinymce);
11517
11518 // Must be on window or IE will leak if the editor is placed in frame or iframe
11519 Event.add(window, 'beforeunload', function(e) {
11520 tinymce.onBeforeUnload.dispatch(tinymce, e);
11521 });
11522
11523 tinymce.onAddEditor = new Dispatcher(tinymce);
11524
11525 tinymce.onRemoveEditor = new Dispatcher(tinymce);
11526
11527 tinymce.EditorManager = extend(tinymce, {
11528 editors : [],
11529
11530 i18n : {},
11531
11532 activeEditor : null,
11533
11534 init : function(s) {
11535 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
11536
11537 function createId(elm) {
11538 var id = elm.id;
11539
11540 // Use element id, or unique name or generate a unique id
11541 if (!id) {
11542 id = elm.name;
11543
11544 if (id && !DOM.get(id)) {
11545 id = elm.name;
11546 } else {
11547 // Generate unique name
11548 id = DOM.uniqueId();
11549 }
11550
11551 elm.setAttribute('id', id);
11552 }
11553
11554 return id;
11555 };
11556
11557 function execCallback(se, n, s) {
11558 var f = se[n];
11559
11560 if (!f)
11561 return;
11562
11563 if (tinymce.is(f, 'string')) {
11564 s = f.replace(/\.\w+$/, '');
11565 s = s ? tinymce.resolve(s) : 0;
11566 f = tinymce.resolve(f);
11567 }
11568
11569 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
11570 };
11571
11572 s = extend({
11573 theme : "simple",
11574 language : "en"
11575 }, s);
11576
11577 t.settings = s;
11578
11579 // Legacy call
11580 Event.bind(window, 'ready', function() {
11581 var l, co;
11582
11583 execCallback(s, 'onpageload');
11584
11585 switch (s.mode) {
11586 case "exact":
11587 l = s.elements || '';
11588
11589 if(l.length > 0) {
11590 each(explode(l), function(v) {
11591 if (DOM.get(v)) {
11592 ed = new tinymce.Editor(v, s);
11593 el.push(ed);
11594 ed.render(1);
11595 } else {
11596 each(document.forms, function(f) {
11597 each(f.elements, function(e) {
11598 if (e.name === v) {
11599 v = 'mce_editor_' + instanceCounter++;
11600 DOM.setAttrib(e, 'id', v);
11601
11602 ed = new tinymce.Editor(v, s);
11603 el.push(ed);
11604 ed.render(1);
11605 }
11606 });
11607 });
11608 }
11609 });
11610 }
11611 break;
11612
11613 case "textareas":
11614 case "specific_textareas":
11615 function hasClass(n, c) {
11616 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
11617 };
11618
11619 each(DOM.select('textarea'), function(elm) {
11620 if (s.editor_deselector && hasClass(elm, s.editor_deselector))
11621 return;
11622
11623 if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
11624 ed = new tinymce.Editor(createId(elm), s);
11625 el.push(ed);
11626 ed.render(1);
11627 }
11628 });
11629 break;
11630
11631 default:
11632 if (s.types) {
11633 // Process type specific selector
11634 each(s.types, function(type) {
11635 each(DOM.select(type.selector), function(elm) {
11636 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
11637 el.push(editor);
11638 editor.render(1);
11639 });
11640 });
11641 } else if (s.selector) {
11642 // Process global selector
11643 each(DOM.select(s.selector), function(elm) {
11644 var editor = new tinymce.Editor(createId(elm), s);
11645 el.push(editor);
11646 editor.render(1);
11647 });
11648 }
11649 }
11650
11651 // Call onInit when all editors are initialized
11652 if (s.oninit) {
11653 l = co = 0;
11654
11655 each(el, function(ed) {
11656 co++;
11657
11658 if (!ed.initialized) {
11659 // Wait for it
11660 ed.onInit.add(function() {
11661 l++;
11662
11663 // All done
11664 if (l == co)
11665 execCallback(s, 'oninit');
11666 });
11667 } else
11668 l++;
11669
11670 // All done
11671 if (l == co)
11672 execCallback(s, 'oninit');
11673 });
11674 }
11675 });
11676 },
11677
11678 get : function(id) {
11679 if (id === undefined)
11680 return this.editors;
11681
11682 return this.editors[id];
11683 },
11684
11685 getInstanceById : function(id) {
11686 return this.get(id);
11687 },
11688
11689 add : function(editor) {
11690 var self = this, editors = self.editors;
11691
11692 // Add named and index editor instance
11693 editors[editor.id] = editor;
11694 editors.push(editor);
11695
11696 self._setActive(editor);
11697 self.onAddEditor.dispatch(self, editor);
11698
11699
11700 return editor;
11701 },
11702
11703 remove : function(editor) {
11704 var t = this, i, editors = t.editors;
11705
11706 // Not in the collection
11707 if (!editors[editor.id])
11708 return null;
11709
11710 delete editors[editor.id];
11711
11712 for (i = 0; i < editors.length; i++) {
11713 if (editors[i] == editor) {
11714 editors.splice(i, 1);
11715 break;
11716 }
11717 }
11718
11719 // Select another editor since the active one was removed
11720 if (t.activeEditor == editor)
11721 t._setActive(editors[0]);
11722
11723 editor.destroy();
11724 t.onRemoveEditor.dispatch(t, editor);
11725
11726 return editor;
11727 },
11728
11729 execCommand : function(c, u, v) {
11730 var t = this, ed = t.get(v), w;
11731
11732 // Manager commands
11733 switch (c) {
11734 case "mceFocus":
11735 ed.focus();
11736 return true;
11737
11738 case "mceAddEditor":
11739 case "mceAddControl":
11740 if (!t.get(v))
11741 new tinymce.Editor(v, t.settings).render();
11742
11743 return true;
11744
11745 case "mceAddFrameControl":
11746 w = v.window;
11747
11748 // Add tinyMCE global instance and tinymce namespace to specified window
11749 w.tinyMCE = tinyMCE;
11750 w.tinymce = tinymce;
11751
11752 tinymce.DOM.doc = w.document;
11753 tinymce.DOM.win = w;
11754
11755 ed = new tinymce.Editor(v.element_id, v);
11756 ed.render();
11757
11758 // Fix IE memory leaks
11759 if (tinymce.isIE) {
11760 function clr() {
11761 ed.destroy();
11762 w.detachEvent('onunload', clr);
11763 w = w.tinyMCE = w.tinymce = null; // IE leak
11764 };
11765
11766 w.attachEvent('onunload', clr);
11767 }
11768
11769 v.page_window = null;
11770
11771 return true;
11772
11773 case "mceRemoveEditor":
11774 case "mceRemoveControl":
11775 if (ed)
11776 ed.remove();
11777
11778 return true;
11779
11780 case 'mceToggleEditor':
11781 if (!ed) {
11782 t.execCommand('mceAddControl', 0, v);
11783 return true;
11784 }
11785
11786 if (ed.isHidden())
11787 ed.show();
11788 else
11789 ed.hide();
11790
11791 return true;
11792 }
11793
11794 // Run command on active editor
11795 if (t.activeEditor)
11796 return t.activeEditor.execCommand(c, u, v);
11797
11798 return false;
11799 },
11800
11801 execInstanceCommand : function(id, c, u, v) {
11802 var ed = this.get(id);
11803
11804 if (ed)
11805 return ed.execCommand(c, u, v);
11806
11807 return false;
11808 },
11809
11810 triggerSave : function() {
11811 each(this.editors, function(e) {
11812 e.save();
11813 });
11814 },
11815
11816 addI18n : function(p, o) {
11817 var lo, i18n = this.i18n;
11818
11819 if (!tinymce.is(p, 'string')) {
11820 each(p, function(o, lc) {
11821 each(o, function(o, g) {
11822 each(o, function(o, k) {
11823 if (g === 'common')
11824 i18n[lc + '.' + k] = o;
11825 else
11826 i18n[lc + '.' + g + '.' + k] = o;
11827 });
11828 });
11829 });
11830 } else {
11831 each(o, function(o, k) {
11832 i18n[p + '.' + k] = o;
11833 });
11834 }
11835 },
11836
11837 // Private methods
11838
11839 _setActive : function(editor) {
11840 this.selectedInstance = this.activeEditor = editor;
11841 }
11842 });
11843 })(tinymce);
11844
11845 (function(tinymce) {
11846 // Shorten these names
11847 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
11848 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
11849 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
11850 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11851 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode, VK = tinymce.VK;
11852
11853 tinymce.create('tinymce.Editor', {
11854 Editor : function(id, s) {
11855 var t = this;
11856
11857 t.id = t.editorId = id;
11858
11859 t.execCommands = {};
11860 t.queryStateCommands = {};
11861 t.queryValueCommands = {};
11862
11863 t.isNotDirty = false;
11864
11865 t.plugins = {};
11866
11867 // Add events to the editor
11868 each([
11869 'onPreInit',
11870
11871 'onBeforeRenderUI',
11872
11873 'onPostRender',
11874
11875 'onLoad',
11876
11877 'onInit',
11878
11879 'onRemove',
11880
11881 'onActivate',
11882
11883 'onDeactivate',
11884
11885 'onClick',
11886
11887 'onEvent',
11888
11889 'onMouseUp',
11890
11891 'onMouseDown',
11892
11893 'onDblClick',
11894
11895 'onKeyDown',
11896
11897 'onKeyUp',
11898
11899 'onKeyPress',
11900
11901 'onContextMenu',
11902
11903 'onSubmit',
11904
11905 'onReset',
11906
11907 'onPaste',
11908
11909 'onPreProcess',
11910
11911 'onPostProcess',
11912
11913 'onBeforeSetContent',
11914
11915 'onBeforeGetContent',
11916
11917 'onSetContent',
11918
11919 'onGetContent',
11920
11921 'onLoadContent',
11922
11923 'onSaveContent',
11924
11925 'onNodeChange',
11926
11927 'onChange',
11928
11929 'onBeforeExecCommand',
11930
11931 'onExecCommand',
11932
11933 'onUndo',
11934
11935 'onRedo',
11936
11937 'onVisualAid',
11938
11939 'onSetProgressState',
11940
11941 'onSetAttrib'
11942 ], function(e) {
11943 t[e] = new Dispatcher(t);
11944 });
11945
11946 t.settings = s = extend({
11947 id : id,
11948 language : 'en',
11949 docs_language : 'en',
11950 theme : 'simple',
11951 skin : 'default',
11952 delta_width : 0,
11953 delta_height : 0,
11954 popup_css : '',
11955 plugins : '',
11956 document_base_url : tinymce.documentBaseURL,
11957 add_form_submit_trigger : 1,
11958 submit_patch : 1,
11959 add_unload_trigger : 1,
11960 convert_urls : 1,
11961 relative_urls : 1,
11962 remove_script_host : 1,
11963 table_inline_editing : 0,
11964 object_resizing : 1,
11965 cleanup : 1,
11966 accessibility_focus : 1,
11967 custom_shortcuts : 1,
11968 custom_undo_redo_keyboard_shortcuts : 1,
11969 custom_undo_redo_restore_selection : 1,
11970 custom_undo_redo : 1,
11971 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
11972 visual_table_class : 'mceItemTable',
11973 visual : 1,
11974 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
11975 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
11976 apply_source_formatting : 1,
11977 directionality : 'ltr',
11978 forced_root_block : 'p',
11979 hidden_input : 1,
11980 padd_empty_editor : 1,
11981 render_ui : 1,
11982 init_theme : 1,
11983 force_p_newlines : 1,
11984 indentation : '30px',
11985 keep_styles : 1,
11986 fix_table_elements : 1,
11987 inline_styles : 1,
11988 convert_fonts_to_spans : true,
11989 indent : 'simple',
11990 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside',
11991 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside',
11992 validate : true,
11993 entity_encoding : 'named',
11994 url_converter : t.convertURL,
11995 url_converter_scope : t,
11996 ie7_compat : true
11997 }, s);
11998
11999 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
12000 base_uri : tinyMCE.baseURI
12001 });
12002
12003 t.baseURI = tinymce.baseURI;
12004
12005 t.contentCSS = [];
12006
12007 // Call setup
12008 t.execCallback('setup', t);
12009 },
12010
12011 render : function(nst) {
12012 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
12013
12014 // Page is not loaded yet, wait for it
12015 if (!Event.domLoaded) {
12016 Event.add(window, 'ready', function() {
12017 t.render();
12018 });
12019 return;
12020 }
12021
12022 tinyMCE.settings = s;
12023
12024 // Element not found, then skip initialization
12025 if (!t.getElement())
12026 return;
12027
12028 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff
12029 // here since the browser says it has contentEditable support but there is no visible
12030 // caret We will remove this check ones Apple implements full contentEditable support
12031 if (tinymce.isIDevice && !tinymce.isIOS5)
12032 return;
12033
12034 // Add hidden input for non input elements inside form elements
12035 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
12036 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
12037
12038 if (tinymce.WindowManager)
12039 t.windowManager = new tinymce.WindowManager(t);
12040
12041 if (s.encoding == 'xml') {
12042 t.onGetContent.add(function(ed, o) {
12043 if (o.save)
12044 o.content = DOM.encode(o.content);
12045 });
12046 }
12047
12048 if (s.add_form_submit_trigger) {
12049 t.onSubmit.addToTop(function() {
12050 if (t.initialized) {
12051 t.save();
12052 t.isNotDirty = 1;
12053 }
12054 });
12055 }
12056
12057 if (s.add_unload_trigger) {
12058 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
12059 if (t.initialized && !t.destroyed && !t.isHidden())
12060 t.save({format : 'raw', no_events : true});
12061 });
12062 }
12063
12064 tinymce.addUnload(t.destroy, t);
12065
12066 if (s.submit_patch) {
12067 t.onBeforeRenderUI.add(function() {
12068 var n = t.getElement().form;
12069
12070 if (!n)
12071 return;
12072
12073 // Already patched
12074 if (n._mceOldSubmit)
12075 return;
12076
12077 // Check page uses id="submit" or name="submit" for it's submit button
12078 if (!n.submit.nodeType && !n.submit.length) {
12079 t.formElement = n;
12080 n._mceOldSubmit = n.submit;
12081 n.submit = function() {
12082 // Save all instances
12083 tinymce.triggerSave();
12084 t.isNotDirty = 1;
12085
12086 return t.formElement._mceOldSubmit(t.formElement);
12087 };
12088 }
12089
12090 n = null;
12091 });
12092 }
12093
12094 // Load scripts
12095 function loadScripts() {
12096 if (s.language && s.language_load !== false)
12097 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
12098
12099 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
12100 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
12101
12102 each(explode(s.plugins), function(p) {
12103 if (p &&!PluginManager.urls[p]) {
12104 if (p.charAt(0) == '-') {
12105 p = p.substr(1, p.length);
12106 var dependencies = PluginManager.dependencies(p);
12107 each(dependencies, function(dep) {
12108 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
12109 var dep = PluginManager.createUrl(defaultSettings, dep);
12110 PluginManager.load(dep.resource, dep);
12111
12112 });
12113 } else {
12114 // Skip safari plugin, since it is removed as of 3.3b1
12115 if (p == 'safari') {
12116 return;
12117 }
12118 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
12119 }
12120 }
12121 });
12122
12123 // Init when que is loaded
12124 sl.loadQueue(function() {
12125 if (!t.removed)
12126 t.init();
12127 });
12128 };
12129
12130 loadScripts();
12131 },
12132
12133 init : function() {
12134 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
12135
12136 tinymce.add(t);
12137
12138 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
12139
12140 if (s.theme) {
12141 s.theme = s.theme.replace(/-/, '');
12142 o = ThemeManager.get(s.theme);
12143 t.theme = new o();
12144
12145 if (t.theme.init && s.init_theme)
12146 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
12147 }
12148 function initPlugin(p) {
12149 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
12150 if (c && tinymce.inArray(initializedPlugins,p) === -1) {
12151 each(PluginManager.dependencies(p), function(dep){
12152 initPlugin(dep);
12153 });
12154 po = new c(t, u);
12155
12156 t.plugins[p] = po;
12157
12158 if (po.init) {
12159 po.init(t, u);
12160 initializedPlugins.push(p);
12161 }
12162 }
12163 }
12164
12165 // Create all plugins
12166 each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
12167
12168 // Setup popup CSS path(s)
12169 if (s.popup_css !== false) {
12170 if (s.popup_css)
12171 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
12172 else
12173 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
12174 }
12175
12176 if (s.popup_css_add)
12177 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
12178
12179 t.controlManager = new tinymce.ControlManager(t);
12180
12181 if (s.custom_undo_redo) {
12182 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
12183 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
12184 t.undoManager.beforeChange();
12185 });
12186
12187 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
12188 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
12189 t.undoManager.add();
12190 });
12191 }
12192
12193 t.onExecCommand.add(function(ed, c) {
12194 // Don't refresh the select lists until caret move
12195 if (!/^(FontName|FontSize)$/.test(c))
12196 t.nodeChanged();
12197 });
12198
12199 // Remove ghost selections on images and tables in Gecko
12200 if (isGecko) {
12201 function repaint(a, o) {
12202 if (!o || !o.initial)
12203 t.execCommand('mceRepaint');
12204 };
12205
12206 t.onUndo.add(repaint);
12207 t.onRedo.add(repaint);
12208 t.onSetContent.add(repaint);
12209 }
12210
12211 // Enables users to override the control factory
12212 t.onBeforeRenderUI.dispatch(t, t.controlManager);
12213
12214 // Measure box
12215 if (s.render_ui) {
12216 w = s.width || e.style.width || e.offsetWidth;
12217 h = s.height || e.style.height || e.offsetHeight;
12218 t.orgDisplay = e.style.display;
12219 re = /^[0-9\.]+(|px)$/i;
12220
12221 if (re.test('' + w))
12222 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
12223
12224 if (re.test('' + h))
12225 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
12226
12227 // Render UI
12228 o = t.theme.renderUI({
12229 targetNode : e,
12230 width : w,
12231 height : h,
12232 deltaWidth : s.delta_width,
12233 deltaHeight : s.delta_height
12234 });
12235
12236 t.editorContainer = o.editorContainer;
12237 }
12238
12239
12240 // User specified a document.domain value
12241 if (document.domain && location.hostname != document.domain)
12242 tinymce.relaxedDomain = document.domain;
12243
12244 // Resize editor
12245 DOM.setStyles(o.sizeContainer || o.editorContainer, {
12246 width : w,
12247 height : h
12248 });
12249
12250 // Load specified content CSS last
12251 if (s.content_css) {
12252 tinymce.each(explode(s.content_css), function(u) {
12253 t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
12254 });
12255 }
12256
12257 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
12258 if (h < 100)
12259 h = 100;
12260
12261 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
12262
12263 // We only need to override paths if we have to
12264 // IE has a bug where it remove site absolute urls to relative ones if this is specified
12265 if (s.document_base_url != tinymce.documentBaseURL)
12266 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
12267
12268 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
12269 if (s.ie7_compat)
12270 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
12271 else
12272 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
12273
12274 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
12275
12276 // Load the CSS by injecting them into the HTML this will reduce "flicker"
12277 for (i = 0; i < t.contentCSS.length; i++) {
12278 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
12279 }
12280
12281 t.contentCSS = [];
12282
12283 bi = s.body_id || 'tinymce';
12284 if (bi.indexOf('=') != -1) {
12285 bi = t.getParam('body_id', '', 'hash');
12286 bi = bi[t.id] || bi;
12287 }
12288
12289 bc = s.body_class || '';
12290 if (bc.indexOf('=') != -1) {
12291 bc = t.getParam('body_class', '', 'hash');
12292 bc = bc[t.id] || '';
12293 }
12294
12295 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
12296
12297 // Domain relaxing enabled, then set document domain
12298 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
12299 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
12300 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();})()';
12301 }
12302
12303 // Create iframe
12304 // TODO: ACC add the appropriate description on this.
12305 n = DOM.add(o.iframeContainer, 'iframe', {
12306 id : t.id + "_ifr",
12307 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
12308 frameBorder : '0',
12309 allowTransparency : "true",
12310 title : s.aria_label,
12311 style : {
12312 width : '100%',
12313 height : h,
12314 display : 'block' // Important for Gecko to render the iframe correctly
12315 }
12316 });
12317
12318 t.contentAreaContainer = o.iframeContainer;
12319 DOM.get(o.editorContainer).style.display = t.orgDisplay;
12320 DOM.get(t.id).style.display = 'none';
12321 DOM.setAttrib(t.id, 'aria-hidden', true);
12322
12323 if (!tinymce.relaxedDomain || !u)
12324 t.setupIframe();
12325
12326 e = n = o = null; // Cleanup
12327 },
12328
12329 setupIframe : function() {
12330 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
12331
12332 // Setup iframe body
12333 if (!isIE || !tinymce.relaxedDomain) {
12334 d.open();
12335 d.write(t.iframeHTML);
12336 d.close();
12337
12338 if (tinymce.relaxedDomain)
12339 d.domain = tinymce.relaxedDomain;
12340 }
12341
12342 // It will not steal focus while setting contentEditable
12343 b = t.getBody();
12344 b.disabled = true;
12345
12346 if (!s.readonly)
12347 b.contentEditable = true;
12348
12349 b.disabled = false;
12350
12351 t.schema = new tinymce.html.Schema(s);
12352
12353 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
12354 keep_values : true,
12355 url_converter : t.convertURL,
12356 url_converter_scope : t,
12357 hex_colors : s.force_hex_style_colors,
12358 class_filter : s.class_filter,
12359 update_styles : 1,
12360 fix_ie_paragraphs : 1,
12361 schema : t.schema
12362 });
12363
12364 t.parser = new tinymce.html.DomParser(s, t.schema);
12365
12366 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
12367 if (!t.settings.allow_html_in_named_anchor) {
12368 t.parser.addAttributeFilter('name', function(nodes, name) {
12369 var i = nodes.length, sibling, prevSibling, parent, node;
12370
12371 while (i--) {
12372 node = nodes[i];
12373 if (node.name === 'a' && node.firstChild) {
12374 parent = node.parent;
12375
12376 // Move children after current node
12377 sibling = node.lastChild;
12378 do {
12379 prevSibling = sibling.prev;
12380 parent.insert(sibling, node);
12381 sibling = prevSibling;
12382 } while (sibling);
12383 }
12384 }
12385 });
12386 }
12387
12388 // Convert src and href into data-mce-src, data-mce-href and data-mce-style
12389 t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
12390 var i = nodes.length, node, dom = t.dom, value, internalName;
12391
12392 while (i--) {
12393 node = nodes[i];
12394 value = node.attr(name);
12395 internalName = 'data-mce-' + name;
12396
12397 // Add internal attribute if we need to we don't on a refresh of the document
12398 if (!node.attributes.map[internalName]) {
12399 if (name === "style")
12400 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
12401 else
12402 node.attr(internalName, t.convertURL(value, name, node.name));
12403 }
12404 }
12405 });
12406
12407 // Keep scripts from executing
12408 t.parser.addNodeFilter('script', function(nodes, name) {
12409 var i = nodes.length, node;
12410
12411 while (i--) {
12412 node = nodes[i];
12413 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
12414 }
12415 });
12416
12417 t.parser.addNodeFilter('#cdata', function(nodes, name) {
12418 var i = nodes.length, node;
12419
12420 while (i--) {
12421 node = nodes[i];
12422 node.type = 8;
12423 node.name = '#comment';
12424 node.value = '[CDATA[' + node.value + ']]';
12425 }
12426 });
12427
12428 t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
12429 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
12430
12431 while (i--) {
12432 node = nodes[i];
12433
12434 if (node.isEmpty(nonEmptyElements))
12435 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
12436 }
12437 });
12438
12439 t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
12440
12441 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
12442
12443 t.formatter = new tinymce.Formatter(this);
12444
12445 // Register default formats
12446 t.formatter.register({
12447 alignleft : [
12448 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
12449 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
12450 ],
12451
12452 aligncenter : [
12453 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
12454 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
12455 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
12456 ],
12457
12458 alignright : [
12459 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
12460 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
12461 ],
12462
12463 alignfull : [
12464 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
12465 ],
12466
12467 bold : [
12468 {inline : 'strong', remove : 'all'},
12469 {inline : 'span', styles : {fontWeight : 'bold'}},
12470 {inline : 'b', remove : 'all'}
12471 ],
12472
12473 italic : [
12474 {inline : 'em', remove : 'all'},
12475 {inline : 'span', styles : {fontStyle : 'italic'}},
12476 {inline : 'i', remove : 'all'}
12477 ],
12478
12479 underline : [
12480 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
12481 {inline : 'u', remove : 'all'}
12482 ],
12483
12484 strikethrough : [
12485 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
12486 {inline : 'strike', remove : 'all'}
12487 ],
12488
12489 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
12490 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
12491 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
12492 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
12493 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
12494 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
12495 subscript : {inline : 'sub'},
12496 superscript : {inline : 'sup'},
12497
12498 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
12499 onmatch : function(node) {
12500 return true;
12501 },
12502
12503 onformat : function(elm, fmt, vars) {
12504 each(vars, function(value, key) {
12505 t.dom.setAttrib(elm, key, value);
12506 });
12507 }
12508 },
12509
12510 removeformat : [
12511 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
12512 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
12513 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
12514 ]
12515 });
12516
12517 // Register default block formats
12518 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
12519 t.formatter.register(name, {block : name, remove : 'all'});
12520 });
12521
12522 // Register user defined formats
12523 t.formatter.register(t.settings.formats);
12524
12525 t.undoManager = new tinymce.UndoManager(t);
12526
12527 // Pass through
12528 t.undoManager.onAdd.add(function(um, l) {
12529 if (um.hasUndo())
12530 return t.onChange.dispatch(t, l, um);
12531 });
12532
12533 t.undoManager.onUndo.add(function(um, l) {
12534 return t.onUndo.dispatch(t, l, um);
12535 });
12536
12537 t.undoManager.onRedo.add(function(um, l) {
12538 return t.onRedo.dispatch(t, l, um);
12539 });
12540
12541 t.forceBlocks = new tinymce.ForceBlocks(t, {
12542 forced_root_block : s.forced_root_block
12543 });
12544
12545 t.editorCommands = new tinymce.EditorCommands(t);
12546
12547 // Pass through
12548 t.serializer.onPreProcess.add(function(se, o) {
12549 return t.onPreProcess.dispatch(t, o, se);
12550 });
12551
12552 t.serializer.onPostProcess.add(function(se, o) {
12553 return t.onPostProcess.dispatch(t, o, se);
12554 });
12555
12556 t.onPreInit.dispatch(t);
12557
12558 if (!s.gecko_spellcheck)
12559 t.getBody().spellcheck = 0;
12560
12561 if (!s.readonly)
12562 t._addEvents();
12563
12564 t.controlManager.onPostRender.dispatch(t, t.controlManager);
12565 t.onPostRender.dispatch(t);
12566
12567 t.quirks = new tinymce.util.Quirks(this);
12568
12569 if (s.directionality)
12570 t.getBody().dir = s.directionality;
12571
12572 if (s.nowrap)
12573 t.getBody().style.whiteSpace = "nowrap";
12574
12575 if (s.handle_node_change_callback) {
12576 t.onNodeChange.add(function(ed, cm, n) {
12577 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
12578 });
12579 }
12580
12581 if (s.save_callback) {
12582 t.onSaveContent.add(function(ed, o) {
12583 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
12584
12585 if (h)
12586 o.content = h;
12587 });
12588 }
12589
12590 if (s.onchange_callback) {
12591 t.onChange.add(function(ed, l) {
12592 t.execCallback('onchange_callback', t, l);
12593 });
12594 }
12595
12596 if (s.protect) {
12597 t.onBeforeSetContent.add(function(ed, o) {
12598 if (s.protect) {
12599 each(s.protect, function(pattern) {
12600 o.content = o.content.replace(pattern, function(str) {
12601 return '<!--mce:protected ' + escape(str) + '-->';
12602 });
12603 });
12604 }
12605 });
12606 }
12607
12608 if (s.convert_newlines_to_brs) {
12609 t.onBeforeSetContent.add(function(ed, o) {
12610 if (o.initial)
12611 o.content = o.content.replace(/\r?\n/g, '<br />');
12612 });
12613 }
12614
12615 if (s.preformatted) {
12616 t.onPostProcess.add(function(ed, o) {
12617 o.content = o.content.replace(/^\s*<pre.*?>/, '');
12618 o.content = o.content.replace(/<\/pre>\s*$/, '');
12619
12620 if (o.set)
12621 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
12622 });
12623 }
12624
12625 if (s.verify_css_classes) {
12626 t.serializer.attribValueFilter = function(n, v) {
12627 var s, cl;
12628
12629 if (n == 'class') {
12630 // Build regexp for classes
12631 if (!t.classesRE) {
12632 cl = t.dom.getClasses();
12633
12634 if (cl.length > 0) {
12635 s = '';
12636
12637 each (cl, function(o) {
12638 s += (s ? '|' : '') + o['class'];
12639 });
12640
12641 t.classesRE = new RegExp('(' + s + ')', 'gi');
12642 }
12643 }
12644
12645 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
12646 }
12647
12648 return v;
12649 };
12650 }
12651
12652 if (s.cleanup_callback) {
12653 t.onBeforeSetContent.add(function(ed, o) {
12654 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
12655 });
12656
12657 t.onPreProcess.add(function(ed, o) {
12658 if (o.set)
12659 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
12660
12661 if (o.get)
12662 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
12663 });
12664
12665 t.onPostProcess.add(function(ed, o) {
12666 if (o.set)
12667 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
12668
12669 if (o.get)
12670 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
12671 });
12672 }
12673
12674 if (s.save_callback) {
12675 t.onGetContent.add(function(ed, o) {
12676 if (o.save)
12677 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
12678 });
12679 }
12680
12681 if (s.handle_event_callback) {
12682 t.onEvent.add(function(ed, e, o) {
12683 if (t.execCallback('handle_event_callback', e, ed, o) === false)
12684 Event.cancel(e);
12685 });
12686 }
12687
12688 // Add visual aids when new contents is added
12689 t.onSetContent.add(function() {
12690 t.addVisual(t.getBody());
12691 });
12692
12693 // Remove empty contents
12694 if (s.padd_empty_editor) {
12695 t.onPostProcess.add(function(ed, o) {
12696 o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
12697 });
12698 }
12699
12700 if (isGecko) {
12701 // Fix gecko link bug, when a link is placed at the end of block elements there is
12702 // no way to move the caret behind the link. This fix adds a bogus br element after the link
12703 function fixLinks(ed, o) {
12704 each(ed.dom.select('a'), function(n) {
12705 var pn = n.parentNode;
12706
12707 if (ed.dom.isBlock(pn) && pn.lastChild === n)
12708 ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
12709 });
12710 };
12711
12712 t.onExecCommand.add(function(ed, cmd) {
12713 if (cmd === 'CreateLink')
12714 fixLinks(ed);
12715 });
12716
12717 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
12718 }
12719
12720 t.load({initial : true, format : 'html'});
12721 t.startContent = t.getContent({format : 'raw'});
12722 t.undoManager.add();
12723 t.initialized = true;
12724
12725 t.onInit.dispatch(t);
12726 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
12727 t.execCallback('init_instance_callback', t);
12728 t.focus(true);
12729 t.nodeChanged({initial : 1});
12730
12731 // Load specified content CSS last
12732 each(t.contentCSS, function(u) {
12733 t.dom.loadCSS(u);
12734 });
12735
12736 // Handle auto focus
12737 if (s.auto_focus) {
12738 setTimeout(function () {
12739 var ed = tinymce.get(s.auto_focus);
12740
12741 ed.selection.select(ed.getBody(), 1);
12742 ed.selection.collapse(1);
12743 ed.getBody().focus();
12744 ed.getWin().focus();
12745 }, 100);
12746 }
12747
12748 e = null;
12749 },
12750
12751
12752 focus : function(sf) {
12753 var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
12754
12755 if (!sf) {
12756 // Get selected control element
12757 ieRng = selection.getRng();
12758 if (ieRng.item) {
12759 controlElm = ieRng.item(0);
12760 }
12761
12762 t._refreshContentEditable();
12763
12764 // Is not content editable
12765 if (!ce)
12766 t.getWin().focus();
12767
12768 // Focus the body as well since it's contentEditable
12769 if (tinymce.isGecko) {
12770 t.getBody().focus();
12771 }
12772
12773 // Restore selected control element
12774 // This is needed when for example an image is selected within a
12775 // layer a call to focus will then remove the control selection
12776 if (controlElm && controlElm.ownerDocument == doc) {
12777 ieRng = doc.body.createControlRange();
12778 ieRng.addElement(controlElm);
12779 ieRng.select();
12780 }
12781
12782 }
12783
12784 if (tinymce.activeEditor != t) {
12785 if ((oed = tinymce.activeEditor) != null)
12786 oed.onDeactivate.dispatch(oed, t);
12787
12788 t.onActivate.dispatch(t, oed);
12789 }
12790
12791 tinymce._setActive(t);
12792 },
12793
12794 execCallback : function(n) {
12795 var t = this, f = t.settings[n], s;
12796
12797 if (!f)
12798 return;
12799
12800 // Look through lookup
12801 if (t.callbackLookup && (s = t.callbackLookup[n])) {
12802 f = s.func;
12803 s = s.scope;
12804 }
12805
12806 if (is(f, 'string')) {
12807 s = f.replace(/\.\w+$/, '');
12808 s = s ? tinymce.resolve(s) : 0;
12809 f = tinymce.resolve(f);
12810 t.callbackLookup = t.callbackLookup || {};
12811 t.callbackLookup[n] = {func : f, scope : s};
12812 }
12813
12814 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
12815 },
12816
12817 translate : function(s) {
12818 var c = this.settings.language || 'en', i18n = tinymce.i18n;
12819
12820 if (!s)
12821 return '';
12822
12823 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
12824 return i18n[c + '.' + b] || '{#' + b + '}';
12825 });
12826 },
12827
12828 getLang : function(n, dv) {
12829 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
12830 },
12831
12832 getParam : function(n, dv, ty) {
12833 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
12834
12835 if (ty === 'hash') {
12836 o = {};
12837
12838 if (is(v, 'string')) {
12839 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
12840 v = v.split('=');
12841
12842 if (v.length > 1)
12843 o[tr(v[0])] = tr(v[1]);
12844 else
12845 o[tr(v[0])] = tr(v);
12846 });
12847 } else
12848 o = v;
12849
12850 return o;
12851 }
12852
12853 return v;
12854 },
12855
12856 nodeChanged : function(o) {
12857 var t = this, s = t.selection, n = s.getStart() || t.getBody();
12858
12859 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
12860 if (t.initialized) {
12861 o = o || {};
12862 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
12863
12864 // Get parents and add them to object
12865 o.parents = [];
12866 t.dom.getParent(n, function(node) {
12867 if (node.nodeName == 'BODY')
12868 return true;
12869
12870 o.parents.push(node);
12871 });
12872
12873 t.onNodeChange.dispatch(
12874 t,
12875 o ? o.controlManager || t.controlManager : t.controlManager,
12876 n,
12877 s.isCollapsed(),
12878 o
12879 );
12880 }
12881 },
12882
12883 addButton : function(n, s) {
12884 var t = this;
12885
12886 t.buttons = t.buttons || {};
12887 t.buttons[n] = s;
12888 },
12889
12890 addCommand : function(name, callback, scope) {
12891 this.execCommands[name] = {func : callback, scope : scope || this};
12892 },
12893
12894 addQueryStateHandler : function(name, callback, scope) {
12895 this.queryStateCommands[name] = {func : callback, scope : scope || this};
12896 },
12897
12898 addQueryValueHandler : function(name, callback, scope) {
12899 this.queryValueCommands[name] = {func : callback, scope : scope || this};
12900 },
12901
12902 addShortcut : function(pa, desc, cmd_func, sc) {
12903 var t = this, c;
12904
12905 if (!t.settings.custom_shortcuts)
12906 return false;
12907
12908 t.shortcuts = t.shortcuts || {};
12909
12910 if (is(cmd_func, 'string')) {
12911 c = cmd_func;
12912
12913 cmd_func = function() {
12914 t.execCommand(c, false, null);
12915 };
12916 }
12917
12918 if (is(cmd_func, 'object')) {
12919 c = cmd_func;
12920
12921 cmd_func = function() {
12922 t.execCommand(c[0], c[1], c[2]);
12923 };
12924 }
12925
12926 each(explode(pa), function(pa) {
12927 var o = {
12928 func : cmd_func,
12929 scope : sc || this,
12930 desc : desc,
12931 alt : false,
12932 ctrl : false,
12933 shift : false
12934 };
12935
12936 each(explode(pa, '+'), function(v) {
12937 switch (v) {
12938 case 'alt':
12939 case 'ctrl':
12940 case 'shift':
12941 o[v] = true;
12942 break;
12943
12944 default:
12945 o.charCode = v.charCodeAt(0);
12946 o.keyCode = v.toUpperCase().charCodeAt(0);
12947 }
12948 });
12949
12950 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
12951 });
12952
12953 return true;
12954 },
12955
12956 execCommand : function(cmd, ui, val, a) {
12957 var t = this, s = 0, o, st;
12958
12959 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
12960 t.focus();
12961
12962 a = extend({}, a);
12963 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
12964 if (a.terminate)
12965 return false;
12966
12967 // Command callback
12968 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
12969 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12970 return true;
12971 }
12972
12973 // Registred commands
12974 if (o = t.execCommands[cmd]) {
12975 st = o.func.call(o.scope, ui, val);
12976
12977 // Fall through on true
12978 if (st !== true) {
12979 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12980 return st;
12981 }
12982 }
12983
12984 // Plugin commands
12985 each(t.plugins, function(p) {
12986 if (p.execCommand && p.execCommand(cmd, ui, val)) {
12987 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12988 s = 1;
12989 return false;
12990 }
12991 });
12992
12993 if (s)
12994 return true;
12995
12996 // Theme commands
12997 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
12998 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12999 return true;
13000 }
13001
13002 // Editor commands
13003 if (t.editorCommands.execCommand(cmd, ui, val)) {
13004 t.onExecCommand.dispatch(t, cmd, ui, val, a);
13005 return true;
13006 }
13007
13008 // Browser commands
13009 t.getDoc().execCommand(cmd, ui, val);
13010 t.onExecCommand.dispatch(t, cmd, ui, val, a);
13011 },
13012
13013 queryCommandState : function(cmd) {
13014 var t = this, o, s;
13015
13016 // Is hidden then return undefined
13017 if (t._isHidden())
13018 return;
13019
13020 // Registred commands
13021 if (o = t.queryStateCommands[cmd]) {
13022 s = o.func.call(o.scope);
13023
13024 // Fall though on true
13025 if (s !== true)
13026 return s;
13027 }
13028
13029 // Registred commands
13030 o = t.editorCommands.queryCommandState(cmd);
13031 if (o !== -1)
13032 return o;
13033
13034 // Browser commands
13035 try {
13036 return this.getDoc().queryCommandState(cmd);
13037 } catch (ex) {
13038 // Fails sometimes see bug: 1896577
13039 }
13040 },
13041
13042 queryCommandValue : function(c) {
13043 var t = this, o, s;
13044
13045 // Is hidden then return undefined
13046 if (t._isHidden())
13047 return;
13048
13049 // Registred commands
13050 if (o = t.queryValueCommands[c]) {
13051 s = o.func.call(o.scope);
13052
13053 // Fall though on true
13054 if (s !== true)
13055 return s;
13056 }
13057
13058 // Registred commands
13059 o = t.editorCommands.queryCommandValue(c);
13060 if (is(o))
13061 return o;
13062
13063 // Browser commands
13064 try {
13065 return this.getDoc().queryCommandValue(c);
13066 } catch (ex) {
13067 // Fails sometimes see bug: 1896577
13068 }
13069 },
13070
13071 show : function() {
13072 var t = this;
13073
13074 DOM.show(t.getContainer());
13075 DOM.hide(t.id);
13076 t.load();
13077 },
13078
13079 hide : function() {
13080 var t = this, d = t.getDoc();
13081
13082 // Fixed bug where IE has a blinking cursor left from the editor
13083 if (isIE && d)
13084 d.execCommand('SelectAll');
13085
13086 // We must save before we hide so Safari doesn't crash
13087 t.save();
13088 DOM.hide(t.getContainer());
13089 DOM.setStyle(t.id, 'display', t.orgDisplay);
13090 },
13091
13092 isHidden : function() {
13093 return !DOM.isHidden(this.id);
13094 },
13095
13096 setProgressState : function(b, ti, o) {
13097 this.onSetProgressState.dispatch(this, b, ti, o);
13098
13099 return b;
13100 },
13101
13102 load : function(o) {
13103 var t = this, e = t.getElement(), h;
13104
13105 if (e) {
13106 o = o || {};
13107 o.load = true;
13108
13109 // Double encode existing entities in the value
13110 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
13111 o.element = e;
13112
13113 if (!o.no_events)
13114 t.onLoadContent.dispatch(t, o);
13115
13116 o.element = e = null;
13117
13118 return h;
13119 }
13120 },
13121
13122 save : function(o) {
13123 var t = this, e = t.getElement(), h, f;
13124
13125 if (!e || !t.initialized)
13126 return;
13127
13128 o = o || {};
13129 o.save = true;
13130
13131 // Add undo level will trigger onchange event
13132 if (!o.no_events) {
13133 t.undoManager.typing = false;
13134 t.undoManager.add();
13135 }
13136
13137 o.element = e;
13138 h = o.content = t.getContent(o);
13139
13140 if (!o.no_events)
13141 t.onSaveContent.dispatch(t, o);
13142
13143 h = o.content;
13144
13145 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
13146 e.innerHTML = h;
13147
13148 // Update hidden form element
13149 if (f = DOM.getParent(t.id, 'form')) {
13150 each(f.elements, function(e) {
13151 if (e.name == t.id) {
13152 e.value = h;
13153 return false;
13154 }
13155 });
13156 }
13157 } else
13158 e.value = h;
13159
13160 o.element = e = null;
13161
13162 return h;
13163 },
13164
13165 setContent : function(content, args) {
13166 var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
13167
13168 // Setup args object
13169 args = args || {};
13170 args.format = args.format || 'html';
13171 args.set = true;
13172 args.content = content;
13173
13174 // Do preprocessing
13175 if (!args.no_events)
13176 self.onBeforeSetContent.dispatch(self, args);
13177
13178 content = args.content;
13179
13180 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
13181 // It will also be impossible to place the caret in the editor unless there is a BR element present
13182 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
13183 forcedRootBlockName = self.settings.forced_root_block;
13184 if (forcedRootBlockName)
13185 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
13186 else
13187 content = '<br data-mce-bogus="1">';
13188
13189 body.innerHTML = content;
13190 self.selection.select(body, true);
13191 self.selection.collapse(true);
13192 return;
13193 }
13194
13195 // Parse and serialize the html
13196 if (args.format !== 'raw') {
13197 content = new tinymce.html.Serializer({}, self.schema).serialize(
13198 self.parser.parse(content)
13199 );
13200 }
13201
13202 // Set the new cleaned contents to the editor
13203 args.content = tinymce.trim(content);
13204 self.dom.setHTML(body, args.content);
13205
13206 // Do post processing
13207 if (!args.no_events)
13208 self.onSetContent.dispatch(self, args);
13209
13210 self.selection.normalize();
13211
13212 return args.content;
13213 },
13214
13215 getContent : function(args) {
13216 var self = this, content;
13217
13218 // Setup args object
13219 args = args || {};
13220 args.format = args.format || 'html';
13221 args.get = true;
13222
13223 // Do preprocessing
13224 if (!args.no_events)
13225 self.onBeforeGetContent.dispatch(self, args);
13226
13227 // Get raw contents or by default the cleaned contents
13228 if (args.format == 'raw')
13229 content = self.getBody().innerHTML;
13230 else
13231 content = self.serializer.serialize(self.getBody(), args);
13232
13233 args.content = tinymce.trim(content);
13234
13235 // Do post processing
13236 if (!args.no_events)
13237 self.onGetContent.dispatch(self, args);
13238
13239 return args.content;
13240 },
13241
13242 isDirty : function() {
13243 var self = this;
13244
13245 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
13246 },
13247
13248 getContainer : function() {
13249 var t = this;
13250
13251 if (!t.container)
13252 t.container = DOM.get(t.editorContainer || t.id + '_parent');
13253
13254 return t.container;
13255 },
13256
13257 getContentAreaContainer : function() {
13258 return this.contentAreaContainer;
13259 },
13260
13261 getElement : function() {
13262 return DOM.get(this.settings.content_element || this.id);
13263 },
13264
13265 getWin : function() {
13266 var t = this, e;
13267
13268 if (!t.contentWindow) {
13269 e = DOM.get(t.id + "_ifr");
13270
13271 if (e)
13272 t.contentWindow = e.contentWindow;
13273 }
13274
13275 return t.contentWindow;
13276 },
13277
13278 getDoc : function() {
13279 var t = this, w;
13280
13281 if (!t.contentDocument) {
13282 w = t.getWin();
13283
13284 if (w)
13285 t.contentDocument = w.document;
13286 }
13287
13288 return t.contentDocument;
13289 },
13290
13291 getBody : function() {
13292 return this.bodyElement || this.getDoc().body;
13293 },
13294
13295 convertURL : function(u, n, e) {
13296 var t = this, s = t.settings;
13297
13298 // Use callback instead
13299 if (s.urlconverter_callback)
13300 return t.execCallback('urlconverter_callback', u, e, true, n);
13301
13302 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
13303 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
13304 return u;
13305
13306 // Convert to relative
13307 if (s.relative_urls)
13308 return t.documentBaseURI.toRelative(u);
13309
13310 // Convert to absolute
13311 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
13312
13313 return u;
13314 },
13315
13316 addVisual : function(e) {
13317 var t = this, s = t.settings;
13318
13319 e = e || t.getBody();
13320
13321 if (!is(t.hasVisual))
13322 t.hasVisual = s.visual;
13323
13324 each(t.dom.select('table,a', e), function(e) {
13325 var v;
13326
13327 switch (e.nodeName) {
13328 case 'TABLE':
13329 v = t.dom.getAttrib(e, 'border');
13330
13331 if (!v || v == '0') {
13332 if (t.hasVisual)
13333 t.dom.addClass(e, s.visual_table_class);
13334 else
13335 t.dom.removeClass(e, s.visual_table_class);
13336 }
13337
13338 return;
13339
13340 case 'A':
13341 v = t.dom.getAttrib(e, 'name');
13342
13343 if (v) {
13344 if (t.hasVisual)
13345 t.dom.addClass(e, 'mceItemAnchor');
13346 else
13347 t.dom.removeClass(e, 'mceItemAnchor');
13348 }
13349
13350 return;
13351 }
13352 });
13353
13354 t.onVisualAid.dispatch(t, e, t.hasVisual);
13355 },
13356
13357 remove : function() {
13358 var t = this, e = t.getContainer();
13359
13360 if (!t.removed) {
13361 t.removed = 1; // Cancels post remove event execution
13362 t.hide();
13363
13364 // Remove all events
13365
13366 // Don't clear the window or document if content editable
13367 // is enabled since other instances might still be present
13368 if (!t.settings.content_editable) {
13369 Event.clear(t.getWin());
13370 Event.clear(t.getDoc());
13371 }
13372
13373 Event.clear(t.getBody());
13374 Event.clear(t.formElement);
13375 Event.unbind(e);
13376
13377 t.execCallback('remove_instance_callback', t);
13378 t.onRemove.dispatch(t);
13379
13380 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
13381 t.onExecCommand.listeners = [];
13382
13383 tinymce.remove(t);
13384 DOM.remove(e);
13385 }
13386 },
13387
13388 destroy : function(s) {
13389 var t = this;
13390
13391 // One time is enough
13392 if (t.destroyed)
13393 return;
13394
13395 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
13396 if (isGecko) {
13397 Event.unbind(t.getDoc());
13398 Event.unbind(t.getWin());
13399 Event.unbind(t.getBody());
13400 }
13401
13402 if (!s) {
13403 tinymce.removeUnload(t.destroy);
13404 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
13405
13406 // Manual destroy
13407 if (t.theme && t.theme.destroy)
13408 t.theme.destroy();
13409
13410 // Destroy controls, selection and dom
13411 t.controlManager.destroy();
13412 t.selection.destroy();
13413 t.dom.destroy();
13414 }
13415
13416 if (t.formElement) {
13417 t.formElement.submit = t.formElement._mceOldSubmit;
13418 t.formElement._mceOldSubmit = null;
13419 }
13420
13421 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
13422
13423 if (t.selection)
13424 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
13425
13426 t.destroyed = 1;
13427 },
13428
13429 // Internal functions
13430
13431 _addEvents : function() {
13432 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
13433 var t = this, i, s = t.settings, dom = t.dom, lo = {
13434 mouseup : 'onMouseUp',
13435 mousedown : 'onMouseDown',
13436 click : 'onClick',
13437 keyup : 'onKeyUp',
13438 keydown : 'onKeyDown',
13439 keypress : 'onKeyPress',
13440 submit : 'onSubmit',
13441 reset : 'onReset',
13442 contextmenu : 'onContextMenu',
13443 dblclick : 'onDblClick',
13444 paste : 'onPaste' // Doesn't work in all browsers yet
13445 };
13446
13447 function eventHandler(e, o) {
13448 var ty = e.type;
13449
13450 // Don't fire events when it's removed
13451 if (t.removed)
13452 return;
13453
13454 // Generic event handler
13455 if (t.onEvent.dispatch(t, e, o) !== false) {
13456 // Specific event handler
13457 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
13458 }
13459 };
13460
13461 // Add DOM events
13462 each(lo, function(v, k) {
13463 switch (k) {
13464 case 'contextmenu':
13465 dom.bind(t.getDoc(), k, eventHandler);
13466 break;
13467
13468 case 'paste':
13469 dom.bind(t.getBody(), k, function(e) {
13470 eventHandler(e);
13471 });
13472 break;
13473
13474 case 'submit':
13475 case 'reset':
13476 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
13477 break;
13478
13479 default:
13480 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
13481 }
13482 });
13483
13484 dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
13485 t.focus(true);
13486 });
13487
13488
13489 // Fixes bug where a specified document_base_uri could result in broken images
13490 // This will also fix drag drop of images in Gecko
13491 if (tinymce.isGecko) {
13492 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
13493 var v;
13494
13495 e = e.target;
13496
13497 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
13498 e.src = t.documentBaseURI.toAbsolute(v);
13499 });
13500 }
13501
13502 // Set various midas options in Gecko
13503 if (isGecko) {
13504 function setOpts() {
13505 var t = this, d = t.getDoc(), s = t.settings;
13506
13507 if (isGecko && !s.readonly) {
13508 t._refreshContentEditable();
13509
13510 try {
13511 // Try new Gecko method
13512 d.execCommand("styleWithCSS", 0, false);
13513 } catch (ex) {
13514 // Use old method
13515 if (!t._isHidden())
13516 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
13517 }
13518
13519 if (!s.table_inline_editing)
13520 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
13521
13522 if (!s.object_resizing)
13523 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
13524 }
13525 };
13526
13527 t.onBeforeExecCommand.add(setOpts);
13528 t.onMouseDown.add(setOpts);
13529 }
13530
13531 // Add node change handlers
13532 t.onMouseUp.add(t.nodeChanged);
13533 //t.onClick.add(t.nodeChanged);
13534 t.onKeyUp.add(function(ed, e) {
13535 var c = e.keyCode;
13536
13537 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)
13538 t.nodeChanged();
13539 });
13540
13541
13542 // Add block quote deletion handler
13543 t.onKeyDown.add(function(ed, e) {
13544 if (e.keyCode != VK.BACKSPACE)
13545 return;
13546
13547 var rng = ed.selection.getRng();
13548 if (!rng.collapsed)
13549 return;
13550
13551 var n = rng.startContainer;
13552 var offset = rng.startOffset;
13553
13554 while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
13555 n = n.parentNode;
13556
13557 // Is the cursor at the beginning of a blockquote?
13558 if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
13559 // Remove the blockquote
13560 ed.formatter.toggle('blockquote', null, n.parentNode);
13561
13562 // Move the caret to the beginning of n
13563 rng.setStart(n, 0);
13564 rng.setEnd(n, 0);
13565 ed.selection.setRng(rng);
13566 ed.selection.collapse(false);
13567 }
13568 });
13569
13570
13571
13572 // Add reset handler
13573 t.onReset.add(function() {
13574 t.setContent(t.startContent, {format : 'raw'});
13575 });
13576
13577 // Add shortcuts
13578 if (s.custom_shortcuts) {
13579 if (s.custom_undo_redo_keyboard_shortcuts) {
13580 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
13581 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
13582 }
13583
13584 // Add default shortcuts for gecko
13585 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
13586 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
13587 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
13588
13589 // BlockFormat shortcuts keys
13590 for (i=1; i<=6; i++)
13591 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
13592
13593 t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
13594 t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
13595 t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
13596
13597 function find(e) {
13598 var v = null;
13599
13600 if (!e.altKey && !e.ctrlKey && !e.metaKey)
13601 return v;
13602
13603 each(t.shortcuts, function(o) {
13604 if (tinymce.isMac && o.ctrl != e.metaKey)
13605 return;
13606 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
13607 return;
13608
13609 if (o.alt != e.altKey)
13610 return;
13611
13612 if (o.shift != e.shiftKey)
13613 return;
13614
13615 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
13616 v = o;
13617 return false;
13618 }
13619 });
13620
13621 return v;
13622 };
13623
13624 t.onKeyUp.add(function(ed, e) {
13625 var o = find(e);
13626
13627 if (o)
13628 return Event.cancel(e);
13629 });
13630
13631 t.onKeyPress.add(function(ed, e) {
13632 var o = find(e);
13633
13634 if (o)
13635 return Event.cancel(e);
13636 });
13637
13638 t.onKeyDown.add(function(ed, e) {
13639 var o = find(e);
13640
13641 if (o) {
13642 o.func.call(o.scope);
13643 return Event.cancel(e);
13644 }
13645 });
13646 }
13647
13648 if (tinymce.isIE) {
13649 // Fix so resize will only update the width and height attributes not the styles of an image
13650 // It will also block mceItemNoResize items
13651 dom.bind(t.getDoc(), 'controlselect', function(e) {
13652 var re = t.resizeInfo, cb;
13653
13654 e = e.target;
13655
13656 // Don't do this action for non image elements
13657 if (e.nodeName !== 'IMG')
13658 return;
13659
13660 if (re)
13661 dom.unbind(re.node, re.ev, re.cb);
13662
13663 if (!dom.hasClass(e, 'mceItemNoResize')) {
13664 ev = 'resizeend';
13665 cb = dom.bind(e, ev, function(e) {
13666 var v;
13667
13668 e = e.target;
13669
13670 if (v = dom.getStyle(e, 'width')) {
13671 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
13672 dom.setStyle(e, 'width', '');
13673 }
13674
13675 if (v = dom.getStyle(e, 'height')) {
13676 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
13677 dom.setStyle(e, 'height', '');
13678 }
13679 });
13680 } else {
13681 ev = 'resizestart';
13682 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
13683 }
13684
13685 re = t.resizeInfo = {
13686 node : e,
13687 ev : ev,
13688 cb : cb
13689 };
13690 });
13691 }
13692
13693 if (tinymce.isOpera) {
13694 t.onClick.add(function(ed, e) {
13695 Event.prevent(e);
13696 });
13697 }
13698
13699 // Add custom undo/redo handlers
13700 if (s.custom_undo_redo) {
13701 function addUndo() {
13702 t.undoManager.typing = false;
13703 t.undoManager.add();
13704 };
13705
13706 var focusLostFunc = tinymce.isGecko ? 'blur' : 'focusout';
13707 dom.bind(t.getDoc(), focusLostFunc, function(e){
13708 if (!t.removed && t.undoManager.typing)
13709 addUndo();
13710 });
13711
13712 // Add undo level when contents is drag/dropped within the editor
13713 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
13714 addUndo();
13715 });
13716
13717 t.onKeyUp.add(function(ed, e) {
13718 var keyCode = e.keyCode;
13719
13720 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
13721 addUndo();
13722 });
13723
13724 t.onKeyDown.add(function(ed, e) {
13725 var keyCode = e.keyCode, sel;
13726
13727 if (keyCode == 8) {
13728 sel = t.getDoc().selection;
13729
13730 // Fix IE control + backspace browser bug
13731 if (sel && sel.createRange && sel.createRange().item) {
13732 t.undoManager.beforeChange();
13733 ed.dom.remove(sel.createRange().item(0));
13734 addUndo();
13735
13736 return Event.cancel(e);
13737 }
13738 }
13739
13740 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
13741 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
13742 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
13743 // Todo: Remove this once we normalize enter behavior on IE
13744 if (tinymce.isIE && keyCode == 13)
13745 t.undoManager.beforeChange();
13746
13747 if (t.undoManager.typing)
13748 addUndo();
13749
13750 return;
13751 }
13752
13753 // If key isn't shift,ctrl,alt,capslock,metakey
13754 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
13755 t.undoManager.beforeChange();
13756 t.undoManager.typing = true;
13757 t.undoManager.add();
13758 }
13759 });
13760
13761 t.onMouseDown.add(function() {
13762 if (t.undoManager.typing)
13763 addUndo();
13764 });
13765 }
13766 },
13767
13768 _refreshContentEditable : function() {
13769 var self = this, body, parent;
13770
13771 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
13772 if (self._isHidden()) {
13773 body = self.getBody();
13774 parent = body.parentNode;
13775
13776 parent.removeChild(body);
13777 parent.appendChild(body);
13778
13779 body.focus();
13780 }
13781 },
13782
13783 _isHidden : function() {
13784 var s;
13785
13786 if (!isGecko)
13787 return 0;
13788
13789 // Weird, wheres that cursor selection?
13790 s = this.selection.getSel();
13791 return (!s || !s.rangeCount || s.rangeCount == 0);
13792 }
13793 });
13794 })(tinymce);
13795
13796 (function(tinymce) {
13797 // Added for compression purposes
13798 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
13799
13800 tinymce.EditorCommands = function(editor) {
13801 var dom = editor.dom,
13802 selection = editor.selection,
13803 commands = {state: {}, exec : {}, value : {}},
13804 settings = editor.settings,
13805 formatter = editor.formatter,
13806 bookmark;
13807
13808 function execCommand(command, ui, value) {
13809 var func;
13810
13811 command = command.toLowerCase();
13812 if (func = commands.exec[command]) {
13813 func(command, ui, value);
13814 return TRUE;
13815 }
13816
13817 return FALSE;
13818 };
13819
13820 function queryCommandState(command) {
13821 var func;
13822
13823 command = command.toLowerCase();
13824 if (func = commands.state[command])
13825 return func(command);
13826
13827 return -1;
13828 };
13829
13830 function queryCommandValue(command) {
13831 var func;
13832
13833 command = command.toLowerCase();
13834 if (func = commands.value[command])
13835 return func(command);
13836
13837 return FALSE;
13838 };
13839
13840 function addCommands(command_list, type) {
13841 type = type || 'exec';
13842
13843 each(command_list, function(callback, command) {
13844 each(command.toLowerCase().split(','), function(command) {
13845 commands[type][command] = callback;
13846 });
13847 });
13848 };
13849
13850 // Expose public methods
13851 tinymce.extend(this, {
13852 execCommand : execCommand,
13853 queryCommandState : queryCommandState,
13854 queryCommandValue : queryCommandValue,
13855 addCommands : addCommands
13856 });
13857
13858 // Private methods
13859
13860 function execNativeCommand(command, ui, value) {
13861 if (ui === undefined)
13862 ui = FALSE;
13863
13864 if (value === undefined)
13865 value = null;
13866
13867 return editor.getDoc().execCommand(command, ui, value);
13868 };
13869
13870 function isFormatMatch(name) {
13871 return formatter.match(name);
13872 };
13873
13874 function toggleFormat(name, value) {
13875 formatter.toggle(name, value ? {value : value} : undefined);
13876 };
13877
13878 function storeSelection(type) {
13879 bookmark = selection.getBookmark(type);
13880 };
13881
13882 function restoreSelection() {
13883 selection.moveToBookmark(bookmark);
13884 };
13885
13886 // Add execCommand overrides
13887 addCommands({
13888 // Ignore these, added for compatibility
13889 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
13890
13891 // Add undo manager logic
13892 'mceEndUndoLevel,mceAddUndoLevel' : function() {
13893 editor.undoManager.add();
13894 },
13895
13896 'Cut,Copy,Paste' : function(command) {
13897 var doc = editor.getDoc(), failed;
13898
13899 // Try executing the native command
13900 try {
13901 execNativeCommand(command);
13902 } catch (ex) {
13903 // Command failed
13904 failed = TRUE;
13905 }
13906
13907 // Present alert message about clipboard access not being available
13908 if (failed || !doc.queryCommandSupported(command)) {
13909 if (tinymce.isGecko) {
13910 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
13911 if (state)
13912 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
13913 });
13914 } else
13915 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
13916 }
13917 },
13918
13919 // Override unlink command
13920 unlink : function(command) {
13921 if (selection.isCollapsed())
13922 selection.select(selection.getNode());
13923
13924 execNativeCommand(command);
13925 selection.collapse(FALSE);
13926 },
13927
13928 // Override justify commands to use the text formatter engine
13929 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13930 var align = command.substring(7);
13931
13932 // Remove all other alignments first
13933 each('left,center,right,full'.split(','), function(name) {
13934 if (align != name)
13935 formatter.remove('align' + name);
13936 });
13937
13938 toggleFormat('align' + align);
13939 execCommand('mceRepaint');
13940 },
13941
13942 // Override list commands to fix WebKit bug
13943 'InsertUnorderedList,InsertOrderedList' : function(command) {
13944 var listElm, listParent;
13945
13946 execNativeCommand(command);
13947
13948 // WebKit produces lists within block elements so we need to split them
13949 // we will replace the native list creation logic to custom logic later on
13950 // TODO: Remove this when the list creation logic is removed
13951 listElm = dom.getParent(selection.getNode(), 'ol,ul');
13952 if (listElm) {
13953 listParent = listElm.parentNode;
13954
13955 // If list is within a text block then split that block
13956 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
13957 storeSelection();
13958 dom.split(listParent, listElm);
13959 restoreSelection();
13960 }
13961 }
13962 },
13963
13964 // Override commands to use the text formatter engine
13965 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13966 toggleFormat(command);
13967 },
13968
13969 // Override commands to use the text formatter engine
13970 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
13971 toggleFormat(command, value);
13972 },
13973
13974 FontSize : function(command, ui, value) {
13975 var fontClasses, fontSizes;
13976
13977 // Convert font size 1-7 to styles
13978 if (value >= 1 && value <= 7) {
13979 fontSizes = tinymce.explode(settings.font_size_style_values);
13980 fontClasses = tinymce.explode(settings.font_size_classes);
13981
13982 if (fontClasses)
13983 value = fontClasses[value - 1] || value;
13984 else
13985 value = fontSizes[value - 1] || value;
13986 }
13987
13988 toggleFormat(command, value);
13989 },
13990
13991 RemoveFormat : function(command) {
13992 formatter.remove(command);
13993 },
13994
13995 mceBlockQuote : function(command) {
13996 toggleFormat('blockquote');
13997 },
13998
13999 FormatBlock : function(command, ui, value) {
14000 return toggleFormat(value || 'p');
14001 },
14002
14003 mceCleanup : function() {
14004 var bookmark = selection.getBookmark();
14005
14006 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
14007
14008 selection.moveToBookmark(bookmark);
14009 },
14010
14011 mceRemoveNode : function(command, ui, value) {
14012 var node = value || selection.getNode();
14013
14014 // Make sure that the body node isn't removed
14015 if (node != editor.getBody()) {
14016 storeSelection();
14017 editor.dom.remove(node, TRUE);
14018 restoreSelection();
14019 }
14020 },
14021
14022 mceSelectNodeDepth : function(command, ui, value) {
14023 var counter = 0;
14024
14025 dom.getParent(selection.getNode(), function(node) {
14026 if (node.nodeType == 1 && counter++ == value) {
14027 selection.select(node);
14028 return FALSE;
14029 }
14030 }, editor.getBody());
14031 },
14032
14033 mceSelectNode : function(command, ui, value) {
14034 selection.select(value);
14035 },
14036
14037 mceInsertContent : function(command, ui, value) {
14038 var parser, serializer, parentNode, rootNode, fragment, args,
14039 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
14040
14041 //selection.normalize();
14042
14043 // Setup parser and serializer
14044 parser = editor.parser;
14045 serializer = new tinymce.html.Serializer({}, editor.schema);
14046 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
14047
14048 // Run beforeSetContent handlers on the HTML to be inserted
14049 args = {content: value, format: 'html'};
14050 selection.onBeforeSetContent.dispatch(selection, args);
14051 value = args.content;
14052
14053 // Add caret at end of contents if it's missing
14054 if (value.indexOf('{$caret}') == -1)
14055 value += '{$caret}';
14056
14057 // Replace the caret marker with a span bookmark element
14058 value = value.replace(/\{\$caret\}/, bookmarkHtml);
14059
14060 // Insert node maker where we will insert the new HTML and get it's parent
14061 if (!selection.isCollapsed())
14062 editor.getDoc().execCommand('Delete', false, null);
14063
14064 parentNode = selection.getNode();
14065
14066 // Parse the fragment within the context of the parent node
14067 args = {context : parentNode.nodeName.toLowerCase()};
14068 fragment = parser.parse(value, args);
14069
14070 // Move the caret to a more suitable location
14071 node = fragment.lastChild;
14072 if (node.attr('id') == 'mce_marker') {
14073 marker = node;
14074
14075 for (node = node.prev; node; node = node.walk(true)) {
14076 if (node.type == 3 || !dom.isBlock(node.name)) {
14077 node.parent.insert(marker, node, node.name === 'br');
14078 break;
14079 }
14080 }
14081 }
14082
14083 // If parser says valid we can insert the contents into that parent
14084 if (!args.invalid) {
14085 value = serializer.serialize(fragment);
14086
14087 // Check if parent is empty or only has one BR element then set the innerHTML of that parent
14088 node = parentNode.firstChild;
14089 node2 = parentNode.lastChild;
14090 if (!node || (node === node2 && node.nodeName === 'BR'))
14091 dom.setHTML(parentNode, value);
14092 else
14093 selection.setContent(value);
14094 } else {
14095 // If the fragment was invalid within that context then we need
14096 // to parse and process the parent it's inserted into
14097
14098 // Insert bookmark node and get the parent
14099 selection.setContent(bookmarkHtml);
14100 parentNode = editor.selection.getNode();
14101 rootNode = editor.getBody();
14102
14103 // Opera will return the document node when selection is in root
14104 if (parentNode.nodeType == 9)
14105 parentNode = node = rootNode;
14106 else
14107 node = parentNode;
14108
14109 // Find the ancestor just before the root element
14110 while (node !== rootNode) {
14111 parentNode = node;
14112 node = node.parentNode;
14113 }
14114
14115 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
14116 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
14117 value = serializer.serialize(
14118 parser.parse(
14119 // Need to replace by using a function since $ in the contents would otherwise be a problem
14120 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
14121 return serializer.serialize(fragment);
14122 })
14123 )
14124 );
14125
14126 // Set the inner/outer HTML depending on if we are in the root or not
14127 if (parentNode == rootNode)
14128 dom.setHTML(rootNode, value);
14129 else
14130 dom.setOuterHTML(parentNode, value);
14131 }
14132
14133 marker = dom.get('mce_marker');
14134
14135 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
14136 nodeRect = dom.getRect(marker);
14137 viewPortRect = dom.getViewPort(editor.getWin());
14138
14139 // Check if node is out side the viewport if it is then scroll to it
14140 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
14141 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
14142 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
14143 viewportBodyElement.scrollLeft = nodeRect.x;
14144 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
14145 }
14146
14147 // Move selection before marker and remove it
14148 rng = dom.createRng();
14149
14150 // If previous sibling is a text node set the selection to the end of that node
14151 node = marker.previousSibling;
14152 if (node && node.nodeType == 3) {
14153 rng.setStart(node, node.nodeValue.length);
14154 } else {
14155 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
14156 rng.setStartBefore(marker);
14157 rng.setEndBefore(marker);
14158 }
14159
14160 // Remove the marker node and set the new range
14161 dom.remove(marker);
14162 selection.setRng(rng);
14163
14164 // Dispatch after event and add any visual elements needed
14165 selection.onSetContent.dispatch(selection, args);
14166 editor.addVisual();
14167 },
14168
14169 mceInsertRawHTML : function(command, ui, value) {
14170 selection.setContent('tiny_mce_marker');
14171 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
14172 },
14173
14174 mceSetContent : function(command, ui, value) {
14175 editor.setContent(value);
14176 },
14177
14178 'Indent,Outdent' : function(command) {
14179 var intentValue, indentUnit, value;
14180
14181 // Setup indent level
14182 intentValue = settings.indentation;
14183 indentUnit = /[a-z%]+$/i.exec(intentValue);
14184 intentValue = parseInt(intentValue);
14185
14186 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
14187 each(selection.getSelectedBlocks(), function(element) {
14188 if (command == 'outdent') {
14189 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
14190 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
14191 } else
14192 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
14193 });
14194 } else
14195 execNativeCommand(command);
14196 },
14197
14198 mceRepaint : function() {
14199 var bookmark;
14200
14201 if (tinymce.isGecko) {
14202 try {
14203 storeSelection(TRUE);
14204
14205 if (selection.getSel())
14206 selection.getSel().selectAllChildren(editor.getBody());
14207
14208 selection.collapse(TRUE);
14209 restoreSelection();
14210 } catch (ex) {
14211 // Ignore
14212 }
14213 }
14214 },
14215
14216 mceToggleFormat : function(command, ui, value) {
14217 formatter.toggle(value);
14218 },
14219
14220 InsertHorizontalRule : function() {
14221 editor.execCommand('mceInsertContent', false, '<hr />');
14222 },
14223
14224 mceToggleVisualAid : function() {
14225 editor.hasVisual = !editor.hasVisual;
14226 editor.addVisual();
14227 },
14228
14229 mceReplaceContent : function(command, ui, value) {
14230 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
14231 },
14232
14233 mceInsertLink : function(command, ui, value) {
14234 var anchor;
14235
14236 if (typeof(value) == 'string')
14237 value = {href : value};
14238
14239 anchor = dom.getParent(selection.getNode(), 'a');
14240
14241 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
14242 value.href = value.href.replace(' ', '%20');
14243
14244 // Remove existing links if there could be child links or that the href isn't specified
14245 if (!anchor || !value.href) {
14246 formatter.remove('link');
14247 }
14248
14249 // Apply new link to selection
14250 if (value.href) {
14251 formatter.apply('link', value, anchor);
14252 }
14253 },
14254
14255 selectAll : function() {
14256 var root = dom.getRoot(), rng = dom.createRng();
14257
14258 rng.setStart(root, 0);
14259 rng.setEnd(root, root.childNodes.length);
14260
14261 editor.selection.setRng(rng);
14262 }
14263 });
14264
14265 // Add queryCommandState overrides
14266 addCommands({
14267 // Override justify commands
14268 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14269 var name = 'align' + command.substring(7);
14270 // Use Formatter.matchNode instead of Formatter.match so that we don't match on parent node. This fixes bug where for both left
14271 // and right align buttons can be active. This could occur when selected nodes have align right and the parent has align left.
14272 var nodes = selection.isCollapsed() ? [selection.getNode()] : selection.getSelectedBlocks();
14273 var matches = tinymce.map(nodes, function(node) {
14274 return !!formatter.matchNode(node, name);
14275 });
14276 return tinymce.inArray(matches, TRUE) !== -1;
14277 },
14278
14279 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
14280 return isFormatMatch(command);
14281 },
14282
14283 mceBlockQuote : function() {
14284 return isFormatMatch('blockquote');
14285 },
14286
14287 Outdent : function() {
14288 var node;
14289
14290 if (settings.inline_styles) {
14291 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
14292 return TRUE;
14293
14294 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
14295 return TRUE;
14296 }
14297
14298 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
14299 },
14300
14301 'InsertUnorderedList,InsertOrderedList' : function(command) {
14302 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
14303 }
14304 }, 'state');
14305
14306 // Add queryCommandValue overrides
14307 addCommands({
14308 'FontSize,FontName' : function(command) {
14309 var value = 0, parent;
14310
14311 if (parent = dom.getParent(selection.getNode(), 'span')) {
14312 if (command == 'fontsize')
14313 value = parent.style.fontSize;
14314 else
14315 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
14316 }
14317
14318 return value;
14319 }
14320 }, 'value');
14321
14322 // Add undo manager logic
14323 if (settings.custom_undo_redo) {
14324 addCommands({
14325 Undo : function() {
14326 editor.undoManager.undo();
14327 },
14328
14329 Redo : function() {
14330 editor.undoManager.redo();
14331 }
14332 });
14333 }
14334 };
14335 })(tinymce);
14336
14337 (function(tinymce) {
14338 var Dispatcher = tinymce.util.Dispatcher;
14339
14340 tinymce.UndoManager = function(editor) {
14341 var self, index = 0, data = [], beforeBookmark;
14342
14343 function getContent() {
14344 // Remove whitespace before/after and remove pure bogus nodes
14345 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
14346 };
14347
14348 return self = {
14349 typing : false,
14350
14351 onAdd : new Dispatcher(self),
14352
14353 onUndo : new Dispatcher(self),
14354
14355 onRedo : new Dispatcher(self),
14356
14357 beforeChange : function() {
14358 beforeBookmark = editor.selection.getBookmark(2, true);
14359 },
14360
14361 add : function(level) {
14362 var i, settings = editor.settings, lastLevel;
14363
14364 level = level || {};
14365 level.content = getContent();
14366
14367 // Add undo level if needed
14368 lastLevel = data[index];
14369 if (lastLevel && lastLevel.content == level.content)
14370 return null;
14371
14372 // Set before bookmark on previous level
14373 if (data[index])
14374 data[index].beforeBookmark = beforeBookmark;
14375
14376 // Time to compress
14377 if (settings.custom_undo_redo_levels) {
14378 if (data.length > settings.custom_undo_redo_levels) {
14379 for (i = 0; i < data.length - 1; i++)
14380 data[i] = data[i + 1];
14381
14382 data.length--;
14383 index = data.length;
14384 }
14385 }
14386
14387 // Get a non intrusive normalized bookmark
14388 level.bookmark = editor.selection.getBookmark(2, true);
14389
14390 // Crop array if needed
14391 if (index < data.length - 1)
14392 data.length = index + 1;
14393
14394 data.push(level);
14395 index = data.length - 1;
14396
14397 self.onAdd.dispatch(self, level);
14398 editor.isNotDirty = 0;
14399
14400 return level;
14401 },
14402
14403 undo : function() {
14404 var level, i;
14405
14406 if (self.typing) {
14407 self.add();
14408 self.typing = false;
14409 }
14410
14411 if (index > 0) {
14412 level = data[--index];
14413
14414 editor.setContent(level.content, {format : 'raw'});
14415 editor.selection.moveToBookmark(level.beforeBookmark);
14416
14417 self.onUndo.dispatch(self, level);
14418 }
14419
14420 return level;
14421 },
14422
14423 redo : function() {
14424 var level;
14425
14426 if (index < data.length - 1) {
14427 level = data[++index];
14428
14429 editor.setContent(level.content, {format : 'raw'});
14430 editor.selection.moveToBookmark(level.bookmark);
14431
14432 self.onRedo.dispatch(self, level);
14433 }
14434
14435 return level;
14436 },
14437
14438 clear : function() {
14439 data = [];
14440 index = 0;
14441 self.typing = false;
14442 },
14443
14444 hasUndo : function() {
14445 return index > 0 || this.typing;
14446 },
14447
14448 hasRedo : function() {
14449 return index < data.length - 1 && !this.typing;
14450 }
14451 };
14452 };
14453 })(tinymce);
14454
14455 (function(tinymce) {
14456 // Shorten names
14457 var Event = tinymce.dom.Event,
14458 isIE = tinymce.isIE,
14459 isGecko = tinymce.isGecko,
14460 isOpera = tinymce.isOpera,
14461 each = tinymce.each,
14462 extend = tinymce.extend,
14463 TRUE = true,
14464 FALSE = false;
14465
14466 function splitList(selection, dom, li) {
14467 var listBlock, block;
14468
14469 // TODO: Fix so this doesn't use native IE logic
14470 if (dom.isEmpty(li) && !isIE) {
14471 listBlock = dom.getParent(li, 'ul,ol');
14472
14473 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
14474 dom.split(listBlock, li);
14475 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
14476 dom.replace(block, li);
14477 selection.select(block, 1);
14478 }
14479
14480 return FALSE;
14481 }
14482
14483 return TRUE;
14484 };
14485
14486 tinymce.create('tinymce.ForceBlocks', {
14487 ForceBlocks : function(ed) {
14488 var t = this, s = ed.settings, elm;
14489
14490 t.editor = ed;
14491 t.dom = ed.dom;
14492 elm = (s.forced_root_block || 'p').toLowerCase();
14493 s.element = elm.toUpperCase();
14494
14495 ed.onPreInit.add(t.setup, t);
14496 },
14497
14498 setup : function() {
14499 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
14500
14501 // Force root blocks
14502 if (s.forced_root_block) {
14503 function addRootBlocks() {
14504 var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
14505
14506 if (!node || node.nodeType !== 1)
14507 return;
14508
14509 // Check if node is wrapped in block
14510 while (node != rootNode) {
14511 if (blockElements[node.nodeName])
14512 return;
14513
14514 node = node.parentNode;
14515 }
14516
14517 // Get current selection
14518 rng = selection.getRng();
14519 if (rng.setStart) {
14520 startContainer = rng.startContainer;
14521 startOffset = rng.startOffset;
14522 endContainer = rng.endContainer;
14523 endOffset = rng.endOffset;
14524 } else {
14525 // Force control range into text range
14526 if (rng.item) {
14527 rng = ed.getDoc().body.createTextRange();
14528 rng.moveToElementText(rng.item(0));
14529 }
14530
14531 tmpRng = rng.duplicate();
14532 tmpRng.collapse(true);
14533 startOffset = tmpRng.move('character', offset) * -1;
14534
14535 if (!tmpRng.collapsed) {
14536 tmpRng = rng.duplicate();
14537 tmpRng.collapse(false);
14538 endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
14539 }
14540 }
14541
14542 // Wrap non block elements and text nodes
14543 for (node = rootNode.firstChild; node; node) {
14544 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
14545 if (!rootBlockNode) {
14546 rootBlockNode = dom.create(s.forced_root_block);
14547 node.parentNode.insertBefore(rootBlockNode, node);
14548 }
14549
14550 tempNode = node;
14551 node = node.nextSibling;
14552 rootBlockNode.appendChild(tempNode);
14553 } else {
14554 rootBlockNode = null;
14555 node = node.nextSibling;
14556 }
14557 }
14558
14559 if (rng.setStart) {
14560 rng.setStart(startContainer, startOffset);
14561 rng.setEnd(endContainer, endOffset);
14562 selection.setRng(rng);
14563 } else {
14564 try {
14565 rng = ed.getDoc().body.createTextRange();
14566 rng.moveToElementText(rootNode);
14567 rng.collapse(true);
14568 rng.moveStart('character', startOffset);
14569
14570 if (endOffset > 0)
14571 rng.moveEnd('character', endOffset);
14572
14573 rng.select();
14574 } catch (ex) {
14575 // Ignore
14576 }
14577 }
14578
14579 ed.nodeChanged();
14580 };
14581
14582 ed.onKeyUp.add(addRootBlocks);
14583 ed.onClick.add(addRootBlocks);
14584 }
14585
14586 if (s.force_br_newlines) {
14587 // Force IE to produce BRs on enter
14588 if (isIE) {
14589 ed.onKeyPress.add(function(ed, e) {
14590 var n;
14591
14592 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
14593 selection.setContent('<br id="__" /> ', {format : 'raw'});
14594 n = dom.get('__');
14595 n.removeAttribute('id');
14596 selection.select(n);
14597 selection.collapse();
14598 return Event.cancel(e);
14599 }
14600 });
14601 }
14602 }
14603
14604 if (s.force_p_newlines) {
14605 ed.onKeyPress.add(function(ed, e) {
14606 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
14607 Event.cancel(e);
14608 });
14609
14610 if (isGecko) {
14611 ed.onKeyDown.add(function(ed, e) {
14612 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
14613 t.backspaceDelete(e, e.keyCode == 8);
14614 });
14615 }
14616 }
14617
14618 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
14619 if (tinymce.isWebKit) {
14620 function insertBr(ed) {
14621 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
14622
14623 // Insert BR element
14624 rng.insertNode(br = dom.create('br'));
14625
14626 // Place caret after BR
14627 rng.setStartAfter(br);
14628 rng.setEndAfter(br);
14629 selection.setRng(rng);
14630
14631 // Could not place caret after BR then insert an nbsp entity and move the caret
14632 if (selection.getSel().focusNode == br.previousSibling) {
14633 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
14634 selection.collapse(TRUE);
14635 }
14636
14637 // Create a temporary DIV after the BR and get the position as it
14638 // seems like getPos() returns 0 for text nodes and BR elements.
14639 dom.insertAfter(div, br);
14640 divYPos = dom.getPos(div).y;
14641 dom.remove(div);
14642
14643 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
14644 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
14645 ed.getWin().scrollTo(0, divYPos);
14646 };
14647
14648 ed.onKeyPress.add(function(ed, e) {
14649 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
14650 insertBr(ed);
14651 Event.cancel(e);
14652 }
14653 });
14654 }
14655
14656 // IE specific fixes
14657 if (isIE) {
14658 // Replaces IE:s auto generated paragraphs with the specified element name
14659 if (s.element != 'P') {
14660 ed.onKeyPress.add(function(ed, e) {
14661 t.lastElm = selection.getNode().nodeName;
14662 });
14663
14664 ed.onKeyUp.add(function(ed, e) {
14665 var bl, n = selection.getNode(), b = ed.getBody();
14666
14667 if (b.childNodes.length === 1 && n.nodeName == 'P') {
14668 n = dom.rename(n, s.element);
14669 selection.select(n);
14670 selection.collapse();
14671 ed.nodeChanged();
14672 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
14673 bl = dom.getParent(n, 'p');
14674
14675 if (bl) {
14676 dom.rename(bl, s.element);
14677 ed.nodeChanged();
14678 }
14679 }
14680 });
14681 }
14682 }
14683 },
14684
14685 getParentBlock : function(n) {
14686 var d = this.dom;
14687
14688 return d.getParent(n, d.isBlock);
14689 },
14690
14691 insertPara : function(e) {
14692 var t = this, ed = t.editor, dom = ed.dom, selection = ed.selection, d = ed.getDoc(), se = ed.settings, s = selection.getSel(), r = selection.getRng(true), b = d.body;
14693 var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, ch, containerBlock, beforeCaretNode, afterCaretNode;
14694
14695 // Checks if the selection/caret is at the end of the specified block element
14696 function isAtEnd(rng, par) {
14697 var rng2 = rng.cloneRange();
14698
14699 rng2.setStart(rng.endContainer, rng.endOffset);
14700 rng2.setEndAfter(par);
14701
14702 // 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
14703 return dom.isEmpty(rng2.cloneContents());
14704 };
14705
14706 function moveToCaretPosition(root, scroll) {
14707 var walker, node, rng, y, vp;
14708
14709 rng = dom.createRng();
14710
14711 if (root.hasChildNodes()) {
14712 walker = new tinymce.dom.TreeWalker(root, root);
14713
14714 while (node = walker.current()) {
14715 if (node.nodeType == 3) {
14716 rng.setStart(node, 0);
14717 rng.setEnd(node, 0);
14718 break;
14719 }
14720
14721 if (/^(BR|IMG)$/.test(node.nodeName)) {
14722 rng.setStartBefore(node);
14723 rng.setEndBefore(node);
14724 break;
14725 }
14726
14727 node = walker.next();
14728 }
14729 } else {
14730 rng.setStart(root, 0);
14731 rng.setEnd(root, 0);
14732 }
14733
14734 selection.setRng(rng);
14735
14736 if (scroll !== false) {
14737 vp = dom.getViewPort(ed.getWin());
14738
14739 // 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
14740 y = dom.getPos(root).y;
14741
14742 // Is element within viewport
14743 if (y < vp.y || y + 25 > vp.y + vp.h) {
14744 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
14745
14746 /*console.debug(
14747 'Element: y=' + y + ', h=' + ch + ', ' +
14748 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
14749 );*/
14750 }
14751 }
14752 };
14753
14754 function clean(node) {
14755 if (node.nodeType == 3 && node.nodeValue.length == 0) {
14756 dom.remove(node);
14757 }
14758
14759 if (node.hasChildNodes()) {
14760 for (var i = 0; i < node.childNodes.length; i++) {
14761 clean(node.childNodes[i]);
14762 }
14763 }
14764 };
14765
14766 ed.undoManager.beforeChange();
14767
14768 // If root blocks are forced then use Operas default behavior since it's really good
14769 // Removed due to bug: #1853816
14770 // if (se.forced_root_block && isOpera)
14771 // return TRUE;
14772
14773 // Handle selection direction for browsers that support it
14774 if (typeof s.anchorNode != "undefined") {
14775 // Setup before range
14776 rb = dom.createRng();
14777
14778 // If is before the first block element and in body, then move it into first block element
14779 rb.setStart(s.anchorNode, s.anchorOffset);
14780 rb.collapse(TRUE);
14781
14782 // Setup after range
14783 ra = dom.createRng();
14784
14785 // If is before the first block element and in body, then move it into first block element
14786 ra.setStart(s.focusNode, s.focusOffset);
14787 ra.collapse(TRUE);
14788
14789 // Setup start/end points
14790 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
14791 sn = dir ? s.anchorNode : s.focusNode;
14792 so = dir ? s.anchorOffset : s.focusOffset;
14793 en = dir ? s.focusNode : s.anchorNode;
14794 eo = dir ? s.focusOffset : s.anchorOffset;
14795 } else {
14796 rb = r.cloneRange();
14797 rb.collapse(TRUE);
14798
14799 ra = r.cloneRange();
14800 ra.collapse(FALSE);
14801
14802 sn = r.startContainer;
14803 so = r.startOffset;
14804 en = r.endContainer;
14805 eo = r.endOffset;
14806 }
14807
14808 // If selection is in empty table cell
14809 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
14810 sn.innerHTML = '';
14811
14812 // Create two new block elements
14813 dom.add(sn, se.element, null, isIE ? '' : '<br />');
14814 aft = dom.add(sn, se.element, null, isIE ? '' : '<br />');
14815 moveToCaretPosition(aft);
14816
14817 return FALSE;
14818 }
14819
14820 // If the caret is in an invalid location in FF we need to move it into the first block
14821 if (sn == b && en == b && b.firstChild && dom.isBlock(b.firstChild)) {
14822 sn = en = sn.firstChild;
14823 so = eo = 0;
14824 rb = dom.createRng();
14825 rb.setStart(sn, 0);
14826 ra = dom.createRng();
14827 ra.setStart(en, 0);
14828 }
14829
14830 // If the body is totally empty add a BR element this might happen on webkit
14831 if (!b.hasChildNodes()) {
14832 b.appendChild(dom.create('br'));
14833 }
14834
14835 // Never use body as start or end node
14836 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
14837 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
14838 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
14839 en = en.nodeName == "BODY" ? en.firstChild : en;
14840
14841 // Get start and end blocks
14842 sb = t.getParentBlock(sn);
14843 eb = t.getParentBlock(en);
14844 bn = sb ? sb.nodeName : se.element; // Get block name to create
14845
14846 // Break container blocks on enter in empty block element (experimental feature)
14847 if (ed.settings.end_container_on_empty_block) {
14848 containerBlock = dom.getParent(sb, 'hgroup,blockquote,section,article');
14849 if (containerBlock && (dom.isEmpty(sb) || (sb.firstChild === sb.lastChild && (!sb.firstChild || sb.firstChild.nodeName == 'BR')))) {
14850 dom.split(containerBlock, sb);
14851 selection.select(sb, true);
14852 selection.collapse(true);
14853 return;
14854 }
14855 }
14856
14857 // Return inside list use default browser behavior
14858 if (n = t.dom.getParent(sb, 'li')) {
14859 if (n.nodeName == 'LI')
14860 return splitList(selection, t.dom, n);
14861
14862 return TRUE;
14863 }
14864
14865 // If caption or absolute layers then always generate new blocks within
14866 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
14867 bn = se.element;
14868 sb = null;
14869 }
14870
14871 // If caption or absolute layers then always generate new blocks within
14872 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
14873 bn = se.element;
14874 eb = null;
14875 }
14876
14877 // Use P instead
14878 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
14879 bn = se.element;
14880 sb = eb = null;
14881 }
14882
14883 // Setup new before and after blocks
14884 bef = (sb && sb.nodeName == bn) ? dom.clone(sb, false) : dom.create(bn);
14885 aft = (eb && eb.nodeName == bn) ? dom.clone(eb, false) : dom.create(bn);
14886
14887 // Remove id from after clone
14888 aft.removeAttribute('id');
14889
14890 // Is header and cursor is at the end, then force paragraph under
14891 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb, dom)) {
14892 aft = dom.create(se.element);
14893
14894 // Use header name as copy if we are in a hgroup
14895 if (t.dom.getParent(sb, 'hgroup')) {
14896 aft = dom.create(bn);
14897 }
14898 }
14899
14900 // Find start chop node
14901 n = sc = sn;
14902 do {
14903 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
14904 break;
14905
14906 sc = n;
14907 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
14908
14909 // Find end chop node
14910 n = ec = en;
14911 do {
14912 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
14913 break;
14914
14915 ec = n;
14916 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
14917
14918 // Place first chop part into before block element
14919 if (sc.nodeName == bn)
14920 rb.setStart(sc, 0);
14921 else
14922 rb.setStartBefore(sc);
14923
14924 rb.setEnd(sn, so);
14925 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
14926
14927 // Place secnd chop part within new block element
14928 try {
14929 ra.setEndAfter(ec);
14930 } catch(ex) {
14931 //console.debug(s.focusNode, s.focusOffset);
14932 }
14933
14934 ra.setStart(en, eo);
14935 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
14936
14937 // Create range around everything
14938 r = dom.createRng();
14939 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
14940 r.setStartBefore(sc.parentNode);
14941 } else {
14942 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
14943 r.setStartBefore(rb.startContainer);
14944 else
14945 r.setStart(rb.startContainer, rb.startOffset);
14946 }
14947
14948 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
14949 r.setEndAfter(ec.parentNode);
14950 else
14951 r.setEnd(ra.endContainer, ra.endOffset);
14952
14953 if (!isIE || sb != eb || (sb && !dom.isEmpty(sb))) {
14954 r.deleteContents();
14955 }
14956
14957 // Remove range end point if it's empty
14958 n = r.startContainer.childNodes[r.startOffset];
14959 if (n && !n.hasChildNodes()) {
14960 dom.remove(n);
14961 }
14962
14963 // Never wrap blocks in blocks
14964 if (bef.firstChild && bef.firstChild.nodeName == bn)
14965 bef.innerHTML = bef.firstChild.innerHTML;
14966
14967 if (aft.firstChild && aft.firstChild.nodeName == bn)
14968 aft.innerHTML = aft.firstChild.innerHTML;
14969
14970 function appendStyles(e, en) {
14971 var nl = [], nn, n, i, padd;
14972
14973 // Use BR on W3C browsers and empty string on IE
14974 padd = isIE ? '' : '<br />';
14975
14976 // Needs to be removed using removeChild since innerHTML adds &nbsp; on IE
14977 while (e.firstChild) {
14978 e.removeChild(e.firstChild);
14979 }
14980
14981 // Make clones of style elements
14982 if (se.keep_styles) {
14983 n = en;
14984 do {
14985 // We only want style specific elements
14986 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
14987 nn = n.cloneNode(FALSE);
14988 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
14989 nl.push(nn);
14990 }
14991 } while (n = n.parentNode);
14992 }
14993
14994 // Append style elements to aft
14995 if (nl.length > 0) {
14996 for (i = nl.length - 1, nn = e; i >= 0; i--)
14997 nn = nn.appendChild(nl[i]);
14998
14999 // Padd most inner style element
15000 nl[0].innerHTML = padd;
15001 return nl[0]; // Move caret to most inner element
15002 } else
15003 e.innerHTML = padd;
15004
15005 return e;
15006 };
15007
15008 // IE gets messed up if the elements has empty text nodes in them this might
15009 // happen when the range spits at the end/beginning of a text node.
15010 // @todo: The acutal bug should be fixed in the Range.js file this is a hot patch
15011 if (isIE) {
15012 clean(bef);
15013 clean(aft);
15014 }
15015
15016 // Padd empty blocks
15017 if (dom.isEmpty(bef)) {
15018 beforeCaretNode = appendStyles(bef, sn);
15019 }
15020
15021 // Fill empty afterblook with current style
15022 if (dom.isEmpty(aft))
15023 afterCaretNode = appendStyles(aft, en);
15024
15025 // Opera needs this one backwards for older versions
15026 if (isOpera && parseFloat(opera.version()) < 9.5) {
15027 r.insertNode(bef);
15028 r.insertNode(aft);
15029 } else {
15030 r.insertNode(aft);
15031 r.insertNode(bef);
15032 }
15033
15034 // IE doesn't render empty block elements unless you poke it with a selection
15035 // So we need to detect old IE and then move the caret into that block to "render it"
15036 if (selection.tridentSel && beforeCaretNode) {
15037 moveToCaretPosition(beforeCaretNode, false);
15038 }
15039
15040 moveToCaretPosition(afterCaretNode || aft);
15041 ed.undoManager.add();
15042
15043 return FALSE;
15044 },
15045
15046 backspaceDelete : function(e, bs) {
15047 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;
15048
15049 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
15050 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
15051 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
15052
15053 // Walk the dom backwards until we find a text node
15054 for (n = sc.lastChild; n; n = walker.prev()) {
15055 if (n.nodeType == 3) {
15056 r.setStart(n, n.nodeValue.length);
15057 r.collapse(true);
15058 se.setRng(r);
15059 return;
15060 }
15061 }
15062 }
15063
15064 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
15065 // This workaround removes the element by hand and moves the caret to the previous element
15066 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
15067 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
15068 // Find previous block element
15069 n = sc;
15070 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
15071
15072 if (n) {
15073 if (sc != b.firstChild) {
15074 // Find last text node
15075 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
15076 while (tn = w.nextNode())
15077 n = tn;
15078
15079 // Place caret at the end of last text node
15080 r = ed.getDoc().createRange();
15081 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
15082 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
15083 se.setRng(r);
15084
15085 // Remove the target container
15086 ed.dom.remove(sc);
15087 }
15088
15089 return Event.cancel(e);
15090 }
15091 }
15092 }
15093 }
15094 });
15095 })(tinymce);
15096
15097 (function(tinymce) {
15098 // Shorten names
15099 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15100
15101 tinymce.create('tinymce.ControlManager', {
15102 ControlManager : function(ed, s) {
15103 var t = this, i;
15104
15105 s = s || {};
15106 t.editor = ed;
15107 t.controls = {};
15108 t.onAdd = new tinymce.util.Dispatcher(t);
15109 t.onPostRender = new tinymce.util.Dispatcher(t);
15110 t.prefix = s.prefix || ed.id + '_';
15111 t._cls = {};
15112
15113 t.onPostRender.add(function() {
15114 each(t.controls, function(c) {
15115 c.postRender();
15116 });
15117 });
15118 },
15119
15120 get : function(id) {
15121 return this.controls[this.prefix + id] || this.controls[id];
15122 },
15123
15124 setActive : function(id, s) {
15125 var c = null;
15126
15127 if (c = this.get(id))
15128 c.setActive(s);
15129
15130 return c;
15131 },
15132
15133 setDisabled : function(id, s) {
15134 var c = null;
15135
15136 if (c = this.get(id))
15137 c.setDisabled(s);
15138
15139 return c;
15140 },
15141
15142 add : function(c) {
15143 var t = this;
15144
15145 if (c) {
15146 t.controls[c.id] = c;
15147 t.onAdd.dispatch(c, t);
15148 }
15149
15150 return c;
15151 },
15152
15153 createControl : function(n) {
15154 var c, t = this, ed = t.editor;
15155
15156 each(ed.plugins, function(p) {
15157 if (p.createControl) {
15158 c = p.createControl(n, t);
15159
15160 if (c)
15161 return false;
15162 }
15163 });
15164
15165 switch (n) {
15166 case "|":
15167 case "separator":
15168 return t.createSeparator();
15169 }
15170
15171 if (!c && ed.buttons && (c = ed.buttons[n]))
15172 return t.createButton(n, c);
15173
15174 return t.add(c);
15175 },
15176
15177 createDropMenu : function(id, s, cc) {
15178 var t = this, ed = t.editor, c, bm, v, cls;
15179
15180 s = extend({
15181 'class' : 'mceDropDown',
15182 constrain : ed.settings.constrain_menus
15183 }, s);
15184
15185 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
15186 if (v = ed.getParam('skin_variant'))
15187 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15188
15189 id = t.prefix + id;
15190 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15191 c = t.controls[id] = new cls(id, s);
15192 c.onAddItem.add(function(c, o) {
15193 var s = o.settings;
15194
15195 s.title = ed.getLang(s.title, s.title);
15196
15197 if (!s.onclick) {
15198 s.onclick = function(v) {
15199 if (s.cmd)
15200 ed.execCommand(s.cmd, s.ui || false, s.value);
15201 };
15202 }
15203 });
15204
15205 ed.onRemove.add(function() {
15206 c.destroy();
15207 });
15208
15209 // Fix for bug #1897785, #1898007
15210 if (tinymce.isIE) {
15211 c.onShowMenu.add(function() {
15212 // IE 8 needs focus in order to store away a range with the current collapsed caret location
15213 ed.focus();
15214
15215 bm = ed.selection.getBookmark(1);
15216 });
15217
15218 c.onHideMenu.add(function() {
15219 if (bm) {
15220 ed.selection.moveToBookmark(bm);
15221 bm = 0;
15222 }
15223 });
15224 }
15225
15226 return t.add(c);
15227 },
15228
15229 createListBox : function(id, s, cc) {
15230 var t = this, ed = t.editor, cmd, c, cls;
15231
15232 if (t.get(id))
15233 return null;
15234
15235 s.title = ed.translate(s.title);
15236 s.scope = s.scope || ed;
15237
15238 if (!s.onselect) {
15239 s.onselect = function(v) {
15240 ed.execCommand(s.cmd, s.ui || false, v || s.value);
15241 };
15242 }
15243
15244 s = extend({
15245 title : s.title,
15246 'class' : 'mce_' + id,
15247 scope : s.scope,
15248 control_manager : t
15249 }, s);
15250
15251 id = t.prefix + id;
15252
15253
15254 function useNativeListForAccessibility(ed) {
15255 return ed.settings.use_accessible_selects && !tinymce.isGecko
15256 }
15257
15258 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
15259 c = new tinymce.ui.NativeListBox(id, s);
15260 else {
15261 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
15262 c = new cls(id, s, ed);
15263 }
15264
15265 t.controls[id] = c;
15266
15267 // Fix focus problem in Safari
15268 if (tinymce.isWebKit) {
15269 c.onPostRender.add(function(c, n) {
15270 // Store bookmark on mousedown
15271 Event.add(n, 'mousedown', function() {
15272 ed.bookmark = ed.selection.getBookmark(1);
15273 });
15274
15275 // Restore on focus, since it might be lost
15276 Event.add(n, 'focus', function() {
15277 ed.selection.moveToBookmark(ed.bookmark);
15278 ed.bookmark = null;
15279 });
15280 });
15281 }
15282
15283 if (c.hideMenu)
15284 ed.onMouseDown.add(c.hideMenu, c);
15285
15286 return t.add(c);
15287 },
15288
15289 createButton : function(id, s, cc) {
15290 var t = this, ed = t.editor, o, c, cls;
15291
15292 if (t.get(id))
15293 return null;
15294
15295 s.title = ed.translate(s.title);
15296 s.label = ed.translate(s.label);
15297 s.scope = s.scope || ed;
15298
15299 if (!s.onclick && !s.menu_button) {
15300 s.onclick = function() {
15301 ed.execCommand(s.cmd, s.ui || false, s.value);
15302 };
15303 }
15304
15305 s = extend({
15306 title : s.title,
15307 'class' : 'mce_' + id,
15308 unavailable_prefix : ed.getLang('unavailable', ''),
15309 scope : s.scope,
15310 control_manager : t
15311 }, s);
15312
15313 id = t.prefix + id;
15314
15315 if (s.menu_button) {
15316 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
15317 c = new cls(id, s, ed);
15318 ed.onMouseDown.add(c.hideMenu, c);
15319 } else {
15320 cls = t._cls.button || tinymce.ui.Button;
15321 c = new cls(id, s, ed);
15322 }
15323
15324 return t.add(c);
15325 },
15326
15327 createMenuButton : function(id, s, cc) {
15328 s = s || {};
15329 s.menu_button = 1;
15330
15331 return this.createButton(id, s, cc);
15332 },
15333
15334 createSplitButton : function(id, s, cc) {
15335 var t = this, ed = t.editor, cmd, c, cls;
15336
15337 if (t.get(id))
15338 return null;
15339
15340 s.title = ed.translate(s.title);
15341 s.scope = s.scope || ed;
15342
15343 if (!s.onclick) {
15344 s.onclick = function(v) {
15345 ed.execCommand(s.cmd, s.ui || false, v || s.value);
15346 };
15347 }
15348
15349 if (!s.onselect) {
15350 s.onselect = function(v) {
15351 ed.execCommand(s.cmd, s.ui || false, v || s.value);
15352 };
15353 }
15354
15355 s = extend({
15356 title : s.title,
15357 'class' : 'mce_' + id,
15358 scope : s.scope,
15359 control_manager : t
15360 }, s);
15361
15362 id = t.prefix + id;
15363 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
15364 c = t.add(new cls(id, s, ed));
15365 ed.onMouseDown.add(c.hideMenu, c);
15366
15367 return c;
15368 },
15369
15370 createColorSplitButton : function(id, s, cc) {
15371 var t = this, ed = t.editor, cmd, c, cls, bm;
15372
15373 if (t.get(id))
15374 return null;
15375
15376 s.title = ed.translate(s.title);
15377 s.scope = s.scope || ed;
15378
15379 if (!s.onclick) {
15380 s.onclick = function(v) {
15381 if (tinymce.isIE)
15382 bm = ed.selection.getBookmark(1);
15383
15384 ed.execCommand(s.cmd, s.ui || false, v || s.value);
15385 };
15386 }
15387
15388 if (!s.onselect) {
15389 s.onselect = function(v) {
15390 ed.execCommand(s.cmd, s.ui || false, v || s.value);
15391 };
15392 }
15393
15394 s = extend({
15395 title : s.title,
15396 'class' : 'mce_' + id,
15397 'menu_class' : ed.getParam('skin') + 'Skin',
15398 scope : s.scope,
15399 more_colors_title : ed.getLang('more_colors')
15400 }, s);
15401
15402 id = t.prefix + id;
15403 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
15404 c = new cls(id, s, ed);
15405 ed.onMouseDown.add(c.hideMenu, c);
15406
15407 // Remove the menu element when the editor is removed
15408 ed.onRemove.add(function() {
15409 c.destroy();
15410 });
15411
15412 // Fix for bug #1897785, #1898007
15413 if (tinymce.isIE) {
15414 c.onShowMenu.add(function() {
15415 // IE 8 needs focus in order to store away a range with the current collapsed caret location
15416 ed.focus();
15417 bm = ed.selection.getBookmark(1);
15418 });
15419
15420 c.onHideMenu.add(function() {
15421 if (bm) {
15422 ed.selection.moveToBookmark(bm);
15423 bm = 0;
15424 }
15425 });
15426 }
15427
15428 return t.add(c);
15429 },
15430
15431 createToolbar : function(id, s, cc) {
15432 var c, t = this, cls;
15433
15434 id = t.prefix + id;
15435 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
15436 c = new cls(id, s, t.editor);
15437
15438 if (t.get(id))
15439 return null;
15440
15441 return t.add(c);
15442 },
15443
15444 createToolbarGroup : function(id, s, cc) {
15445 var c, t = this, cls;
15446 id = t.prefix + id;
15447 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
15448 c = new cls(id, s, t.editor);
15449
15450 if (t.get(id))
15451 return null;
15452
15453 return t.add(c);
15454 },
15455
15456 createSeparator : function(cc) {
15457 var cls = cc || this._cls.separator || tinymce.ui.Separator;
15458
15459 return new cls();
15460 },
15461
15462 setControlType : function(n, c) {
15463 return this._cls[n.toLowerCase()] = c;
15464 },
15465
15466 destroy : function() {
15467 each(this.controls, function(c) {
15468 c.destroy();
15469 });
15470
15471 this.controls = null;
15472 }
15473 });
15474 })(tinymce);
15475
15476 (function(tinymce) {
15477 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
15478
15479 tinymce.create('tinymce.WindowManager', {
15480 WindowManager : function(ed) {
15481 var t = this;
15482
15483 t.editor = ed;
15484 t.onOpen = new Dispatcher(t);
15485 t.onClose = new Dispatcher(t);
15486 t.params = {};
15487 t.features = {};
15488 },
15489
15490 open : function(s, p) {
15491 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
15492
15493 // Default some options
15494 s = s || {};
15495 p = p || {};
15496 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
15497 sh = isOpera ? vp.h : screen.height;
15498 s.name = s.name || 'mc_' + new Date().getTime();
15499 s.width = parseInt(s.width || 320);
15500 s.height = parseInt(s.height || 240);
15501 s.resizable = true;
15502 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
15503 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
15504 p.inline = false;
15505 p.mce_width = s.width;
15506 p.mce_height = s.height;
15507 p.mce_auto_focus = s.auto_focus;
15508
15509 if (mo) {
15510 if (isIE) {
15511 s.center = true;
15512 s.help = false;
15513 s.dialogWidth = s.width + 'px';
15514 s.dialogHeight = s.height + 'px';
15515 s.scroll = s.scrollbars || false;
15516 }
15517 }
15518
15519 // Build features string
15520 each(s, function(v, k) {
15521 if (tinymce.is(v, 'boolean'))
15522 v = v ? 'yes' : 'no';
15523
15524 if (!/^(name|url)$/.test(k)) {
15525 if (isIE && mo)
15526 f += (f ? ';' : '') + k + ':' + v;
15527 else
15528 f += (f ? ',' : '') + k + '=' + v;
15529 }
15530 });
15531
15532 t.features = s;
15533 t.params = p;
15534 t.onOpen.dispatch(t, s, p);
15535
15536 u = s.url || s.file;
15537 u = tinymce._addVer(u);
15538
15539 try {
15540 if (isIE && mo) {
15541 w = 1;
15542 window.showModalDialog(u, window, f);
15543 } else
15544 w = window.open(u, s.name, f);
15545 } catch (ex) {
15546 // Ignore
15547 }
15548
15549 if (!w)
15550 alert(t.editor.getLang('popup_blocked'));
15551 },
15552
15553 close : function(w) {
15554 w.close();
15555 this.onClose.dispatch(this);
15556 },
15557
15558 createInstance : function(cl, a, b, c, d, e) {
15559 var f = tinymce.resolve(cl);
15560
15561 return new f(a, b, c, d, e);
15562 },
15563
15564 confirm : function(t, cb, s, w) {
15565 w = w || window;
15566
15567 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
15568 },
15569
15570 alert : function(tx, cb, s, w) {
15571 var t = this;
15572
15573 w = w || window;
15574 w.alert(t._decode(t.editor.getLang(tx, tx)));
15575
15576 if (cb)
15577 cb.call(s || t);
15578 },
15579
15580 resizeBy : function(dw, dh, win) {
15581 win.resizeBy(dw, dh);
15582 },
15583
15584 // Internal functions
15585
15586 _decode : function(s) {
15587 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
15588 }
15589 });
15590 }(tinymce));
15591 (function(tinymce) {
15592 tinymce.Formatter = function(ed) {
15593 var formats = {},
15594 each = tinymce.each,
15595 dom = ed.dom,
15596 selection = ed.selection,
15597 TreeWalker = tinymce.dom.TreeWalker,
15598 rangeUtils = new tinymce.dom.RangeUtils(dom),
15599 isValid = ed.schema.isValidChild,
15600 isBlock = dom.isBlock,
15601 forcedRootBlock = ed.settings.forced_root_block,
15602 nodeIndex = dom.nodeIndex,
15603 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
15604 MCE_ATTR_RE = /^(src|href|style)$/,
15605 FALSE = false,
15606 TRUE = true,
15607 undefined;
15608
15609 // Returns the content editable state of a node
15610 function getContentEditable(node) {
15611 var contentEditable = node.getAttribute("data-mce-contenteditable");
15612
15613 // Check for fake content editable
15614 if (contentEditable && contentEditable !== "inherit") {
15615 return contentEditable;
15616 }
15617
15618 // Check for real content editable
15619 return node.contentEditable !== "inherit" ? node.contentEditable : null;
15620 };
15621
15622 function isArray(obj) {
15623 return obj instanceof Array;
15624 };
15625
15626 function getParents(node, selector) {
15627 return dom.getParents(node, selector, dom.getRoot());
15628 };
15629
15630 function isCaretNode(node) {
15631 return node.nodeType === 1 && node.id === '_mce_caret';
15632 };
15633
15634 // Public functions
15635
15636 function get(name) {
15637 return name ? formats[name] : formats;
15638 };
15639
15640 function register(name, format) {
15641 if (name) {
15642 if (typeof(name) !== 'string') {
15643 each(name, function(format, name) {
15644 register(name, format);
15645 });
15646 } else {
15647 // Force format into array and add it to internal collection
15648 format = format.length ? format : [format];
15649
15650 each(format, function(format) {
15651 // Set deep to false by default on selector formats this to avoid removing
15652 // alignment on images inside paragraphs when alignment is changed on paragraphs
15653 if (format.deep === undefined)
15654 format.deep = !format.selector;
15655
15656 // Default to true
15657 if (format.split === undefined)
15658 format.split = !format.selector || format.inline;
15659
15660 // Default to true
15661 if (format.remove === undefined && format.selector && !format.inline)
15662 format.remove = 'none';
15663
15664 // Mark format as a mixed format inline + block level
15665 if (format.selector && format.inline) {
15666 format.mixed = true;
15667 format.block_expand = true;
15668 }
15669
15670 // Split classes if needed
15671 if (typeof(format.classes) === 'string')
15672 format.classes = format.classes.split(/\s+/);
15673 });
15674
15675 formats[name] = format;
15676 }
15677 }
15678 };
15679
15680 var getTextDecoration = function(node) {
15681 var decoration;
15682
15683 ed.dom.getParent(node, function(n) {
15684 decoration = ed.dom.getStyle(n, 'text-decoration');
15685 return decoration && decoration !== 'none';
15686 });
15687
15688 return decoration;
15689 };
15690
15691 var processUnderlineAndColor = function(node) {
15692 var textDecoration;
15693 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
15694 textDecoration = getTextDecoration(node.parentNode);
15695 if (ed.dom.getStyle(node, 'color') && textDecoration) {
15696 ed.dom.setStyle(node, 'text-decoration', textDecoration);
15697 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
15698 ed.dom.setStyle(node, 'text-decoration', null);
15699 }
15700 }
15701 };
15702
15703 function apply(name, vars, node) {
15704 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
15705
15706 function setElementFormat(elm, fmt) {
15707 fmt = fmt || format;
15708
15709 if (elm) {
15710 if (fmt.onformat) {
15711 fmt.onformat(elm, fmt, vars, node);
15712 }
15713
15714 each(fmt.styles, function(value, name) {
15715 dom.setStyle(elm, name, replaceVars(value, vars));
15716 });
15717
15718 each(fmt.attributes, function(value, name) {
15719 dom.setAttrib(elm, name, replaceVars(value, vars));
15720 });
15721
15722 each(fmt.classes, function(value) {
15723 value = replaceVars(value, vars);
15724
15725 if (!dom.hasClass(elm, value))
15726 dom.addClass(elm, value);
15727 });
15728 }
15729 };
15730 function adjustSelectionToVisibleSelection() {
15731 function findSelectionEnd(start, end) {
15732 var walker = new TreeWalker(end);
15733 for (node = walker.current(); node; node = walker.prev()) {
15734 if (node.childNodes.length > 1 || node == start) {
15735 return node;
15736 }
15737 }
15738 };
15739
15740 // Adjust selection so that a end container with a end offset of zero is not included in the selection
15741 // as this isn't visible to the user.
15742 var rng = ed.selection.getRng();
15743 var start = rng.startContainer;
15744 var end = rng.endContainer;
15745
15746 if (start != end && rng.endOffset == 0) {
15747 var newEnd = findSelectionEnd(start, end);
15748 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
15749
15750 rng.setEnd(newEnd, endOffset);
15751 }
15752
15753 return rng;
15754 }
15755
15756 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
15757 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
15758
15759 // find the index of the first child list.
15760 each(node.childNodes, function(n, index) {
15761 if (n.nodeName === "UL" || n.nodeName === "OL") {
15762 listIndex = index;
15763 list = n;
15764 return false;
15765 }
15766 });
15767
15768 // get the index of the bookmarks
15769 each(node.childNodes, function(n, index) {
15770 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
15771 if (n.id == bookmark.id + "_start") {
15772 startIndex = index;
15773 } else if (n.id == bookmark.id + "_end") {
15774 endIndex = index;
15775 }
15776 }
15777 });
15778
15779 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
15780 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
15781 each(tinymce.grep(node.childNodes), process);
15782 return 0;
15783 } else {
15784 currentWrapElm = dom.clone(wrapElm, FALSE);
15785
15786 // create a list of the nodes on the same side of the list as the selection
15787 each(tinymce.grep(node.childNodes), function(n, index) {
15788 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
15789 nodes.push(n);
15790 n.parentNode.removeChild(n);
15791 }
15792 });
15793
15794 // insert the wrapping element either before or after the list.
15795 if (startIndex < listIndex) {
15796 node.insertBefore(currentWrapElm, list);
15797 } else if (startIndex > listIndex) {
15798 node.insertBefore(currentWrapElm, list.nextSibling);
15799 }
15800
15801 // add the new nodes to the list.
15802 newWrappers.push(currentWrapElm);
15803
15804 each(nodes, function(node) {
15805 currentWrapElm.appendChild(node);
15806 });
15807
15808 return currentWrapElm;
15809 }
15810 };
15811
15812 function applyRngStyle(rng, bookmark, node_specific) {
15813 var newWrappers = [], wrapName, wrapElm, contentEditable = true;
15814
15815 // Setup wrapper element
15816 wrapName = format.inline || format.block;
15817 wrapElm = dom.create(wrapName);
15818 setElementFormat(wrapElm);
15819
15820 rangeUtils.walk(rng, function(nodes) {
15821 var currentWrapElm;
15822
15823 function process(node) {
15824 var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
15825
15826 lastContentEditable = contentEditable;
15827 nodeName = node.nodeName.toLowerCase();
15828 parentName = node.parentNode.nodeName.toLowerCase();
15829
15830 // Node has a contentEditable value
15831 if (node.nodeType === 1 && getContentEditable(node)) {
15832 lastContentEditable = contentEditable;
15833 contentEditable = getContentEditable(node) === "true";
15834 hasContentEditableState = true; // We don't want to wrap the container only it's children
15835 }
15836
15837 // Stop wrapping on br elements
15838 if (isEq(nodeName, 'br')) {
15839 currentWrapElm = 0;
15840
15841 // Remove any br elements when we wrap things
15842 if (format.block)
15843 dom.remove(node);
15844
15845 return;
15846 }
15847
15848 // If node is wrapper type
15849 if (format.wrapper && matchNode(node, name, vars)) {
15850 currentWrapElm = 0;
15851 return;
15852 }
15853
15854 // Can we rename the block
15855 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
15856 node = dom.rename(node, wrapName);
15857 setElementFormat(node);
15858 newWrappers.push(node);
15859 currentWrapElm = 0;
15860 return;
15861 }
15862
15863 // Handle selector patterns
15864 if (format.selector) {
15865 // Look for matching formats
15866 each(formatList, function(format) {
15867 // Check collapsed state if it exists
15868 if ('collapsed' in format && format.collapsed !== isCollapsed) {
15869 return;
15870 }
15871
15872 if (dom.is(node, format.selector) && !isCaretNode(node)) {
15873 setElementFormat(node, format);
15874 found = true;
15875 }
15876 });
15877
15878 // Continue processing if a selector match wasn't found and a inline element is defined
15879 if (!format.inline || found) {
15880 currentWrapElm = 0;
15881 return;
15882 }
15883 }
15884
15885 // Is it valid to wrap this item
15886 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
15887 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
15888 // Start wrapping
15889 if (!currentWrapElm) {
15890 // Wrap the node
15891 currentWrapElm = dom.clone(wrapElm, FALSE);
15892 node.parentNode.insertBefore(currentWrapElm, node);
15893 newWrappers.push(currentWrapElm);
15894 }
15895
15896 currentWrapElm.appendChild(node);
15897 } else if (nodeName == 'li' && bookmark) {
15898 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
15899 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
15900 } else {
15901 // Start a new wrapper for possible children
15902 currentWrapElm = 0;
15903
15904 each(tinymce.grep(node.childNodes), process);
15905
15906 if (hasContentEditableState) {
15907 contentEditable = lastContentEditable; // Restore last contentEditable state from stack
15908 }
15909
15910 // End the last wrapper
15911 currentWrapElm = 0;
15912 }
15913 };
15914
15915 // Process siblings from range
15916 each(nodes, process);
15917 });
15918
15919 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
15920 if (format.wrap_links === false) {
15921 each(newWrappers, function(node) {
15922 function process(node) {
15923 var i, currentWrapElm, children;
15924
15925 if (node.nodeName === 'A') {
15926 currentWrapElm = dom.clone(wrapElm, FALSE);
15927 newWrappers.push(currentWrapElm);
15928
15929 children = tinymce.grep(node.childNodes);
15930 for (i = 0; i < children.length; i++)
15931 currentWrapElm.appendChild(children[i]);
15932
15933 node.appendChild(currentWrapElm);
15934 }
15935
15936 each(tinymce.grep(node.childNodes), process);
15937 };
15938
15939 process(node);
15940 });
15941 }
15942
15943 // Cleanup
15944
15945 each(newWrappers, function(node) {
15946 var childCount;
15947
15948 function getChildCount(node) {
15949 var count = 0;
15950
15951 each(node.childNodes, function(node) {
15952 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
15953 count++;
15954 });
15955
15956 return count;
15957 };
15958
15959 function mergeStyles(node) {
15960 var child, clone;
15961
15962 each(node.childNodes, function(node) {
15963 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
15964 child = node;
15965 return FALSE; // break loop
15966 }
15967 });
15968
15969 // If child was found and of the same type as the current node
15970 if (child && matchName(child, format)) {
15971 clone = dom.clone(child, FALSE);
15972 setElementFormat(clone);
15973
15974 dom.replace(clone, node, TRUE);
15975 dom.remove(child, 1);
15976 }
15977
15978 return clone || node;
15979 };
15980
15981 childCount = getChildCount(node);
15982
15983 // Remove empty nodes but only if there is multiple wrappers and they are not block
15984 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
15985 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
15986 dom.remove(node, 1);
15987 return;
15988 }
15989
15990 if (format.inline || format.wrapper) {
15991 // Merges the current node with it's children of similar type to reduce the number of elements
15992 if (!format.exact && childCount === 1)
15993 node = mergeStyles(node);
15994
15995 // Remove/merge children
15996 each(formatList, function(format) {
15997 // Merge all children of similar type will move styles from child to parent
15998 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
15999 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16000 each(dom.select(format.inline, node), function(child) {
16001 var parent;
16002
16003 // When wrap_links is set to false we don't want
16004 // to remove the format on children within links
16005 if (format.wrap_links === false) {
16006 parent = child.parentNode;
16007
16008 do {
16009 if (parent.nodeName === 'A')
16010 return;
16011 } while (parent = parent.parentNode);
16012 }
16013
16014 removeFormat(format, vars, child, format.exact ? child : null);
16015 });
16016 });
16017
16018 // Remove child if direct parent is of same type
16019 if (matchNode(node.parentNode, name, vars)) {
16020 dom.remove(node, 1);
16021 node = 0;
16022 return TRUE;
16023 }
16024
16025 // Look for parent with similar style format
16026 if (format.merge_with_parents) {
16027 dom.getParent(node.parentNode, function(parent) {
16028 if (matchNode(parent, name, vars)) {
16029 dom.remove(node, 1);
16030 node = 0;
16031 return TRUE;
16032 }
16033 });
16034 }
16035
16036 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16037 if (node && format.merge_siblings !== false) {
16038 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16039 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16040 }
16041 }
16042 });
16043 };
16044
16045 if (format) {
16046 if (node) {
16047 if (node.nodeType) {
16048 rng = dom.createRng();
16049 rng.setStartBefore(node);
16050 rng.setEndAfter(node);
16051 applyRngStyle(expandRng(rng, formatList), null, true);
16052 } else {
16053 applyRngStyle(node, null, true);
16054 }
16055 } else {
16056 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16057 // Obtain selection node before selection is unselected by applyRngStyle()
16058 var curSelNode = ed.selection.getNode();
16059
16060 // Apply formatting to selection
16061 ed.selection.setRng(adjustSelectionToVisibleSelection());
16062 bookmark = selection.getBookmark();
16063 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
16064
16065 // Colored nodes should be underlined so that the color of the underline matches the text color.
16066 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
16067 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
16068 processUnderlineAndColor(curSelNode);
16069 }
16070
16071 selection.moveToBookmark(bookmark);
16072 moveStart(selection.getRng(TRUE));
16073 ed.nodeChanged();
16074 } else
16075 performCaretAction('apply', name, vars);
16076 }
16077 }
16078 };
16079
16080 function remove(name, vars, node) {
16081 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
16082
16083 // Merges the styles for each node
16084 function process(node) {
16085 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
16086
16087 // Node has a contentEditable value
16088 if (node.nodeType === 1 && getContentEditable(node)) {
16089 lastContentEditable = contentEditable;
16090 contentEditable = getContentEditable(node) === "true";
16091 hasContentEditableState = true; // We don't want to wrap the container only it's children
16092 }
16093
16094 // Grab the children first since the nodelist might be changed
16095 children = tinymce.grep(node.childNodes);
16096
16097 // Process current node
16098 if (contentEditable && !hasContentEditableState) {
16099 for (i = 0, l = formatList.length; i < l; i++) {
16100 if (removeFormat(formatList[i], vars, node, node))
16101 break;
16102 }
16103 }
16104
16105 // Process the children
16106 if (format.deep) {
16107 if (children.length) {
16108 for (i = 0, l = children.length; i < l; i++)
16109 process(children[i]);
16110
16111 if (hasContentEditableState) {
16112 contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16113 }
16114 }
16115 }
16116 };
16117
16118 function findFormatRoot(container) {
16119 var formatRoot;
16120
16121 // Find format root
16122 each(getParents(container.parentNode).reverse(), function(parent) {
16123 var format;
16124
16125 // Find format root element
16126 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
16127 // Is the node matching the format we are looking for
16128 format = matchNode(parent, name, vars);
16129 if (format && format.split !== false)
16130 formatRoot = parent;
16131 }
16132 });
16133
16134 return formatRoot;
16135 };
16136
16137 function wrapAndSplit(format_root, container, target, split) {
16138 var parent, clone, lastClone, firstClone, i, formatRootParent;
16139
16140 // Format root found then clone formats and split it
16141 if (format_root) {
16142 formatRootParent = format_root.parentNode;
16143
16144 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
16145 clone = dom.clone(parent, FALSE);
16146
16147 for (i = 0; i < formatList.length; i++) {
16148 if (removeFormat(formatList[i], vars, clone, clone)) {
16149 clone = 0;
16150 break;
16151 }
16152 }
16153
16154 // Build wrapper node
16155 if (clone) {
16156 if (lastClone)
16157 clone.appendChild(lastClone);
16158
16159 if (!firstClone)
16160 firstClone = clone;
16161
16162 lastClone = clone;
16163 }
16164 }
16165
16166 // Never split block elements if the format is mixed
16167 if (split && (!format.mixed || !isBlock(format_root)))
16168 container = dom.split(format_root, container);
16169
16170 // Wrap container in cloned formats
16171 if (lastClone) {
16172 target.parentNode.insertBefore(lastClone, target);
16173 firstClone.appendChild(target);
16174 }
16175 }
16176
16177 return container;
16178 };
16179
16180 function splitToFormatRoot(container) {
16181 return wrapAndSplit(findFormatRoot(container), container, container, true);
16182 };
16183
16184 function unwrap(start) {
16185 var node = dom.get(start ? '_start' : '_end'),
16186 out = node[start ? 'firstChild' : 'lastChild'];
16187
16188 // If the end is placed within the start the result will be removed
16189 // So this checks if the out node is a bookmark node if it is it
16190 // checks for another more suitable node
16191 if (isBookmarkNode(out))
16192 out = out[start ? 'firstChild' : 'lastChild'];
16193
16194 dom.remove(node, true);
16195
16196 return out;
16197 };
16198
16199 function removeRngStyle(rng) {
16200 var startContainer, endContainer;
16201
16202 rng = expandRng(rng, formatList, TRUE);
16203
16204 if (format.split) {
16205 startContainer = getContainer(rng, TRUE);
16206 endContainer = getContainer(rng);
16207
16208 if (startContainer != endContainer) {
16209 // Wrap start/end nodes in span element since these might be cloned/moved
16210 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
16211 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
16212
16213 // Split start/end
16214 splitToFormatRoot(startContainer);
16215 splitToFormatRoot(endContainer);
16216
16217 // Unwrap start/end to get real elements again
16218 startContainer = unwrap(TRUE);
16219 endContainer = unwrap();
16220 } else
16221 startContainer = endContainer = splitToFormatRoot(startContainer);
16222
16223 // Update range positions since they might have changed after the split operations
16224 rng.startContainer = startContainer.parentNode;
16225 rng.startOffset = nodeIndex(startContainer);
16226 rng.endContainer = endContainer.parentNode;
16227 rng.endOffset = nodeIndex(endContainer) + 1;
16228 }
16229
16230 // Remove items between start/end
16231 rangeUtils.walk(rng, function(nodes) {
16232 each(nodes, function(node) {
16233 process(node);
16234
16235 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
16236 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
16237 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
16238 }
16239 });
16240 });
16241 };
16242
16243 // Handle node
16244 if (node) {
16245 if (node.nodeType) {
16246 rng = dom.createRng();
16247 rng.setStartBefore(node);
16248 rng.setEndAfter(node);
16249 removeRngStyle(rng);
16250 } else {
16251 removeRngStyle(node);
16252 }
16253
16254 return;
16255 }
16256
16257 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16258 bookmark = selection.getBookmark();
16259 removeRngStyle(selection.getRng(TRUE));
16260 selection.moveToBookmark(bookmark);
16261
16262 // 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
16263 if (format.inline && match(name, vars, selection.getStart())) {
16264 moveStart(selection.getRng(true));
16265 }
16266
16267 ed.nodeChanged();
16268 } else
16269 performCaretAction('remove', name, vars);
16270
16271 // When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width
16272 if (tinymce.isWebKit) {
16273 ed.execCommand('mceCleanup');
16274 }
16275 };
16276
16277 function toggle(name, vars, node) {
16278 var fmt = get(name);
16279
16280 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
16281 remove(name, vars, node);
16282 else
16283 apply(name, vars, node);
16284 };
16285
16286 function matchNode(node, name, vars, similar) {
16287 var formatList = get(name), format, i, classes;
16288
16289 function matchItems(node, format, item_name) {
16290 var key, value, items = format[item_name], i;
16291
16292 // Custom match
16293 if (format.onmatch) {
16294 return format.onmatch(node, format, item_name);
16295 }
16296
16297 // Check all items
16298 if (items) {
16299 // Non indexed object
16300 if (items.length === undefined) {
16301 for (key in items) {
16302 if (items.hasOwnProperty(key)) {
16303 if (item_name === 'attributes')
16304 value = dom.getAttrib(node, key);
16305 else
16306 value = getStyle(node, key);
16307
16308 if (similar && !value && !format.exact)
16309 return;
16310
16311 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
16312 return;
16313 }
16314 }
16315 } else {
16316 // Only one match needed for indexed arrays
16317 for (i = 0; i < items.length; i++) {
16318 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
16319 return format;
16320 }
16321 }
16322 }
16323
16324 return format;
16325 };
16326
16327 if (formatList && node) {
16328 // Check each format in list
16329 for (i = 0; i < formatList.length; i++) {
16330 format = formatList[i];
16331
16332 // Name name, attributes, styles and classes
16333 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
16334 // Match classes
16335 if (classes = format.classes) {
16336 for (i = 0; i < classes.length; i++) {
16337 if (!dom.hasClass(node, classes[i]))
16338 return;
16339 }
16340 }
16341
16342 return format;
16343 }
16344 }
16345 }
16346 };
16347
16348 function match(name, vars, node) {
16349 var startNode;
16350
16351 function matchParents(node) {
16352 // Find first node with similar format settings
16353 node = dom.getParent(node, function(node) {
16354 return !!matchNode(node, name, vars, true);
16355 });
16356
16357 // Do an exact check on the similar format element
16358 return matchNode(node, name, vars);
16359 };
16360
16361 // Check specified node
16362 if (node)
16363 return matchParents(node);
16364
16365 // Check selected node
16366 node = selection.getNode();
16367 if (matchParents(node))
16368 return TRUE;
16369
16370 // Check start node if it's different
16371 startNode = selection.getStart();
16372 if (startNode != node) {
16373 if (matchParents(startNode))
16374 return TRUE;
16375 }
16376
16377 return FALSE;
16378 };
16379
16380 function matchAll(names, vars) {
16381 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
16382
16383 // Check start of selection for formats
16384 startElement = selection.getStart();
16385 dom.getParent(startElement, function(node) {
16386 var i, name;
16387
16388 for (i = 0; i < names.length; i++) {
16389 name = names[i];
16390
16391 if (!checkedMap[name] && matchNode(node, name, vars)) {
16392 checkedMap[name] = true;
16393 matchedFormatNames.push(name);
16394 }
16395 }
16396 });
16397
16398 return matchedFormatNames;
16399 };
16400
16401 function canApply(name) {
16402 var formatList = get(name), startNode, parents, i, x, selector;
16403
16404 if (formatList) {
16405 startNode = selection.getStart();
16406 parents = getParents(startNode);
16407
16408 for (x = formatList.length - 1; x >= 0; x--) {
16409 selector = formatList[x].selector;
16410
16411 // Format is not selector based, then always return TRUE
16412 if (!selector)
16413 return TRUE;
16414
16415 for (i = parents.length - 1; i >= 0; i--) {
16416 if (dom.is(parents[i], selector))
16417 return TRUE;
16418 }
16419 }
16420 }
16421
16422 return FALSE;
16423 };
16424
16425 // Expose to public
16426 tinymce.extend(this, {
16427 get : get,
16428 register : register,
16429 apply : apply,
16430 remove : remove,
16431 toggle : toggle,
16432 match : match,
16433 matchAll : matchAll,
16434 matchNode : matchNode,
16435 canApply : canApply
16436 });
16437
16438 // Private functions
16439
16440 function matchName(node, format) {
16441 // Check for inline match
16442 if (isEq(node, format.inline))
16443 return TRUE;
16444
16445 // Check for block match
16446 if (isEq(node, format.block))
16447 return TRUE;
16448
16449 // Check for selector match
16450 if (format.selector)
16451 return dom.is(node, format.selector);
16452 };
16453
16454 function isEq(str1, str2) {
16455 str1 = str1 || '';
16456 str2 = str2 || '';
16457
16458 str1 = '' + (str1.nodeName || str1);
16459 str2 = '' + (str2.nodeName || str2);
16460
16461 return str1.toLowerCase() == str2.toLowerCase();
16462 };
16463
16464 function getStyle(node, name) {
16465 var styleVal = dom.getStyle(node, name);
16466
16467 // Force the format to hex
16468 if (name == 'color' || name == 'backgroundColor')
16469 styleVal = dom.toHex(styleVal);
16470
16471 // Opera will return bold as 700
16472 if (name == 'fontWeight' && styleVal == 700)
16473 styleVal = 'bold';
16474
16475 return '' + styleVal;
16476 };
16477
16478 function replaceVars(value, vars) {
16479 if (typeof(value) != "string")
16480 value = value(vars);
16481 else if (vars) {
16482 value = value.replace(/%(\w+)/g, function(str, name) {
16483 return vars[name] || str;
16484 });
16485 }
16486
16487 return value;
16488 };
16489
16490 function isWhiteSpaceNode(node) {
16491 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
16492 };
16493
16494 function wrap(node, name, attrs) {
16495 var wrapper = dom.create(name, attrs);
16496
16497 node.parentNode.insertBefore(wrapper, node);
16498 wrapper.appendChild(node);
16499
16500 return wrapper;
16501 };
16502
16503 function expandRng(rng, format, remove) {
16504 var sibling, lastIdx, leaf,
16505 startContainer = rng.startContainer,
16506 startOffset = rng.startOffset,
16507 endContainer = rng.endContainer,
16508 endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint;
16509
16510 // This function walks up the tree if there is no siblings before/after the node
16511 function findParentContainer(start) {
16512 var container, parent, child, sibling, siblingName;
16513
16514 container = parent = start ? startContainer : endContainer;
16515 siblingName = start ? 'previousSibling' : 'nextSibling';
16516 root = dom.getRoot();
16517
16518 // If it's a text node and the offset is inside the text
16519 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
16520 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
16521 return container;
16522 }
16523 }
16524
16525 for (;;) {
16526 // Stop expanding on block elements
16527 if (!format[0].block_expand && isBlock(parent))
16528 return parent;
16529
16530 // Walk left/right
16531 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
16532 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
16533 return parent;
16534 }
16535 }
16536
16537 // Check if we can move up are we at root level or body level
16538 if (parent.parentNode == root) {
16539 container = parent;
16540 break;
16541 }
16542
16543 parent = parent.parentNode;
16544 }
16545
16546 return container;
16547 };
16548
16549 // This function walks down the tree to find the leaf at the selection.
16550 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
16551 function findLeaf(node, offset) {
16552 if (offset === undefined)
16553 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
16554 while (node && node.hasChildNodes()) {
16555 node = node.childNodes[offset];
16556 if (node)
16557 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
16558 }
16559 return { node: node, offset: offset };
16560 }
16561
16562 // If index based start position then resolve it
16563 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
16564 lastIdx = startContainer.childNodes.length - 1;
16565 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
16566
16567 if (startContainer.nodeType == 3)
16568 startOffset = 0;
16569 }
16570
16571 // If index based end position then resolve it
16572 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
16573 lastIdx = endContainer.childNodes.length - 1;
16574 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
16575
16576 if (endContainer.nodeType == 3)
16577 endOffset = endContainer.nodeValue.length;
16578 }
16579
16580 // Expands the node to the closes contentEditable false element if it exists
16581 function findParentContentEditable(node) {
16582 var parent = node;
16583
16584 while (parent) {
16585 if (parent.nodeType === 1 && getContentEditable(parent)) {
16586 return getContentEditable(parent) === "false" ? parent : node;
16587 }
16588
16589 parent = parent.parentNode;
16590 }
16591
16592 return node;
16593 };
16594
16595 // Expand to closest contentEditable element
16596 startContainer = findParentContentEditable(startContainer);
16597 endContainer = findParentContentEditable(endContainer);
16598
16599 // Exclude bookmark nodes if possible
16600 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
16601 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
16602 startContainer = startContainer.nextSibling || startContainer;
16603
16604 if (startContainer.nodeType == 3)
16605 startOffset = 0;
16606 }
16607
16608 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
16609 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
16610 endContainer = endContainer.previousSibling || endContainer;
16611
16612 if (endContainer.nodeType == 3)
16613 endOffset = endContainer.length;
16614 }
16615
16616 if (format[0].inline) {
16617 if (rng.collapsed) {
16618 function findWordEndPoint(container, offset, start) {
16619 var walker, node, pos, lastTextNode;
16620
16621 function findSpace(node, offset) {
16622 var pos, pos2, str = node.nodeValue;
16623
16624 if (typeof(offset) == "undefined") {
16625 offset = start ? str.length : 0;
16626 }
16627
16628 if (start) {
16629 pos = str.lastIndexOf(' ', offset);
16630 pos2 = str.lastIndexOf('\u00a0', offset);
16631 pos = pos > pos2 ? pos : pos2;
16632
16633 // Include the space on remove to avoid tag soup
16634 if (pos !== -1 && !remove) {
16635 pos++;
16636 }
16637 } else {
16638 pos = str.indexOf(' ', offset);
16639 pos2 = str.indexOf('\u00a0', offset);
16640 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
16641 }
16642
16643 return pos;
16644 };
16645
16646 if (container.nodeType === 3) {
16647 pos = findSpace(container, offset);
16648
16649 if (pos !== -1) {
16650 return {container : container, offset : pos};
16651 }
16652
16653 lastTextNode = container;
16654 }
16655
16656 // Walk the nodes inside the block
16657 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
16658 while (node = walker[start ? 'prev' : 'next']()) {
16659 if (node.nodeType === 3) {
16660 lastTextNode = node;
16661 pos = findSpace(node);
16662
16663 if (pos !== -1) {
16664 return {container : node, offset : pos};
16665 }
16666 } else if (isBlock(node)) {
16667 break;
16668 }
16669 }
16670
16671 if (lastTextNode) {
16672 if (start) {
16673 offset = 0;
16674 } else {
16675 offset = lastTextNode.length;
16676 }
16677
16678 return {container: lastTextNode, offset: offset};
16679 }
16680 }
16681
16682 // Expand left to closest word boundery
16683 endPoint = findWordEndPoint(startContainer, startOffset, true);
16684 if (endPoint) {
16685 startContainer = endPoint.container;
16686 startOffset = endPoint.offset;
16687 }
16688
16689 // Expand right to closest word boundery
16690 endPoint = findWordEndPoint(endContainer, endOffset);
16691 if (endPoint) {
16692 endContainer = endPoint.container;
16693 endOffset = endPoint.offset;
16694 }
16695 }
16696
16697 // Avoid applying formatting to a trailing space.
16698 leaf = findLeaf(endContainer, endOffset);
16699 if (leaf.node) {
16700 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
16701 leaf = findLeaf(leaf.node.previousSibling);
16702
16703 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
16704 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
16705
16706 if (leaf.offset > 1) {
16707 endContainer = leaf.node;
16708 endContainer.splitText(leaf.offset - 1);
16709 } else if (leaf.node.previousSibling) {
16710 // TODO: Figure out why this is in here
16711 //endContainer = leaf.node.previousSibling;
16712 }
16713 }
16714 }
16715 }
16716
16717 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
16718 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
16719 // This will reduce the number of wrapper elements that needs to be created
16720 // Move start point up the tree
16721 if (format[0].inline || format[0].block_expand) {
16722 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
16723 startContainer = findParentContainer(true);
16724 }
16725
16726 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
16727 endContainer = findParentContainer();
16728 }
16729 }
16730
16731 // Expand start/end container to matching selector
16732 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
16733 function findSelectorEndPoint(container, sibling_name) {
16734 var parents, i, y, curFormat;
16735
16736 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
16737 container = container[sibling_name];
16738
16739 parents = getParents(container);
16740 for (i = 0; i < parents.length; i++) {
16741 for (y = 0; y < format.length; y++) {
16742 curFormat = format[y];
16743
16744 // If collapsed state is set then skip formats that doesn't match that
16745 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
16746 continue;
16747
16748 if (dom.is(parents[i], curFormat.selector))
16749 return parents[i];
16750 }
16751 }
16752
16753 return container;
16754 };
16755
16756 // Find new startContainer/endContainer if there is better one
16757 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
16758 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
16759 }
16760
16761 // Expand start/end container to matching block element or text node
16762 if (format[0].block || format[0].selector) {
16763 function findBlockEndPoint(container, sibling_name, sibling_name2) {
16764 var node;
16765
16766 // Expand to block of similar type
16767 if (!format[0].wrapper)
16768 node = dom.getParent(container, format[0].block);
16769
16770 // Expand to first wrappable block element or any block element
16771 if (!node)
16772 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
16773
16774 // Exclude inner lists from wrapping
16775 if (node && format[0].wrapper)
16776 node = getParents(node, 'ul,ol').reverse()[0] || node;
16777
16778 // Didn't find a block element look for first/last wrappable element
16779 if (!node) {
16780 node = container;
16781
16782 while (node[sibling_name] && !isBlock(node[sibling_name])) {
16783 node = node[sibling_name];
16784
16785 // Break on BR but include it will be removed later on
16786 // we can't remove it now since we need to check if it can be wrapped
16787 if (isEq(node, 'br'))
16788 break;
16789 }
16790 }
16791
16792 return node || container;
16793 };
16794
16795 // Find new startContainer/endContainer if there is better one
16796 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
16797 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
16798
16799 // Non block element then try to expand up the leaf
16800 if (format[0].block) {
16801 if (!isBlock(startContainer))
16802 startContainer = findParentContainer(true);
16803
16804 if (!isBlock(endContainer))
16805 endContainer = findParentContainer();
16806 }
16807 }
16808
16809 // Setup index for startContainer
16810 if (startContainer.nodeType == 1) {
16811 startOffset = nodeIndex(startContainer);
16812 startContainer = startContainer.parentNode;
16813 }
16814
16815 // Setup index for endContainer
16816 if (endContainer.nodeType == 1) {
16817 endOffset = nodeIndex(endContainer) + 1;
16818 endContainer = endContainer.parentNode;
16819 }
16820
16821 // Return new range like object
16822 return {
16823 startContainer : startContainer,
16824 startOffset : startOffset,
16825 endContainer : endContainer,
16826 endOffset : endOffset
16827 };
16828 }
16829
16830 function removeFormat(format, vars, node, compare_node) {
16831 var i, attrs, stylesModified;
16832
16833 // Check if node matches format
16834 if (!matchName(node, format))
16835 return FALSE;
16836
16837 // Should we compare with format attribs and styles
16838 if (format.remove != 'all') {
16839 // Remove styles
16840 each(format.styles, function(value, name) {
16841 value = replaceVars(value, vars);
16842
16843 // Indexed array
16844 if (typeof(name) === 'number') {
16845 name = value;
16846 compare_node = 0;
16847 }
16848
16849 if (!compare_node || isEq(getStyle(compare_node, name), value))
16850 dom.setStyle(node, name, '');
16851
16852 stylesModified = 1;
16853 });
16854
16855 // Remove style attribute if it's empty
16856 if (stylesModified && dom.getAttrib(node, 'style') == '') {
16857 node.removeAttribute('style');
16858 node.removeAttribute('data-mce-style');
16859 }
16860
16861 // Remove attributes
16862 each(format.attributes, function(value, name) {
16863 var valueOut;
16864
16865 value = replaceVars(value, vars);
16866
16867 // Indexed array
16868 if (typeof(name) === 'number') {
16869 name = value;
16870 compare_node = 0;
16871 }
16872
16873 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
16874 // Keep internal classes
16875 if (name == 'class') {
16876 value = dom.getAttrib(node, name);
16877 if (value) {
16878 // Build new class value where everything is removed except the internal prefixed classes
16879 valueOut = '';
16880 each(value.split(/\s+/), function(cls) {
16881 if (/mce\w+/.test(cls))
16882 valueOut += (valueOut ? ' ' : '') + cls;
16883 });
16884
16885 // We got some internal classes left
16886 if (valueOut) {
16887 dom.setAttrib(node, name, valueOut);
16888 return;
16889 }
16890 }
16891 }
16892
16893 // IE6 has a bug where the attribute doesn't get removed correctly
16894 if (name == "class")
16895 node.removeAttribute('className');
16896
16897 // Remove mce prefixed attributes
16898 if (MCE_ATTR_RE.test(name))
16899 node.removeAttribute('data-mce-' + name);
16900
16901 node.removeAttribute(name);
16902 }
16903 });
16904
16905 // Remove classes
16906 each(format.classes, function(value) {
16907 value = replaceVars(value, vars);
16908
16909 if (!compare_node || dom.hasClass(compare_node, value))
16910 dom.removeClass(node, value);
16911 });
16912
16913 // Check for non internal attributes
16914 attrs = dom.getAttribs(node);
16915 for (i = 0; i < attrs.length; i++) {
16916 if (attrs[i].nodeName.indexOf('_') !== 0)
16917 return FALSE;
16918 }
16919 }
16920
16921 // Remove the inline child if it's empty for example <b> or <span>
16922 if (format.remove != 'none') {
16923 removeNode(node, format);
16924 return TRUE;
16925 }
16926 };
16927
16928 function removeNode(node, format) {
16929 var parentNode = node.parentNode, rootBlockElm;
16930
16931 if (format.block) {
16932 if (!forcedRootBlock) {
16933 function find(node, next, inc) {
16934 node = getNonWhiteSpaceSibling(node, next, inc);
16935
16936 return !node || (node.nodeName == 'BR' || isBlock(node));
16937 };
16938
16939 // Append BR elements if needed before we remove the block
16940 if (isBlock(node) && !isBlock(parentNode)) {
16941 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
16942 node.insertBefore(dom.create('br'), node.firstChild);
16943
16944 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
16945 node.appendChild(dom.create('br'));
16946 }
16947 } else {
16948 // Wrap the block in a forcedRootBlock if we are at the root of document
16949 if (parentNode == dom.getRoot()) {
16950 if (!format.list_block || !isEq(node, format.list_block)) {
16951 each(tinymce.grep(node.childNodes), function(node) {
16952 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
16953 if (!rootBlockElm)
16954 rootBlockElm = wrap(node, forcedRootBlock);
16955 else
16956 rootBlockElm.appendChild(node);
16957 } else
16958 rootBlockElm = 0;
16959 });
16960 }
16961 }
16962 }
16963 }
16964
16965 // Never remove nodes that isn't the specified inline element if a selector is specified too
16966 if (format.selector && format.inline && !isEq(format.inline, node))
16967 return;
16968
16969 dom.remove(node, 1);
16970 };
16971
16972 function getNonWhiteSpaceSibling(node, next, inc) {
16973 if (node) {
16974 next = next ? 'nextSibling' : 'previousSibling';
16975
16976 for (node = inc ? node : node[next]; node; node = node[next]) {
16977 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
16978 return node;
16979 }
16980 }
16981 };
16982
16983 function isBookmarkNode(node) {
16984 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
16985 };
16986
16987 function mergeSiblings(prev, next) {
16988 var marker, sibling, tmpSibling;
16989
16990 function compareElements(node1, node2) {
16991 // Not the same name
16992 if (node1.nodeName != node2.nodeName)
16993 return FALSE;
16994
16995 function getAttribs(node) {
16996 var attribs = {};
16997
16998 each(dom.getAttribs(node), function(attr) {
16999 var name = attr.nodeName.toLowerCase();
17000
17001 // Don't compare internal attributes or style
17002 if (name.indexOf('_') !== 0 && name !== 'style')
17003 attribs[name] = dom.getAttrib(node, name);
17004 });
17005
17006 return attribs;
17007 };
17008
17009 function compareObjects(obj1, obj2) {
17010 var value, name;
17011
17012 for (name in obj1) {
17013 // Obj1 has item obj2 doesn't have
17014 if (obj1.hasOwnProperty(name)) {
17015 value = obj2[name];
17016
17017 // Obj2 doesn't have obj1 item
17018 if (value === undefined)
17019 return FALSE;
17020
17021 // Obj2 item has a different value
17022 if (obj1[name] != value)
17023 return FALSE;
17024
17025 // Delete similar value
17026 delete obj2[name];
17027 }
17028 }
17029
17030 // Check if obj 2 has something obj 1 doesn't have
17031 for (name in obj2) {
17032 // Obj2 has item obj1 doesn't have
17033 if (obj2.hasOwnProperty(name))
17034 return FALSE;
17035 }
17036
17037 return TRUE;
17038 };
17039
17040 // Attribs are not the same
17041 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
17042 return FALSE;
17043
17044 // Styles are not the same
17045 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
17046 return FALSE;
17047
17048 return TRUE;
17049 };
17050
17051 // Check if next/prev exists and that they are elements
17052 if (prev && next) {
17053 function findElementSibling(node, sibling_name) {
17054 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
17055 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
17056 return node;
17057
17058 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
17059 return sibling;
17060 }
17061
17062 return node;
17063 };
17064
17065 // If previous sibling is empty then jump over it
17066 prev = findElementSibling(prev, 'previousSibling');
17067 next = findElementSibling(next, 'nextSibling');
17068
17069 // Compare next and previous nodes
17070 if (compareElements(prev, next)) {
17071 // Append nodes between
17072 for (sibling = prev.nextSibling; sibling && sibling != next;) {
17073 tmpSibling = sibling;
17074 sibling = sibling.nextSibling;
17075 prev.appendChild(tmpSibling);
17076 }
17077
17078 // Remove next node
17079 dom.remove(next);
17080
17081 // Move children into prev node
17082 each(tinymce.grep(next.childNodes), function(node) {
17083 prev.appendChild(node);
17084 });
17085
17086 return prev;
17087 }
17088 }
17089
17090 return next;
17091 };
17092
17093 function isTextBlock(name) {
17094 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
17095 };
17096
17097 function getContainer(rng, start) {
17098 var container, offset, lastIdx, walker;
17099
17100 container = rng[start ? 'startContainer' : 'endContainer'];
17101 offset = rng[start ? 'startOffset' : 'endOffset'];
17102
17103 if (container.nodeType == 1) {
17104 lastIdx = container.childNodes.length - 1;
17105
17106 if (!start && offset)
17107 offset--;
17108
17109 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17110 }
17111
17112 // If start text node is excluded then walk to the next node
17113 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
17114 container = new TreeWalker(container, ed.getBody()).next() || container;
17115 }
17116
17117 // If end text node is excluded then walk to the previous node
17118 if (container.nodeType === 3 && !start && offset == 0) {
17119 container = new TreeWalker(container, ed.getBody()).prev() || container;
17120 }
17121
17122 return container;
17123 };
17124
17125 function performCaretAction(type, name, vars) {
17126 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
17127
17128 // Creates a caret container bogus element
17129 function createCaretContainer(fill) {
17130 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
17131
17132 if (fill) {
17133 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
17134 }
17135
17136 return caretContainer;
17137 };
17138
17139 function isCaretContainerEmpty(node, nodes) {
17140 while (node) {
17141 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
17142 return false;
17143 }
17144
17145 // Collect nodes
17146 if (nodes && node.nodeType === 1) {
17147 nodes.push(node);
17148 }
17149
17150 node = node.firstChild;
17151 }
17152
17153 return true;
17154 };
17155
17156 // Returns any parent caret container element
17157 function getParentCaretContainer(node) {
17158 while (node) {
17159 if (node.id === caretContainerId) {
17160 return node;
17161 }
17162
17163 node = node.parentNode;
17164 }
17165 };
17166
17167 // Finds the first text node in the specified node
17168 function findFirstTextNode(node) {
17169 var walker;
17170
17171 if (node) {
17172 walker = new TreeWalker(node, node);
17173
17174 for (node = walker.current(); node; node = walker.next()) {
17175 if (node.nodeType === 3) {
17176 return node;
17177 }
17178 }
17179 }
17180 };
17181
17182 // Removes the caret container for the specified node or all on the current document
17183 function removeCaretContainer(node, move_caret) {
17184 var child, rng;
17185
17186 if (!node) {
17187 node = getParentCaretContainer(selection.getStart());
17188
17189 if (!node) {
17190 while (node = dom.get(caretContainerId)) {
17191 removeCaretContainer(node, false);
17192 }
17193 }
17194 } else {
17195 rng = selection.getRng(true);
17196
17197 if (isCaretContainerEmpty(node)) {
17198 if (move_caret !== false) {
17199 rng.setStartBefore(node);
17200 rng.setEndBefore(node);
17201 }
17202
17203 dom.remove(node);
17204 } else {
17205 child = findFirstTextNode(node);
17206
17207 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
17208 child = child.deleteData(0, 1);
17209 }
17210
17211 dom.remove(node, 1);
17212 }
17213
17214 selection.setRng(rng);
17215 }
17216 };
17217
17218 // Applies formatting to the caret postion
17219 function applyCaretFormat() {
17220 var rng, caretContainer, textNode, offset, bookmark, container, text;
17221
17222 rng = selection.getRng(true);
17223 offset = rng.startOffset;
17224 container = rng.startContainer;
17225 text = container.nodeValue;
17226
17227 caretContainer = getParentCaretContainer(selection.getStart());
17228 if (caretContainer) {
17229 textNode = findFirstTextNode(caretContainer);
17230 }
17231
17232 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
17233 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
17234 // Get bookmark of caret position
17235 bookmark = selection.getBookmark();
17236
17237 // Collapse bookmark range (WebKit)
17238 rng.collapse(true);
17239
17240 // Expand the range to the closest word and split it at those points
17241 rng = expandRng(rng, get(name));
17242 rng = rangeUtils.split(rng);
17243
17244 // Apply the format to the range
17245 apply(name, vars, rng);
17246
17247 // Move selection back to caret position
17248 selection.moveToBookmark(bookmark);
17249 } else {
17250 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
17251 caretContainer = createCaretContainer(true);
17252 textNode = caretContainer.firstChild;
17253
17254 rng.insertNode(caretContainer);
17255 offset = 1;
17256
17257 apply(name, vars, caretContainer);
17258 } else {
17259 apply(name, vars, caretContainer);
17260 }
17261
17262 // Move selection to text node
17263 selection.setCursorLocation(textNode, offset);
17264 }
17265 };
17266
17267 function removeCaretFormat() {
17268 var rng = selection.getRng(true), container, offset, bookmark,
17269 hasContentAfter, node, formatNode, parents = [], i, caretContainer;
17270
17271 container = rng.startContainer;
17272 offset = rng.startOffset;
17273 node = container;
17274
17275 if (container.nodeType == 3) {
17276 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17277 hasContentAfter = true;
17278 }
17279
17280 node = node.parentNode;
17281 }
17282
17283 while (node) {
17284 if (matchNode(node, name, vars)) {
17285 formatNode = node;
17286 break;
17287 }
17288
17289 if (node.nextSibling) {
17290 hasContentAfter = true;
17291 }
17292
17293 parents.push(node);
17294 node = node.parentNode;
17295 }
17296
17297 // Node doesn't have the specified format
17298 if (!formatNode) {
17299 return;
17300 }
17301
17302 // Is there contents after the caret then remove the format on the element
17303 if (hasContentAfter) {
17304 // Get bookmark of caret position
17305 bookmark = selection.getBookmark();
17306
17307 // Collapse bookmark range (WebKit)
17308 rng.collapse(true);
17309
17310 // Expand the range to the closest word and split it at those points
17311 rng = expandRng(rng, get(name), true);
17312 rng = rangeUtils.split(rng);
17313
17314 // Remove the format from the range
17315 remove(name, vars, rng);
17316
17317 // Move selection back to caret position
17318 selection.moveToBookmark(bookmark);
17319 } else {
17320 caretContainer = createCaretContainer();
17321
17322 node = caretContainer;
17323 for (i = parents.length - 1; i >= 0; i--) {
17324 node.appendChild(dom.clone(parents[i], false));
17325 node = node.firstChild;
17326 }
17327
17328 // Insert invisible character into inner most format element
17329 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
17330 node = node.firstChild;
17331
17332 // Insert caret container after the formated node
17333 dom.insertAfter(caretContainer, formatNode);
17334
17335 // Move selection to text node
17336 selection.setCursorLocation(node, 1);
17337 }
17338 };
17339
17340 // Only bind the caret events once
17341 if (!self._hasCaretEvents) {
17342 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
17343 ed.onBeforeGetContent.addToTop(function() {
17344 var nodes = [], i;
17345
17346 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
17347 // Mark children
17348 i = nodes.length;
17349 while (i--) {
17350 dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
17351 }
17352 }
17353 });
17354
17355 // Remove caret container on mouse up and on key up
17356 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
17357 ed[name].addToTop(function() {
17358 removeCaretContainer();
17359 });
17360 });
17361
17362 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
17363 ed.onKeyDown.addToTop(function(ed, e) {
17364 var keyCode = e.keyCode;
17365
17366 if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
17367 removeCaretContainer(getParentCaretContainer(selection.getStart()));
17368 }
17369 });
17370
17371 self._hasCaretEvents = true;
17372 }
17373
17374 // Do apply or remove caret format
17375 if (type == "apply") {
17376 applyCaretFormat();
17377 } else {
17378 removeCaretFormat();
17379 }
17380 };
17381
17382 function moveStart(rng) {
17383 var container = rng.startContainer,
17384 offset = rng.startOffset,
17385 walker, node, nodes, tmpNode;
17386
17387 // Convert text node into index if possible
17388 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
17389 container = container.parentNode;
17390 offset = nodeIndex(container) + 1;
17391 }
17392
17393 // Move startContainer/startOffset in to a suitable node
17394 if (container.nodeType == 1) {
17395 nodes = container.childNodes;
17396 container = nodes[Math.min(offset, nodes.length - 1)];
17397 walker = new TreeWalker(container);
17398
17399 // If offset is at end of the parent node walk to the next one
17400 if (offset > nodes.length - 1)
17401 walker.next();
17402
17403 for (node = walker.current(); node; node = walker.next()) {
17404 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
17405 // IE has a "neat" feature where it moves the start node into the closest element
17406 // we can avoid this by inserting an element before it and then remove it after we set the selection
17407 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
17408 node.parentNode.insertBefore(tmpNode, node);
17409
17410 // Set selection and remove tmpNode
17411 rng.setStart(node, 0);
17412 selection.setRng(rng);
17413 dom.remove(tmpNode);
17414
17415 return;
17416 }
17417 }
17418 }
17419 };
17420
17421 };
17422 })(tinymce);
17423
17424 tinymce.onAddEditor.add(function(tinymce, ed) {
17425 var filters, fontSizes, dom, settings = ed.settings;
17426
17427 if (settings.inline_styles) {
17428 fontSizes = tinymce.explode(settings.font_size_legacy_values);
17429
17430 function replaceWithSpan(node, styles) {
17431 tinymce.each(styles, function(value, name) {
17432 if (value)
17433 dom.setStyle(node, name, value);
17434 });
17435
17436 dom.rename(node, 'span');
17437 };
17438
17439 filters = {
17440 font : function(dom, node) {
17441 replaceWithSpan(node, {
17442 backgroundColor : node.style.backgroundColor,
17443 color : node.color,
17444 fontFamily : node.face,
17445 fontSize : fontSizes[parseInt(node.size) - 1]
17446 });
17447 },
17448
17449 u : function(dom, node) {
17450 replaceWithSpan(node, {
17451 textDecoration : 'underline'
17452 });
17453 },
17454
17455 strike : function(dom, node) {
17456 replaceWithSpan(node, {
17457 textDecoration : 'line-through'
17458 });
17459 }
17460 };
17461
17462 function convert(editor, params) {
17463 dom = editor.dom;
17464
17465 if (settings.convert_fonts_to_spans) {
17466 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
17467 filters[node.nodeName.toLowerCase()](ed.dom, node);
17468 });
17469 }
17470 };
17471
17472 ed.onPreProcess.add(convert);
17473 ed.onSetContent.add(convert);
17474
17475 ed.onInit.add(function() {
17476 ed.selection.onSetContent.add(convert);
17477 });
17478 }
17479 });
17480