PageSourceSearch

https://onecnctraining.com/lib/editor/tinymce/tiny_mce/3.5.11/tiny_mce_src.js

js onecnctraining.com collected 2026-10-08 07:32:54 UTC 506,071 bytes, 19,110 lines download raw bytes

1// FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
2(function(win) {
3	var whiteSpaceRe = /^\s*|\s*$/g,
4		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
5
6	var tinymce = {
7		majorVersion : '3',
8
9		minorVersion : '5.11',
10
11		releaseDate : '2014-05-08',
12
13		_init : function() {
14			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
15
16			t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1);
17
18			t.isOpera = win.opera && opera.buildNumber;
19
20			t.isWebKit = /WebKit/.test(ua);
21
22			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11;
23
24			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
25
26			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
27
28			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
29
30			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
31
32			t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua);
33
34			t.isMac = ua.indexOf('Mac') != -1;
35
36			t.isAir = /adobeair/i.test(ua);
37
38			t.isIDevice = /(iPad|iPhone)/.test(ua);
39			
40			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
41
42			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
43			if (win.tinyMCEPreInit) {
44				t.suffix = tinyMCEPreInit.suffix;
45				t.baseURL = tinyMCEPreInit.base;
46				t.query = tinyMCEPreInit.query;
47				return;
48			}
49
50			// Get suffix and base
51			t.suffix = '';
52
53			// If base element found, add that infront of baseURL
54			nl = d.getElementsByTagName('base');
55			for (i=0; i<nl.length; i++) {
56				v = nl[i].href;
57				if (v) {
58					// Host only value like http://site.com or http://site.com:8008
59					if (/^https?:\/\/[^\/]+$/.test(v))
60						v += '/';
61
62					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
63				}
64			}
65
66			function getBase(n) {
67				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
68					if (/_(src|dev)\.js/g.test(n.src))
69						t.suffix = '_src';
70
71					if ((p = n.src.indexOf('?')) != -1)
72						t.query = n.src.substring(p + 1);
73
74					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
75
76					// If path to script is relative and a base href was found add that one infront
77					// the src property will always be an absolute one on non IE browsers and IE 8
78					// so this logic will basically only be executed on older IE versions
79					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
80						t.baseURL = base + t.baseURL;
81
82					return t.baseURL;
83				}
84
85				return null;
86			};
87
88			// Check document
89			nl = d.getElementsByTagName('script');
90			for (i=0; i<nl.length; i++) {
91				if (getBase(nl[i]))
92					return;
93			}
94
95			// Check head
96			n = d.getElementsByTagName('head')[0];
97			if (n) {
98				nl = n.getElementsByTagName('script');
99				for (i=0; i<nl.length; i++) {
100					if (getBase(nl[i]))
101						return;
102				}
103			}
104
105			return;
106		},
107
108		is : function(o, t) {
109			if (!t)
110				return o !== undef;
111
112			if (t == 'array' && tinymce.isArray(o))
113				return true;
114
115			return typeof(o) == t;
116		},
117
118		isArray: Array.isArray || function(obj) {
119			return Object.prototype.toString.call(obj) === "[object Array]";
120		},
121
122		makeMap : function(items, delim, map) {
123			var i;
124
125			items = items || [];
126			delim = delim || ',';
127
128			if (typeof(items) == "string")
129				items = items.split(delim);
130
131			map = map || {};
132
133			i = items.length;
134			while (i--)
135				map[items[i]] = {};
136
137			return map;
138		},
139
140		each : function(o, cb, s) {
141			var n, l;
142
143			if (!o)
144				return 0;
145
146			s = s || o;
147
148			if (o.length !== undef) {
149				// Indexed arrays, needed for Safari
150				for (n=0, l = o.length; n < l; n++) {
151					if (cb.call(s, o[n], n, o) === false)
152						return 0;
153				}
154			} else {
155				// Hashtables
156				for (n in o) {
157					if (o.hasOwnProperty(n)) {
158						if (cb.call(s, o[n], n, o) === false)
159							return 0;
160					}
161				}
162			}
163
164			return 1;
165		},
166
167
168		map : function(a, f) {
169			var o = [];
170
171			tinymce.each(a, function(v) {
172				o.push(f(v));
173			});
174
175			return o;
176		},
177
178		grep : function(a, f) {
179			var o = [];
180
181			tinymce.each(a, function(v) {
182				if (!f || f(v))
183					o.push(v);
184			});
185
186			return o;
187		},
188
189		inArray : function(a, v) {
190			var i, l;
191
192			if (a) {
193				for (i = 0, l = a.length; i < l; i++) {
194					if (a[i] === v)
195						return i;
196				}
197			}
198
199			return -1;
200		},
201
202		extend : function(obj, ext) {
203			var i, l, name, args = arguments, value;
204
205			for (i = 1, l = args.length; i < l; i++) {
206				ext = args[i];
207				for (name in ext) {
208					if (ext.hasOwnProperty(name)) {
209						value = ext[name];
210
211						if (value !== undef) {
212							obj[name] = value;
213						}
214					}
215				}
216			}
217
218			return obj;
219		},
220
221
222		trim : function(s) {
223			return (s ? '' + s : '').replace(whiteSpaceRe, '');
224		},
225
226		create : function(s, p, root) {
227			var t = this, sp, ns, cn, scn, c, de = 0;
228
229			// Parse : <prefix> <class>:<super class>
230			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
231			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
232
233			// Create namespace for new class
234			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
235
236			// Class already exists
237			if (ns[cn])
238				return;
239
240			// Make pure static class
241			if (s[2] == 'static') {
242				ns[cn] = p;
243
244				if (this.onCreate)
245					this.onCreate(s[2], s[3], ns[cn]);
246
247				return;
248			}
249
250			// Create default constructor
251			if (!p[cn]) {
252				p[cn] = function() {};
253				de = 1;
254			}
255
256			// Add constructor and methods
257			ns[cn] = p[cn];
258			t.extend(ns[cn].prototype, p);
259
260			// Extend
261			if (s[5]) {
262				sp = t.resolve(s[5]).prototype;
263				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
264
265				// Extend constructor
266				c = ns[cn];
267				if (de) {
268					// Add passthrough constructor
269					ns[cn] = function() {
270						return sp[scn].apply(this, arguments);
271					};
272				} else {
273					// Add inherit constructor
274					ns[cn] = function() {
275						this.parent = sp[scn];
276						return c.apply(this, arguments);
277					};
278				}
279				ns[cn].prototype[cn] = ns[cn];
280
281				// Add super methods
282				t.each(sp, function(f, n) {
283					ns[cn].prototype[n] = sp[n];
284				});
285
286				// Add overridden methods
287				t.each(p, function(f, n) {
288					// Extend methods if needed
289					if (sp[n]) {
290						ns[cn].prototype[n] = function() {
291							this.parent = sp[n];
292							return f.apply(this, arguments);
293						};
294					} else {
295						if (n != cn)
296							ns[cn].prototype[n] = f;
297					}
298				});
299			}
300
301			// Add static methods
302			t.each(p['static'], function(f, n) {
303				ns[cn][n] = f;
304			});
305
306			if (this.onCreate)
307				this.onCreate(s[2], s[3], ns[cn].prototype);
308		},
309
310		walk : function(o, f, n, s) {
311			s = s || this;
312
313			if (o) {
314				if (n)
315					o = o[n];
316
317				tinymce.each(o, function(o, i) {
318					if (f.call(s, o, i, n) === false)
319						return false;
320
321					tinymce.walk(o, f, n, s);
322				});
323			}
324		},
325
326		createNS : function(n, o) {
327			var i, v;
328
329			o = o || win;
330
331			n = n.split('.');
332			for (i=0; i<n.length; i++) {
333				v = n[i];
334
335				if (!o[v])
336					o[v] = {};
337
338				o = o[v];
339			}
340
341			return o;
342		},
343
344		resolve : function(n, o) {
345			var i, l;
346
347			o = o || win;
348
349			n = n.split('.');
350			for (i = 0, l = n.length; i < l; i++) {
351				o = o[n[i]];
352
353				if (!o)
354					break;
355			}
356
357			return o;
358		},
359
360		addUnload : function(f, s) {
361			var t = this, unload;
362
363			unload = function() {
364				var li = t.unloads, o, n;
365
366				if (li) {
367					// Call unload handlers
368					for (n in li) {
369						o = li[n];
370
371						if (o && o.func)
372							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
373					}
374
375					// Detach unload function
376					if (win.detachEvent) {
377						win.detachEvent('onbeforeunload', fakeUnload);
378						win.detachEvent('onunload', unload);
379					} else if (win.removeEventListener)
380						win.removeEventListener('unload', unload, false);
381
382					// Destroy references
383					t.unloads = o = li = w = unload = 0;
384
385					// Run garbarge collector on IE
386					if (win.CollectGarbage)
387						CollectGarbage();
388				}
389			};
390
391			function fakeUnload() {
392				var d = document;
393
394				function stop() {
395					// Prevent memory leak
396					d.detachEvent('onstop', stop);
397
398					// Call unload handler
399					if (unload)
400						unload();
401
402					d = 0;
403				};
404
405				// Is there things still loading, then do some magic
406				if (d.readyState == 'interactive') {
407					// Fire unload when the currently loading page is stopped
408					if (d)
409						d.attachEvent('onstop', stop);
410
411					// Remove onstop listener after a while to prevent the unload function
412					// to execute if the user presses cancel in an onbeforeunload
413					// confirm dialog and then presses the browser stop button
414					win.setTimeout(function() {
415						if (d)
416							d.detachEvent('onstop', stop);
417					}, 0);
418				}
419			};
420
421			f = {func : f, scope : s || this};
422
423			if (!t.unloads) {
424				// Attach unload handler
425				if (win.attachEvent) {
426					win.attachEvent('onunload', unload);
427					win.attachEvent('onbeforeunload', fakeUnload);
428				} else if (win.addEventListener)
429					win.addEventListener('unload', unload, false);
430
431				// Setup initial unload handler array
432				t.unloads = [f];
433			} else
434				t.unloads.push(f);
435
436			return f;
437		},
438
439		removeUnload : function(f) {
440			var u = this.unloads, r = null;
441
442			tinymce.each(u, function(o, i) {
443				if (o && o.func == f) {
444					u.splice(i, 1);
445					r = f;
446					return false;
447				}
448			});
449
450			return r;
451		},
452
453		explode : function(s, d) {
454			if (!s || tinymce.is(s, 'array')) {
455				return s;
456			}
457
458			return tinymce.map(s.split(d || ','), tinymce.trim);
459		},
460
461		_addVer : function(u) {
462			var v;
463
464			if (!this.query)
465				return u;
466
467			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
468
469			if (u.indexOf('#') == -1)
470				return u + v;
471
472			return u.replace('#', v + '#');
473		},
474
475		// Fix function for IE 9 where regexps isn't working correctly
476		// Todo: remove me once MS fixes the bug
477		_replace : function(find, replace, str) {
478			// On IE9 we have to fake $x replacement
479			if (isRegExpBroken) {
480				return str.replace(find, function() {
481					var val = replace, args = arguments, i;
482
483					for (i = 0; i < args.length - 2; i++) {
484						if (args[i] === undef) {
485							val = val.replace(new RegExp('\\$' + i, 'g'), '');
486						} else {
487							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
488						}
489					}
490
491					return val;
492				});
493			}
494
495			return str.replace(find, replace);
496		}
497
498		};
499
500	// Initialize the API
501	tinymce._init();
502
503	// Expose tinymce namespace to the global namespace (window)
504	win.tinymce = win.tinyMCE = tinymce;
505
506	// Describe the different namespaces
507
508	})(window);
509tinymce.create('tinymce.util.Dispatcher', {
510	scope : null,
511	listeners : null,
512	inDispatch: false,
513
514	Dispatcher : function(scope) {
515		this.scope = scope || this;
516		this.listeners = [];
517	},
518
519	add : function(callback, scope) {
520		this.listeners.push({cb : callback, scope : scope || this.scope});
521
522		return callback;
523	},
524
525	addToTop : function(callback, scope) {
526		var self = this, listener = {cb : callback, scope : scope || self.scope};
527
528		// Create new listeners if addToTop is executed in a dispatch loop
529		if (self.inDispatch) {
530			self.listeners = [listener].concat(self.listeners);
531		} else {
532			self.listeners.unshift(listener);
533		}
534
535		return callback;
536	},
537
538	remove : function(callback) {
539		var listeners = this.listeners, output = null;
540
541		tinymce.each(listeners, function(listener, i) {
542			if (callback == listener.cb) {
543				output = listener;
544				listeners.splice(i, 1);
545				return false;
546			}
547		});
548
549		return output;
550	},
551
552	dispatch : function() {
553		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
554
555		self.inDispatch = true;
556		
557		// Needs to be a real loop since the listener count might change while looping
558		// And this is also more efficient
559		for (i = 0; i < listeners.length; i++) {
560			listener = listeners[i];
561			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
562
563			if (returnValue === false)
564				break;
565		}
566
567		self.inDispatch = false;
568
569		return returnValue;
570	}
571
572	});
573(function() {
574	var each = tinymce.each;
575
576	tinymce.create('tinymce.util.URI', {
577		URI : function(u, s) {
578			var t = this, o, a, b, base_url;
579
580			// Trim whitespace
581			u = tinymce.trim(u);
582
583			// Default settings
584			s = t.settings = s || {};
585
586			// Strange app protocol that isn't http/https or local anchor
587			// For example: mailto,skype,tel etc.
588			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
589				t.source = u;
590				return;
591			}
592
593			// Absolute path with no host, fake host and protocol
594			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
595				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
596
597			// Relative path http:// or protocol relative //path
598			if (!/^[\w\-]*:?\/\//.test(u)) {
599				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
600				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
601			}
602
603			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
604			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
605			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
606			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
607				var s = u[i];
608
609				// Zope 3 workaround, they use @@something
610				if (s)
611					s = s.replace(/\(mce_at\)/g, '@@');
612
613				t[v] = s;
614			});
615
616			b = s.base_uri;
617			if (b) {
618				if (!t.protocol)
619					t.protocol = b.protocol;
620
621				if (!t.userInfo)
622					t.userInfo = b.userInfo;
623
624				if (!t.port && t.host === 'mce_host')
625					t.port = b.port;
626
627				if (!t.host || t.host === 'mce_host')
628					t.host = b.host;
629
630				t.source = '';
631			}
632
633			//t.path = t.path || '/';
634		},
635
636		setPath : function(p) {
637			var t = this;
638
639			p = /^(.*?)\/?(\w+)?$/.exec(p);
640
641			// Update path parts
642			t.path = p[0];
643			t.directory = p[1];
644			t.file = p[2];
645
646			// Rebuild source
647			t.source = '';
648			t.getURI();
649		},
650
651		toRelative : function(u) {
652			var t = this, o;
653
654			if (u === "./")
655				return u;
656
657			u = new tinymce.util.URI(u, {base_uri : t});
658
659			// Not on same domain/port or protocol
660			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
661				return u.getURI();
662
663			var tu = t.getURI(), uu = u.getURI();
664			
665			// Allow usage of the base_uri when relative_urls = true
666			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
667				return tu;
668
669			o = t.toRelPath(t.path, u.path);
670
671			// Add query
672			if (u.query)
673				o += '?' + u.query;
674
675			// Add anchor
676			if (u.anchor)
677				o += '#' + u.anchor;
678
679			return o;
680		},
681	
682		toAbsolute : function(u, nh) {
683			u = new tinymce.util.URI(u, {base_uri : this});
684
685			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
686		},
687
688		toRelPath : function(base, path) {
689			var items, bp = 0, out = '', i, l;
690
691			// Split the paths
692			base = base.substring(0, base.lastIndexOf('/'));
693			base = base.split('/');
694			items = path.split('/');
695
696			if (base.length >= items.length) {
697				for (i = 0, l = base.length; i < l; i++) {
698					if (i >= items.length || base[i] != items[i]) {
699						bp = i + 1;
700						break;
701					}
702				}
703			}
704
705			if (base.length < items.length) {
706				for (i = 0, l = items.length; i < l; i++) {
707					if (i >= base.length || base[i] != items[i]) {
708						bp = i + 1;
709						break;
710					}
711				}
712			}
713
714			if (bp === 1)
715				return path;
716
717			for (i = 0, l = base.length - (bp - 1); i < l; i++)
718				out += "../";
719
720			for (i = bp - 1, l = items.length; i < l; i++) {
721				if (i != bp - 1)
722					out += "/" + items[i];
723				else
724					out += items[i];
725			}
726
727			return out;
728		},
729
730		toAbsPath : function(base, path) {
731			var i, nb = 0, o = [], tr, outPath;
732
733			// Split paths
734			tr = /\/$/.test(path) ? '/' : '';
735			base = base.split('/');
736			path = path.split('/');
737
738			// Remove empty chunks
739			each(base, function(k) {
740				if (k)
741					o.push(k);
742			});
743
744			base = o;
745
746			// Merge relURLParts chunks
747			for (i = path.length - 1, o = []; i >= 0; i--) {
748				// Ignore empty or .
749				if (path[i].length === 0 || path[i] === ".")
750					continue;
751
752				// Is parent
753				if (path[i] === '..') {
754					nb++;
755					continue;
756				}
757
758				// Move up
759				if (nb > 0) {
760					nb--;
761					continue;
762				}
763
764				o.push(path[i]);
765			}
766
767			i = base.length - nb;
768
769			// If /a/b/c or /
770			if (i <= 0)
771				outPath = o.reverse().join('/');
772			else
773				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
774
775			// Add front / if it's needed
776			if (outPath.indexOf('/') !== 0)
777				outPath = '/' + outPath;
778
779			// Add traling / if it's needed
780			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
781				outPath += tr;
782
783			return outPath;
784		},
785
786		getURI : function(nh) {
787			var s, t = this;
788
789			// Rebuild source
790			if (!t.source || nh) {
791				s = '';
792
793				if (!nh) {
794					if (t.protocol)
795						s += t.protocol + '://';
796
797					if (t.userInfo)
798						s += t.userInfo + '@';
799
800					if (t.host)
801						s += t.host;
802
803					if (t.port)
804						s += ':' + t.port;
805				}
806
807				if (t.path)
808					s += t.path;
809
810				if (t.query)
811					s += '?' + t.query;
812
813				if (t.anchor)
814					s += '#' + t.anchor;
815
816				t.source = s;
817			}
818
819			return t.source;
820		}
821	});
822})();
823(function() {
824	var each = tinymce.each;
825
826	tinymce.create('static tinymce.util.Cookie', {
827		getHash : function(n) {
828			var v = this.get(n), h;
829
830			if (v) {
831				each(v.split('&'), function(v) {
832					v = v.split('=');
833					h = h || {};
834					h[unescape(v[0])] = unescape(v[1]);
835				});
836			}
837
838			return h;
839		},
840
841		setHash : function(n, v, e, p, d, s) {
842			var o = '';
843
844			each(v, function(v, k) {
845				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
846			});
847
848			this.set(n, o, e, p, d, s);
849		},
850
851		get : function(n) {
852			var c = document.cookie, e, p = n + "=", b;
853
854			// Strict mode
855			if (!c)
856				return;
857
858			b = c.indexOf("; " + p);
859
860			if (b == -1) {
861				b = c.indexOf(p);
862
863				if (b !== 0)
864					return null;
865			} else
866				b += 2;
867
868			e = c.indexOf(";", b);
869
870			if (e == -1)
871				e = c.length;
872
873			return unescape(c.substring(b + p.length, e));
874		},
875
876		set : function(n, v, e, p, d, s) {
877			document.cookie = n + "=" + escape(v) +
878				((e) ? "; expires=" + e.toGMTString() : "") +
879				((p) ? "; path=" + escape(p) : "") +
880				((d) ? "; domain=" + d : "") +
881				((s) ? "; secure" : "");
882		},
883
884		remove : function(name, path, domain) {
885			var date = new Date();
886
887			date.setTime(date.getTime() - 1000);
888
889			this.set(name, '', date, path, domain);
890		}
891	});
892})();
893(function() {
894	function serialize(o, quote) {
895		var i, v, t, name;
896
897		quote = quote || '"';
898
899		if (o == null)
900			return 'null';
901
902		t = typeof o;
903
904		if (t == 'string') {
905			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
906
907			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
908				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
909				if (quote === '"' && a === "'")
910					return a;
911
912				i = v.indexOf(b);
913
914				if (i + 1)
915					return '\\' + v.charAt(i + 1);
916
917				a = b.charCodeAt().toString(16);
918
919				return '\\u' + '0000'.substring(a.length) + a;
920			}) + quote;
921		}
922
923		if (t == 'object') {
924			if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
925					for (i=0, v = '['; i<o.length; i++)
926						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
927
928					return v + ']';
929				}
930
931				v = '{';
932
933				for (name in o) {
934					if (o.hasOwnProperty(name)) {
935						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
936					}
937				}
938
939				return v + '}';
940		}
941
942		return '' + o;
943	};
944
945	tinymce.util.JSON = {
946		serialize: serialize,
947
948		parse: function(s) {
949			try {
950				return eval('(' + s + ')');
951			} catch (ex) {
952				// Ignore
953			}
954		}
955
956		};
957})();
958tinymce.create('static tinymce.util.XHR', {
959	send : function(o) {
960		var x, t, w = window, c = 0;
961
962		function ready() {
963			if (!o.async || x.readyState == 4 || c++ > 10000) {
964				if (o.success && c < 10000 && x.status == 200)
965					o.success.call(o.success_scope, '' + x.responseText, x, o);
966				else if (o.error)
967					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
968
969				x = null;
970			} else
971				w.setTimeout(ready, 10);
972		};
973
974		// Default settings
975		o.scope = o.scope || this;
976		o.success_scope = o.success_scope || o.scope;
977		o.error_scope = o.error_scope || o.scope;
978		o.async = o.async === false ? false : true;
979		o.data = o.data || '';
980
981		function get(s) {
982			x = 0;
983
984			try {
985				x = new ActiveXObject(s);
986			} catch (ex) {
987			}
988
989			return x;
990		};
991
992		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
993
994		if (x) {
995			if (x.overrideMimeType)
996				x.overrideMimeType(o.content_type);
997
998			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
999
1000			if (o.content_type)
1001				x.setRequestHeader('Content-Type', o.content_type);
1002
1003			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1004
1005			x.send(o.data);
1006
1007			// Syncronous request
1008			if (!o.async)
1009				return ready();
1010
1011			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1012			t = w.setTimeout(ready, 10);
1013		}
1014	}
1015});
1016(function() {
1017	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1018
1019	tinymce.create('tinymce.util.JSONRequest', {
1020		JSONRequest : function(s) {
1021			this.settings = extend({
1022			}, s);
1023			this.count = 0;
1024		},
1025
1026		send : function(o) {
1027			var ecb = o.error, scb = o.success;
1028
1029			o = extend(this.settings, o);
1030
1031			o.success = function(c, x) {
1032				c = JSON.parse(c);
1033
1034				if (typeof(c) == 'undefined') {
1035					c = {
1036						error : 'JSON Parse error.'
1037					};
1038				}
1039
1040				if (c.error)
1041					ecb.call(o.error_scope || o.scope, c.error, x);
1042				else
1043					scb.call(o.success_scope || o.scope, c.result);
1044			};
1045
1046			o.error = function(ty, x) {
1047				if (ecb)
1048					ecb.call(o.error_scope || o.scope, ty, x);
1049			};
1050
1051			o.data = JSON.serialize({
1052				id : o.id || 'c' + (this.count++),
1053				method : o.method,
1054				params : o.params
1055			});
1056
1057			// JSON content type for Ruby on rails. Bug: #1883287
1058			o.content_type = 'application/json';
1059
1060			XHR.send(o);
1061		},
1062
1063		'static' : {
1064			sendRPC : function(o) {
1065				return new tinymce.util.JSONRequest().send(o);
1066			}
1067		}
1068	});
1069}());
1070(function(tinymce){
1071	tinymce.VK = {
1072		BACKSPACE: 8,
1073		DELETE: 46,
1074		DOWN: 40,
1075		ENTER: 13,
1076		LEFT: 37,
1077		RIGHT: 39,
1078		SPACEBAR: 32,
1079		TAB: 9,
1080		UP: 38,
1081
1082		modifierPressed: function (e) {
1083			return e.shiftKey || e.ctrlKey || e.altKey;
1084		},
1085
1086		metaKeyPressed: function(e) {
1087			// Check if ctrl or meta key is pressed also check if alt is false for Polish users
1088			return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;
1089		}
1090	};
1091})(tinymce);
1092tinymce.util.Quirks = function(editor) {
1093	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
1094		settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each;
1095
1096	function setEditorCommandState(cmd, state) {
1097		try {
1098			editor.getDoc().execCommand(cmd, false, state);
1099		} catch (ex) {
1100			// Ignore
1101		}
1102	}
1103
1104	function getDocumentMode() {
1105		var documentMode = editor.getDoc().documentMode;
1106
1107		return documentMode ? documentMode : 6;
1108	};
1109
1110	function isDefaultPrevented(e) {
1111		return e.isDefaultPrevented();
1112	};
1113
1114	function cleanupStylesWhenDeleting() {
1115		function removeMergedFormatSpans(isDelete) {
1116			var rng, blockElm, wrapperElm, bookmark, container, offset, elm;
1117
1118			function isAtStartOrEndOfElm() {
1119				if (container.nodeType == 3) {
1120					if (isDelete && offset == container.length) {
1121						return true;
1122					}
1123
1124					if (!isDelete && offset === 0) {
1125						return true;
1126					}
1127				}
1128			}
1129
1130			rng = selection.getRng();
1131			var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
1132
1133			if (!rng.collapsed) {
1134				isDelete = true;
1135			}
1136
1137			container = rng[(isDelete ? 'start' : 'end') + 'Container'];
1138			offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];
1139
1140			if (container.nodeType == 3) {
1141				blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1142
1143				// On delete clone the root span of the next block element
1144				if (isDelete) {
1145					blockElm = dom.getNext(blockElm, dom.isBlock);
1146				}
1147
1148				if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) {
1149					// Wrap children of block in a EM and let WebKit stick is
1150					// runtime styles junk into that EM
1151					wrapperElm = dom.create('em', {'id': '__mceDel'});
1152
1153					each(tinymce.grep(blockElm.childNodes), function(node) {
1154						wrapperElm.appendChild(node);
1155					});
1156
1157					blockElm.appendChild(wrapperElm);
1158				}
1159			}
1160
1161			// Do the backspace/delete action
1162			rng = dom.createRng();
1163			rng.setStart(tmpRng[0], tmpRng[1]);
1164			rng.setEnd(tmpRng[2], tmpRng[3]);
1165			selection.setRng(rng);
1166			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1167
1168			// Remove temp wrapper element
1169			if (wrapperElm) {
1170				bookmark = selection.getBookmark();
1171
1172				while (elm = dom.get('__mceDel')) {
1173					dom.remove(elm, true);
1174				}
1175
1176				selection.moveToBookmark(bookmark);
1177			}
1178		}
1179
1180		editor.onKeyDown.add(function(editor, e) {
1181			var isDelete;
1182
1183			isDelete = e.keyCode == DELETE;
1184			if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1185				e.preventDefault();
1186				removeMergedFormatSpans(isDelete);
1187			}
1188		});
1189
1190		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1191	};
1192	
1193	function emptyEditorWhenDeleting() {
1194		function serializeRng(rng) {
1195			var body = dom.create("body");
1196			var contents = rng.cloneContents();
1197			body.appendChild(contents);
1198			return selection.serializer.serialize(body, {format: 'html'});
1199		}
1200
1201		function allContentsSelected(rng) {
1202			var selection = serializeRng(rng);
1203
1204			var allRng = dom.createRng();
1205			allRng.selectNode(editor.getBody());
1206
1207			var allSelection = serializeRng(allRng);
1208			return selection === allSelection;
1209		}
1210
1211		editor.onKeyDown.add(function(editor, e) {
1212			var keyCode = e.keyCode, isCollapsed;
1213
1214			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1215			if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
1216				isCollapsed = editor.selection.isCollapsed();
1217
1218				// Selection is collapsed but the editor isn't empty
1219				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1220					return;
1221				}
1222
1223				// IE deletes all contents correctly when everything is selected
1224				if (tinymce.isIE && !isCollapsed) {
1225					return;
1226				}
1227
1228				// Selection isn't collapsed but not all the contents is selected
1229				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1230					return;
1231				}
1232
1233				// Manually empty the editor
1234				editor.setContent('');
1235				editor.selection.setCursorLocation(editor.getBody(), 0);
1236				editor.nodeChanged();
1237			}
1238		});
1239	};
1240
1241	function selectAll() {
1242		editor.onKeyDown.add(function(editor, e) {
1243			if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
1244				e.preventDefault();
1245				editor.execCommand('SelectAll');
1246			}
1247		});
1248	};
1249
1250	function inputMethodFocus() {
1251		if (!editor.settings.content_editable) {
1252			// Case 1 IME doesn't initialize if you focus the document
1253			dom.bind(editor.getDoc(), 'focusin', function(e) {
1254				selection.setRng(selection.getRng());
1255			});
1256
1257			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1258			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1259				if (e.target == editor.getDoc().documentElement) {
1260					editor.getWin().focus();
1261					selection.setRng(selection.getRng());
1262				}
1263			});
1264		}
1265	};
1266
1267	function removeHrOnBackspace() {
1268		editor.onKeyDown.add(function(editor, e) {
1269			if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
1270				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1271					var node = selection.getNode();
1272					var previousSibling = node.previousSibling;
1273
1274					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1275						dom.remove(previousSibling);
1276						tinymce.dom.Event.cancel(e);
1277					}
1278				}
1279			}
1280		})
1281	}
1282
1283	function focusBody() {
1284		// Fix for a focus bug in FF 3.x where the body element
1285		// wouldn't get proper focus if the user clicked on the HTML element
1286		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1287			editor.onMouseDown.add(function(editor, e) {
1288				if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
1289					var body = editor.getBody();
1290
1291					// Blur the body it's focused but not correctly focused
1292					body.blur();
1293
1294					// Refocus the body after a little while
1295					setTimeout(function() {
1296						body.focus();
1297					}, 0);
1298				}
1299			});
1300		}
1301	};
1302
1303	function selectControlElements() {
1304		editor.onClick.add(function(editor, e) {
1305			e = e.target;
1306
1307			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1308			// WebKit can't even do simple things like selecting an image
1309			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1310			if (/^(IMG|HR)$/.test(e.nodeName)) {
1311				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1312			}
1313
1314			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1315				selection.select(e);
1316			}
1317
1318			editor.nodeChanged();
1319		});
1320	};
1321
1322	function removeStylesWhenDeletingAccrossBlockElements() {
1323		function getAttributeApplyFunction() {
1324			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1325
1326			return function() {
1327				var target = selection.getStart();
1328
1329				if (target !== editor.getBody()) {
1330					dom.setAttrib(target, "style", null);
1331
1332					each(template, function(attr) {
1333						target.setAttributeNode(attr.cloneNode(true));
1334					});
1335				}
1336			};
1337		}
1338
1339		function isSelectionAcrossElements() {
1340			return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
1341		}
1342
1343		function blockEvent(editor, e) {
1344			e.preventDefault();
1345			return false;
1346		}
1347
1348		editor.onKeyPress.add(function(editor, e) {
1349			var applyAttributes;
1350
1351			if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1352				applyAttributes = getAttributeApplyFunction();
1353				editor.getDoc().execCommand('delete', false, null);
1354				applyAttributes();
1355				e.preventDefault();
1356				return false;
1357			}
1358		});
1359
1360		dom.bind(editor.getDoc(), 'cut', function(e) {
1361			var applyAttributes;
1362
1363			if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
1364				applyAttributes = getAttributeApplyFunction();
1365				editor.onKeyUp.addToTop(blockEvent);
1366
1367				setTimeout(function() {
1368					applyAttributes();
1369					editor.onKeyUp.remove(blockEvent);
1370				}, 0);
1371			}
1372		});
1373	}
1374
1375	function selectionChangeNodeChanged() {
1376		var lastRng, selectionTimer;
1377
1378		dom.bind(editor.getDoc(), 'selectionchange', function() {
1379			if (selectionTimer) {
1380				clearTimeout(selectionTimer);
1381				selectionTimer = 0;
1382			}
1383
1384			selectionTimer = window.setTimeout(function() {
1385				var rng = selection.getRng();
1386
1387				// Compare the ranges to see if it was a real change or not
1388				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1389					editor.nodeChanged();
1390					lastRng = rng;
1391				}
1392			}, 50);
1393		});
1394	}
1395
1396	function ensureBodyHasRoleApplication() {
1397		document.body.setAttribute("role", "application");
1398	}
1399
1400	function disableBackspaceIntoATable() {
1401		editor.onKeyDown.add(function(editor, e) {
1402			if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
1403				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1404					var previousSibling = selection.getNode().previousSibling;
1405					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1406						return tinymce.dom.Event.cancel(e);
1407					}
1408				}
1409			}
1410		})
1411	}
1412
1413	function addNewLinesBeforeBrInPre() {
1414		// IE8+ rendering mode does the right thing with BR in PRE
1415		if (getDocumentMode() > 7) {
1416			return;
1417		}
1418
1419		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1420		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1421		setEditorCommandState('RespectVisibilityInDesign', true);
1422		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1423		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1424
1425		// Adds a \n before all BR elements in PRE to get them visual
1426		parser.addNodeFilter('pre', function(nodes, name) {
1427			var i = nodes.length, brNodes, j, brElm, sibling;
1428
1429			while (i--) {
1430				brNodes = nodes[i].getAll('br');
1431				j = brNodes.length;
1432				while (j--) {
1433					brElm = brNodes[j];
1434
1435					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1436					sibling = brElm.prev;
1437					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1438						sibling.value += '\n';
1439					} else {
1440						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1441					}
1442				}
1443			}
1444		});
1445
1446		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1447		serializer.addNodeFilter('pre', function(nodes, name) {
1448			var i = nodes.length, brNodes, j, brElm, sibling;
1449
1450			while (i--) {
1451				brNodes = nodes[i].getAll('br');
1452				j = brNodes.length;
1453				while (j--) {
1454					brElm = brNodes[j];
1455					sibling = brElm.prev;
1456					if (sibling && sibling.type == 3) {
1457						sibling.value = sibling.value.replace(/\r?\n$/, '');
1458					}
1459				}
1460			}
1461		});
1462	}
1463
1464	function removePreSerializedStylesWhenSelectingControls() {
1465		dom.bind(editor.getBody(), 'mouseup', function(e) {
1466			var value, node = selection.getNode();
1467
1468			// Moved styles to attributes on IMG eements
1469			if (node.nodeName == 'IMG') {
1470				// Convert style width to width attribute
1471				if (value = dom.getStyle(node, 'width')) {
1472					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1473					dom.setStyle(node, 'width', '');
1474				}
1475
1476				// Convert style height to height attribute
1477				if (value = dom.getStyle(node, 'height')) {
1478					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1479					dom.setStyle(node, 'height', '');
1480				}
1481			}
1482		});
1483	}
1484
1485	function keepInlineElementOnDeleteBackspace() {
1486		editor.onKeyDown.add(function(editor, e) {
1487			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1488
1489			isDelete = e.keyCode == DELETE;
1490			if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1491				rng = selection.getRng();
1492				container = rng.startContainer;
1493				offset = rng.startOffset;
1494				collapsed = rng.collapsed;
1495
1496				// Override delete if the start container is a text node and is at the beginning of text or
1497				// just before/after the last character to be deleted in collapsed mode
1498				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1499					// Edge case when deleting <p><b><img> |x</b></p>
1500					sibling = container.previousSibling;
1501					if (sibling && sibling.nodeName == "IMG") {
1502						return;
1503					}
1504
1505					nonEmptyElements = editor.schema.getNonEmptyElements();
1506
1507					// Prevent default logic since it's broken
1508					e.preventDefault();
1509
1510					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1511					brElm = dom.create('br', {id: '__tmp'});
1512					container.parentNode.insertBefore(brElm, container);
1513
1514					// Do the browser delete
1515					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1516
1517					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1518					container = selection.getRng().startContainer;
1519					sibling = container.previousSibling;
1520					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1521						dom.remove(sibling);
1522					}
1523
1524					// Remove the temp element we inserted
1525					dom.remove('__tmp');
1526				}
1527			}
1528		});
1529	}
1530
1531	function removeBlockQuoteOnBackSpace() {
1532		// Add block quote deletion handler
1533		editor.onKeyDown.add(function(editor, e) {
1534			var rng, container, offset, root, parent;
1535
1536			if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
1537				return;
1538			}
1539
1540			rng = selection.getRng();
1541			container = rng.startContainer;
1542			offset = rng.startOffset;
1543			root = dom.getRoot();
1544			parent = container;
1545
1546			if (!rng.collapsed || offset !== 0) {
1547				return;
1548			}
1549
1550			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1551				parent = parent.parentNode;
1552			}
1553
1554			// Is the cursor at the beginning of a blockquote?
1555			if (parent.tagName === 'BLOCKQUOTE') {
1556				// Remove the blockquote
1557				editor.formatter.toggle('blockquote', null, parent);
1558
1559				// Move the caret to the beginning of container
1560				rng = dom.createRng();
1561				rng.setStart(container, 0);
1562				rng.setEnd(container, 0);
1563				selection.setRng(rng);
1564			}
1565		});
1566	};
1567
1568	function setGeckoEditingOptions() {
1569		function setOpts() {
1570			editor._refreshContentEditable();
1571
1572			setEditorCommandState("StyleWithCSS", false);
1573			setEditorCommandState("enableInlineTableEditing", false);
1574
1575			if (!settings.object_resizing) {
1576				setEditorCommandState("enableObjectResizing", false);
1577			}
1578		};
1579
1580		if (!settings.readonly) {
1581			editor.onBeforeExecCommand.add(setOpts);
1582			editor.onMouseDown.add(setOpts);
1583		}
1584	};
1585
1586	function addBrAfterLastLinks() {
1587		function fixLinks(editor, o) {
1588			each(dom.select('a'), function(node) {
1589				var parentNode = node.parentNode, root = dom.getRoot();
1590
1591				if (parentNode.lastChild === node) {
1592					while (parentNode && !dom.isBlock(parentNode)) {
1593						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1594							return;
1595						}
1596
1597						parentNode = parentNode.parentNode;
1598					}
1599
1600					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1601				}
1602			});
1603		};
1604
1605		editor.onExecCommand.add(function(editor, cmd) {
1606			if (cmd === 'CreateLink') {
1607				fixLinks(editor);
1608			}
1609		});
1610
1611		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1612	};
1613
1614	function setDefaultBlockType() {
1615		if (settings.forced_root_block) {
1616			editor.onInit.add(function() {
1617				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1618			});
1619		}
1620	}
1621
1622	function removeGhostSelection() {
1623		function repaint(sender, args) {
1624			if (!sender || !args.initial) {
1625				editor.execCommand('mceRepaint');
1626			}
1627		};
1628
1629		editor.onUndo.add(repaint);
1630		editor.onRedo.add(repaint);
1631		editor.onSetContent.add(repaint);
1632	};
1633
1634	function deleteControlItemOnBackSpace() {
1635		editor.onKeyDown.add(function(editor, e) {
1636			var rng;
1637
1638			if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
1639				rng = editor.getDoc().selection.createRange();
1640				if (rng && rng.item) {
1641					e.preventDefault();
1642					editor.undoManager.beforeChange();
1643					dom.remove(rng.item(0));
1644					editor.undoManager.add();
1645				}
1646			}
1647		});
1648	};
1649
1650	function renderEmptyBlocksFix() {
1651		var emptyBlocksCSS;
1652
1653		// IE10+
1654		if (getDocumentMode() >= 10) {
1655			emptyBlocksCSS = '';
1656			each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1657				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1658			});
1659
1660			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1661		}
1662	};
1663
1664	function fakeImageResize() {
1665		var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio,
1666			resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc();
1667
1668		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1669			return;
1670		}
1671
1672		// Try disabling object resizing if WebKit implements resizing in the future
1673		setEditorCommandState("enableObjectResizing", false);
1674
1675		// Details about each resize handle how to scale etc
1676		resizeHandles = {
1677			// Name: x multiplier, y multiplier, delta size x, delta size y
1678			n: [.5, 0, 0, -1],
1679			e: [1, .5, 1, 0],
1680			s: [.5, 1, 0, 1],
1681			w: [0, .5, -1, 0],
1682			nw: [0, 0, -1, -1],
1683			ne: [1, 0, 1, -1],
1684			se: [1, 1, 1, 1],
1685			sw : [0, 1, -1, 1]
1686		};
1687
1688		function resizeElement(e) {
1689			var deltaX, deltaY;
1690
1691			// Calc new width/height
1692			deltaX = e.screenX - startX;
1693			deltaY = e.screenY - startY;
1694
1695			// Calc new size
1696			width = deltaX * selectedHandle[2] + startW;
1697			height = deltaY * selectedHandle[3] + startH;
1698
1699			// Never scale down lower than 5 pixels
1700			width = width < 5 ? 5 : width;
1701			height = height < 5 ? 5 : height;
1702
1703			// Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
1704			if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
1705				width = Math.round(height / ratio);
1706				height = Math.round(width * ratio);
1707			}
1708
1709			// Update ghost size
1710			dom.setStyles(selectedElmGhost, {
1711				width: width,
1712				height: height
1713			});
1714
1715			// Update ghost X position if needed
1716			if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
1717				dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
1718			}
1719
1720			// Update ghost Y position if needed
1721			if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
1722				dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
1723			}
1724		}
1725
1726		function endResize() {
1727			function setSizeProp(name, value) {
1728				if (value) {
1729					// Resize by using style or attribute
1730					if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
1731						dom.setStyle(selectedElm, name, value);
1732					} else {
1733						dom.setAttrib(selectedElm, name, value);
1734					}
1735				}
1736			}
1737
1738			// Set width/height properties
1739			setSizeProp('width', width);
1740			setSizeProp('height', height);
1741
1742			dom.unbind(editableDoc, 'mousemove', resizeElement);
1743			dom.unbind(editableDoc, 'mouseup', endResize);
1744
1745			if (rootDocument != editableDoc) {
1746				dom.unbind(rootDocument, 'mousemove', resizeElement);
1747				dom.unbind(rootDocument, 'mouseup', endResize);
1748			}
1749
1750			// Remove ghost and update resize handle positions
1751			dom.remove(selectedElmGhost);
1752			showResizeRect(selectedElm);
1753		}
1754
1755		function showResizeRect(targetElm) {
1756			var position, targetWidth, targetHeight;
1757
1758			hideResizeRect();
1759
1760			// Get position and size of target
1761			position = dom.getPos(targetElm);
1762			selectedElmX = position.x;
1763			selectedElmY = position.y;
1764			targetWidth = targetElm.offsetWidth;
1765			targetHeight = targetElm.offsetHeight;
1766
1767			// Reset width/height if user selects a new image/table
1768			if (selectedElm != targetElm) {
1769				selectedElm = targetElm;
1770				width = height = 0;
1771			}
1772
1773			each(resizeHandles, function(handle, name) {
1774				var handleElm;
1775
1776				// Get existing or render resize handle
1777				handleElm = dom.get('mceResizeHandle' + name);
1778				if (!handleElm) {
1779					handleElm = dom.add(editableDoc.documentElement, 'div', {
1780						id: 'mceResizeHandle' + name,
1781						'class': 'mceResizeHandle',
1782						style: 'cursor:' + name + '-resize; margin:0; padding:0'
1783					});
1784
1785					dom.bind(handleElm, 'mousedown', function(e) {
1786						e.preventDefault();
1787
1788						endResize();
1789
1790						startX = e.screenX;
1791						startY = e.screenY;
1792						startW = selectedElm.clientWidth;
1793						startH = selectedElm.clientHeight;
1794						ratio = startH / startW;
1795						selectedHandle = handle;
1796
1797						selectedElmGhost = selectedElm.cloneNode(true);
1798						dom.addClass(selectedElmGhost, 'mceClonedResizable');
1799						dom.setStyles(selectedElmGhost, {
1800							left: selectedElmX,
1801							top: selectedElmY,
1802							margin: 0
1803						});
1804
1805						editableDoc.documentElement.appendChild(selectedElmGhost);
1806
1807						dom.bind(editableDoc, 'mousemove', resizeElement);
1808						dom.bind(editableDoc, 'mouseup', endResize);
1809
1810						if (rootDocument != editableDoc) {
1811							dom.bind(rootDocument, 'mousemove', resizeElement);
1812							dom.bind(rootDocument, 'mouseup', endResize);
1813						}
1814					});
1815				} else {
1816					dom.show(handleElm);
1817				}
1818
1819				// Position element
1820				dom.setStyles(handleElm, {
1821					left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
1822					top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
1823				});
1824			});
1825
1826			// Only add resize rectangle on WebKit and only on images
1827			if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {
1828				selectedElm.setAttribute('data-mce-selected', '1');
1829			}
1830		}
1831
1832		function hideResizeRect() {
1833			if (selectedElm) {
1834				selectedElm.removeAttribute('data-mce-selected');
1835			}
1836
1837			for (var name in resizeHandles) {
1838				dom.hide('mceResizeHandle' + name);
1839			}
1840		}
1841
1842		// Add CSS for resize handles, cloned element and selected
1843		editor.contentStyles.push(
1844			'.mceResizeHandle {' +
1845				'position: absolute;' +
1846				'border: 1px solid black;' +
1847				'background: #FFF;' +
1848				'width: 5px;' +
1849				'height: 5px;' +
1850				'z-index: 10000' +
1851			'}' +
1852			'.mceResizeHandle:hover {' +
1853				'background: #000' +
1854			'}' +
1855			'img[data-mce-selected] {' +
1856				'outline: 1px solid black' +
1857			'}' +
1858			'img.mceClonedResizable, table.mceClonedResizable {' +
1859				'position: absolute;' +
1860				'outline: 1px dashed black;' +
1861				'opacity: .5;' +
1862				'z-index: 10000' +
1863			'}'
1864		);
1865
1866		function updateResizeRect() {
1867			var controlElm = dom.getParent(selection.getNode(), 'table,img');
1868
1869			// Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
1870			each(dom.select('img[data-mce-selected]'), function(img) {
1871				img.removeAttribute('data-mce-selected');
1872			});
1873
1874			if (controlElm) {
1875				showResizeRect(controlElm);
1876			} else {
1877				hideResizeRect();
1878			}
1879		}
1880
1881		// Show/hide resize rect when image is selected
1882		editor.onNodeChange.add(updateResizeRect);
1883
1884		// Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container
1885		dom.bind(editableDoc, 'selectionchange', updateResizeRect);
1886
1887		// Remove the internal attribute when serializing the DOM
1888		editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) {
1889			var i = nodes.length;
1890
1891			while (i--) {
1892				nodes[i].attr(name, null);
1893			}
1894		});
1895	}
1896
1897	function keepNoScriptContents() {
1898		if (getDocumentMode() < 9) {
1899			parser.addNodeFilter('noscript', function(nodes) {
1900				var i = nodes.length, node, textNode;
1901
1902				while (i--) {
1903					node = nodes[i];
1904					textNode = node.firstChild;
1905
1906					if (textNode) {
1907						node.attr('data-mce-innertext', textNode.value);
1908					}
1909				}
1910			});
1911
1912			serializer.addNodeFilter('noscript', function(nodes) {
1913				var i = nodes.length, node, textNode, value;
1914
1915				while (i--) {
1916					node = nodes[i];
1917					textNode = nodes[i].firstChild;
1918
1919					if (textNode) {
1920						textNode.value = tinymce.html.Entities.decode(textNode.value);
1921					} else {
1922						// Old IE can't retain noscript value so an attribute is used to store it
1923						value = node.attributes.map['data-mce-innertext'];
1924						if (value) {
1925							node.attr('data-mce-innertext', null);
1926							textNode = new tinymce.html.Node('#text', 3);
1927							textNode.value = value;
1928							textNode.raw = true;
1929							node.append(textNode);
1930						}
1931					}
1932				}
1933			});
1934		}
1935	}
1936
1937	function bodyHeight() {
1938		editor.contentStyles.push('body {min-height: 100px}');
1939		editor.onClick.add(function(ed, e) {
1940			if (e.target.nodeName == 'HTML') {
1941				editor.execCommand('SelectAll');
1942				editor.selection.collapse(true);
1943				editor.nodeChanged();
1944			}
1945		});
1946	}
1947
1948	function fixControlSelection() {
1949		editor.onInit.add(function() {
1950			var selectedRng;
1951
1952			editor.getBody().addEventListener('mscontrolselect', function(e) {
1953				setTimeout(function() {
1954					if (editor.selection.getNode() != e.target) {
1955						selectedRng = editor.selection.getRng();
1956						selection.fakeRng = editor.dom.createRng();
1957						selection.fakeRng.setStartBefore(e.target);
1958						selection.fakeRng.setEndAfter(e.target);
1959					}
1960				}, 0);
1961			}, false);
1962
1963			editor.getDoc().addEventListener('selectionchange', function(e) {
1964				if (selectedRng && !tinymce.dom.RangeUtils.compareRanges(editor.selection.getRng(), selectedRng)) {
1965					selection.fakeRng = selectedRng = null;
1966				}
1967			}, false);
1968		});
1969	}
1970
1971	// All browsers
1972	disableBackspaceIntoATable();
1973	removeBlockQuoteOnBackSpace();
1974	emptyEditorWhenDeleting();
1975
1976	// WebKit
1977	if (tinymce.isWebKit) {
1978		keepInlineElementOnDeleteBackspace();
1979		cleanupStylesWhenDeleting();
1980		inputMethodFocus();
1981		selectControlElements();
1982		setDefaultBlockType();
1983
1984		// iOS
1985		if (tinymce.isIDevice) {
1986			selectionChangeNodeChanged();
1987		} else {
1988			fakeImageResize();
1989			selectAll();
1990		}
1991	}
1992
1993	// IE
1994	if (tinymce.isIE && !tinymce.isIE11) {
1995		removeHrOnBackspace();
1996		ensureBodyHasRoleApplication();
1997		addNewLinesBeforeBrInPre();
1998		removePreSerializedStylesWhenSelectingControls();
1999		deleteControlItemOnBackSpace();
2000		renderEmptyBlocksFix();
2001		keepNoScriptContents();
2002	}
2003
2004	// IE 11+
2005	if (tinymce.isIE11) {
2006		bodyHeight();
2007		fixControlSelection();
2008	}
2009
2010	// Gecko
2011	if (tinymce.isGecko && !tinymce.isIE11) {
2012		removeHrOnBackspace();
2013		focusBody();
2014		removeStylesWhenDeletingAccrossBlockElements();
2015		setGeckoEditingOptions();
2016		addBrAfterLastLinks();
2017		removeGhostSelection();
2018	}
2019
2020	// Opera
2021	if (tinymce.isOpera) {
2022		fakeImageResize();
2023	}
2024};
2025(function(tinymce) {
2026	var namedEntities, baseEntities, reverseEntities,
2027		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2028		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2029		rawCharsRegExp = /[<>&\"\']/g,
2030		entityRegExp = /&(#x|#)?([\w]+);/g,
2031		asciiMap = {
2032				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
2033				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
2034				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
2035				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
2036				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
2037		};
2038
2039	// Raw entities
2040	baseEntities = {
2041		'\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
2042		"'" : '&#39;',
2043		'<' : '&lt;',
2044		'>' : '&gt;',
2045		'&' : '&amp;'
2046	};
2047
2048	// Reverse lookup table for raw entities
2049	reverseEntities = {
2050		'&lt;' : '<',
2051		'&gt;' : '>',
2052		'&amp;' : '&',
2053		'&quot;' : '"',
2054		'&apos;' : "'"
2055	};
2056
2057	// Decodes text by using the browser
2058	function nativeDecode(text) {
2059		var elm;
2060
2061		elm = document.createElement("div");
2062		elm.innerHTML = text;
2063
2064		return elm.textContent || elm.innerText || text;
2065	};
2066
2067	// Build a two way lookup table for the entities
2068	function buildEntitiesLookup(items, radix) {
2069		var i, chr, entity, lookup = {};
2070
2071		if (items) {
2072			items = items.split(',');
2073			radix = radix || 10;
2074
2075			// Build entities lookup table
2076			for (i = 0; i < items.length; i += 2) {
2077				chr = String.fromCharCode(parseInt(items[i], radix));
2078
2079				// Only add non base entities
2080				if (!baseEntities[chr]) {
2081					entity = '&' + items[i + 1] + ';';
2082					lookup[chr] = entity;
2083					lookup[entity] = chr;
2084				}
2085			}
2086
2087			return lookup;
2088		}
2089	};
2090
2091	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
2092	namedEntities = buildEntitiesLookup(
2093		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
2094		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
2095		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
2096		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
2097		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
2098		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
2099		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
2100		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
2101		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
2102		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
2103		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
2104		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
2105		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
2106		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
2107		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
2108		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
2109		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
2110		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
2111		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
2112		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8
2112k4,' +
2113		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
2114		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
2115		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
2116		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2117		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
2118
2119	tinymce.html = tinymce.html || {};
2120
2121	tinymce.html.Entities = {
2122		encodeRaw : function(text, attr) {
2123			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2124				return baseEntities[chr] || chr;
2125			});
2126		},
2127
2128		encodeAllRaw : function(text) {
2129			return ('' + text).replace(rawCharsRegExp, function(chr) {
2130				return baseEntities[chr] || chr;
2131			});
2132		},
2133
2134		encodeNumeric : function(text, attr) {
2135			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2136				// Multi byte sequence convert it to a single entity
2137				if (chr.length > 1)
2138					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
2139
2140				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
2141			});
2142		},
2143
2144		encodeNamed : function(text, attr, entities) {
2145			entities = entities || namedEntities;
2146
2147			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2148				return baseEntities[chr] || entities[chr] || chr;
2149			});
2150		},
2151
2152		getEncodeFunc : function(name, entities) {
2153			var Entities = tinymce.html.Entities;
2154
2155			entities = buildEntitiesLookup(entities) || namedEntities;
2156
2157			function encodeNamedAndNumeric(text, attr) {
2158				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2159					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
2160				});
2161			};
2162
2163			function encodeCustomNamed(text, attr) {
2164				return Entities.encodeNamed(text, attr, entities);
2165			};
2166
2167			// Replace + with , to be compatible with previous TinyMCE versions
2168			name = tinymce.makeMap(name.replace(/\+/g, ','));
2169
2170			// Named and numeric encoder
2171			if (name.named && name.numeric)
2172				return encodeNamedAndNumeric;
2173
2174			// Named encoder
2175			if (name.named) {
2176				// Custom names
2177				if (entities)
2178					return encodeCustomNamed;
2179
2180				return Entities.encodeNamed;
2181			}
2182
2183			// Numeric
2184			if (name.numeric)
2185				return Entities.encodeNumeric;
2186
2187			// Raw encoder
2188			return Entities.encodeRaw;
2189		},
2190
2191		decode : function(text) {
2192			return text.replace(entityRegExp, function(all, numeric, value) {
2193				if (numeric) {
2194					value = parseInt(value, numeric.length === 2 ? 16 : 10);
2195
2196					// Support upper UTF
2197					if (value > 0xFFFF) {
2198						value -= 0x10000;
2199
2200						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
2201					} else
2202						return asciiMap[value] || String.fromCharCode(value);
2203				}
2204
2205				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
2206			});
2207		}
2208	};
2209})(tinymce);
2210tinymce.html.Styles = function(settings, schema) {
2211	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2212		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2213		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2214		trimRightRegExp = /\s+$/,
2215		urlColorRegExp = /rgb/,
2216		undef, i, encodingLookup = {}, encodingItems;
2217
2218	settings = settings || {};
2219
2220	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
2221	for (i = 0; i < encodingItems.length; i++) {
2222		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
2223		encodingLookup['\uFEFF' + i] = encodingItems[i];
2224	}
2225
2226	function toHex(match, r, g, b) {
2227		function hex(val) {
2228			val = parseInt(val).toString(16);
2229
2230			return val.length > 1 ? val : '0' + val; // 0 -> 00
2231		};
2232
2233		return '#' + hex(r) + hex(g) + hex(b);
2234	};
2235
2236	return {
2237		toHex : function(color) {
2238			return color.replace(rgbRegExp, toHex);
2239		},
2240
2241		parse : function(css) {
2242			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2243
2244			function compress(prefix, suffix) {
2245				var top, right, bottom, left;
2246
2247				// IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
2248				// So lets asume it shouldn't be there
2249				if (styles['border-image'] === 'none') {
2250					delete styles['border-image'];
2251				}
2252
2253				// Get values and check it it needs compressing
2254				top = styles[prefix + '-top' + suffix];
2255				if (!top)
2256					return;
2257
2258				right = styles[prefix + '-right' + suffix];
2259				if (top != right)
2260					return;
2261
2262				bottom = styles[prefix + '-bottom' + suffix];
2263				if (right != bottom)
2264					return;
2265
2266				left = styles[prefix + '-left' + suffix];
2267				if (bottom != left)
2268					return;
2269
2270				// Compress
2271				styles[prefix + suffix] = left;
2272				delete styles[prefix + '-top' + suffix];
2273				delete styles[prefix + '-right' + suffix];
2274				delete styles[prefix + '-bottom' + suffix];
2275				delete styles[prefix + '-left' + suffix];
2276			};
2277
2278			function canCompress(key) {
2279				var value = styles[key], i;
2280
2281				if (!value || value.indexOf(' ') < 0)
2282					return;
2283
2284				value = value.split(' ');
2285				i = value.length;
2286				while (i--) {
2287					if (value[i] !== value[0])
2288						return false;
2289				}
2290
2291				styles[key] = value[0];
2292
2293				return true;
2294			};
2295
2296			function compress2(target, a, b, c) {
2297				if (!canCompress(a))
2298					return;
2299
2300				if (!canCompress(b))
2301					return;
2302
2303				if (!canCompress(c))
2304					return;
2305
2306				// Compress
2307				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2308				delete styles[a];
2309				delete styles[b];
2310				delete styles[c];
2311			};
2312
2313			// Encodes the specified string by replacing all \" \' ; : with _<num>
2314			function encode(str) {
2315				isEncoded = true;
2316
2317				return encodingLookup[str];
2318			};
2319
2320			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2321			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2322			function decode(str, keep_slashes) {
2323				if (isEncoded) {
2324					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2325						return encodingLookup[str];
2326					});
2327				}
2328
2329				if (!keep_slashes)
2330					str = str.replace(/\\([\'\";:])/g, "$1");
2331
2332				return str;
2333			};
2334
2335			function processUrl(match, url, url2, url3, str, str2) {
2336				str = str || str2;
2337
2338				if (str) {
2339					str = decode(str);
2340
2341					// Force strings into single quote format
2342					return "'" + str.replace(/\'/g, "\\'") + "'";
2343				}
2344
2345				url = decode(url || url2 || url3);
2346
2347				// Convert the URL to relative/absolute depending on config
2348				if (urlConverter)
2349					url = urlConverter.call(urlConverterScope, url, 'style');
2350
2351				// Output new URL format
2352				return "url('" + url.replace(/\'/g, "\\'") + "')";
2353			};
2354
2355			if (css) {
2356				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2357				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2358					return str.replace(/[;:]/g, encode);
2359				});
2360
2361				// Parse styles
2362				while (matches = styleRegExp.exec(css)) {
2363					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2364					value = matches[2].replace(trimRightRegExp, '');
2365
2366					if (name && value.length > 0) {
2367						// Opera will produce 700 instead of bold in their style values
2368						if (name === 'font-weight' && value === '700')
2369							value = 'bold';
2370						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2371							value = value.toLowerCase();		
2372
2373						// Convert RGB colors to HEX
2374						value = value.replace(rgbRegExp, toHex);
2375
2376						// Convert URLs and force them into url('value') format
2377						value = value.replace(urlOrStrRegExp, processUrl);
2378						styles[name] = isEncoded ? decode(value, true) : value;
2379					}
2380
2381					styleRegExp.lastIndex = matches.index + matches[0].length;
2382				}
2383
2384				// Compress the styles to reduce it's size for example IE will expand styles
2385				compress("border", "");
2386				compress("border", "-width");
2387				compress("border", "-color");
2388				compress("border", "-style");
2389				compress("padding", "");
2390				compress("margin", "");
2391				compress2('border', 'border-width', 'border-style', 'border-color');
2392
2393				// Remove pointless border, IE produces these
2394				if (styles.border === 'medium none')
2395					delete styles.border;
2396			}
2397
2398			return styles;
2399		},
2400
2401		serialize : function(styles, element_name) {
2402			var css = '', name, value;
2403
2404			function serializeStyles(name) {
2405				var styleList, i, l, value;
2406
2407				styleList = schema.styles[name];
2408				if (styleList) {
2409					for (i = 0, l = styleList.length; i < l; i++) {
2410						name = styleList[i];
2411						value = styles[name];
2412
2413						if (value !== undef && value.length > 0)
2414							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2415					}
2416				}
2417			};
2418
2419			// Serialize styles according to schema
2420			if (element_name && schema && schema.styles) {
2421				// Serialize global styles and element specific styles
2422				serializeStyles('*');
2423				serializeStyles(element_name);
2424			} else {
2425				// Output the styles in the order they are inside the object
2426				for (name in styles) {
2427					value = styles[name];
2428
2429					if (value !== undef && value.length > 0)
2430						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2431				}
2432			}
2433
2434			return css;
2435		}
2436	};
2437};
2438(function(tinymce) {
2439	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2440
2441	function split(str, delim) {
2442		return str.split(delim || ',');
2443	};
2444
2445	function unpack(lookup, data) {
2446		var key, elements = {};
2447
2448		function replace(value) {
2449			return value.replace(/[A-Z]+/g, function(key) {
2450				return replace(lookup[key]);
2451			});
2452		};
2453
2454		// Unpack lookup
2455		for (key in lookup) {
2456			if (lookup.hasOwnProperty(key))
2457				lookup[key] = replace(lookup[key]);
2458		}
2459
2460		// Unpack and parse data into object map
2461		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2462			attributes = split(attributes, '|');
2463
2464			elements[name] = {
2465				attributes : makeMap(attributes),
2466				attributesOrder : attributes,
2467				children : makeMap(children, '|', {'#comment' : {}})
2468			}
2469		});
2470
2471		return elements;
2472	};
2473
2474	function getHTML5() {
2475		var html5 = mapCache.html5;
2476
2477		if (!html5) {
2478			html5 = mapCache.html5 = unpack({
2479					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2480					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|' +
2481						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2482					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|' +
2483						'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|' +
2484						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2485				}, 'html[A|manifest][body|head]' +
2486					'head[A][base|command|link|meta|noscript|script|style|title]' +
2487					'title[A][#]' +
2488					'base[A|href|target][]' +
2489					'link[A|href|rel|media|type|sizes][]' +
2490					'meta[A|http-equiv|name|content|charset][]' +
2491					'style[A|type|media|scoped][#]' +
2492					'script[A|charset|type|src|defer|async][#]' +
2493					'noscript[A][C]' +
2494					'body[A][C]' +
2495					'section[A][C]' +
2496					'nav[A][C]' +
2497					'article[A][C]' +
2498					'aside[A][C]' +
2499					'h1[A][B]' +
2500					'h2[A][B]' +
2501					'h3[A][B]' +
2502					'h4[A][B]' +
2503					'h5[A][B]' +
2504					'h6[A][B]' +
2505					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2506					'header[A][C]' +
2507					'footer[A][C]' +
2508					'address[A][C]' +
2509					'p[A][B]' +
2510					'br[A][]' +
2511					'pre[A][B]' +
2512					'dialog[A][dd|dt]' +
2513					'blockquote[A|cite][C]' +
2514					'ol[A|start|reversed][li]' +
2515					'ul[A][li]' +
2516					'li[A|value][C]' +
2517					'dl[A][dd|dt]' +
2518					'dt[A][B]' +
2519					'dd[A][C]' +
2520					'a[A|href|target|ping|rel|media|type][B]' +
2521					'em[A][B]' +
2522					'strong[A][B]' +
2523					'small[A][B]' +
2524					'cite[A][B]' +
2525					'q[A|cite][B]' +
2526					'dfn[A][B]' +
2527					'abbr[A][B]' +
2528					'code[A][B]' +
2529					'var[A][B]' +
2530					'samp[A][B]' +
2531					'kbd[A][B]' +
2532					'sub[A][B]' +
2533					'sup[A][B]' +
2534					'i[A][B]' +
2535					'b[A][B]' +
2536					'mark[A][B]' +
2537					'progress[A|value|max][B]' +
2538					'meter[A|value|min|max|low|high|optimum][B]' +
2539					'time[A|datetime][B]' +
2540					'ruby[A][B|rt|rp]' +
2541					'rt[A][B]' +
2542					'rp[A][B]' +
2543					'bdo[A][B]' +
2544					'span[A][B]' +
2545					'ins[A|cite|datetime][B]' +
2546					'del[A|cite|datetime][B]' +
2547					'figure[A][C|legend|figcaption]' +
2548					'figcaption[A][C]' +
2549					'img[A|alt|src|height|width|usemap|ismap][]' +
2550					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2551					'embed[A|src|height|width|type][]' +
2552					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2553					'param[A|name|value][]' +
2554					'details[A|open][C|legend]' +
2555					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2556					'menu[A|type|label][C|li]' +
2557					'legend[A][C|B]' +
2558					'div[A][C]' +
2559					'source[A|src|type|media][]' +
2560					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2561					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2562					'hr[A][]' +
2563					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2564					'fieldset[A|disabled|form|name][C|legend]' +
2565					'label[A|form|for][B]' +
2566					'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2567						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2568					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2569					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2570					'datalist[A][B|option]' +
2571					'optgroup[A|disabled|label][option]' +
2572					'option[A|disabled|selected|label|value][]' +
2573					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2574					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2575					'output[A|for|form|name][B]' +
2576					'canvas[A|width|height][]' +
2577					'map[A|name][B|C]' +
2578					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2579					'mathml[A][]' +
2580					'svg[A][]' +
2581					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2582					'caption[A][C]' +
2583					'colgroup[A|span][col]' +
2584					'col[A|span][]' +
2585					'thead[A][tr]' +
2586					'tfoot[A][tr]' +
2587					'tbody[A][tr]' +
2588					'tr[A][th|td]' +
2589					'th[A|headers|rowspan|colspan|scope][B]' +
2590					'td[A|headers|rowspan|colspan][C]' +
2591					'wbr[A][]'
2592			);
2593		}
2594
2595		return html5;
2596	};
2597
2598	function getHTML4() {
2599		var html4 = mapCache.html4;
2600
2601		if (!html4) {
2602			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2603			html4 = mapCache.html4 = unpack({
2604				Z : 'H|K|N|O|P',
2605				Y : 'X|form|R|Q',
2606				ZG : 'E|span|width|align|char|charoff|valign',
2607				X : 'p|T|div|U|W|isindex|fieldset|table',
2608				ZF : 'E|align|char|charoff|valign',
2609				W : 'pre|hr|blockquote|address|center|noframes',
2610				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2611				ZD : '[E][S]',
2612				U : 'ul|ol|dl|menu|dir',
2613				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2614				T : 'h1|h2|h3|h4|h5|h6',
2615				ZB : 'X|S|Q',
2616				S : 'R|P',
2617				ZA : 'a|G|J|M|O|P',
2618				R : 'a|H|K|N|O',
2619				Q : 'noscript|P',
2620				P : 'ins|del|script',
2621				O : 'input|select|textarea|label|button',
2622				N : 'M|L',
2623				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2624				L : 'sub|sup',
2625				K : 'J|I',
2626				J : 'tt|i|b|u|s|strike',
2627				I : 'big|small|font|basefont',
2628				H : 'G|F',
2629				G : 'br|span|bdo',
2630				F : 'object|applet|img|map|iframe',
2631				E : 'A|B|C',
2632				D : 'accesskey|tabindex|onfocus|onblur',
2633				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2634				B : 'lang|xml:lang|dir',
2635				A : 'id|class|style|title'
2636			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2637				'style[B|id|type|media|title|xml:space][]' + 
2638				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2639				'param[id|name|value|valuetype|type][]' + 
2640				'p[E|align][#|S]' + 
2641				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2642				'br[A|clear][]' + 
2643				'span[E][#|S]' + 
2644				'bdo[A|C|B][#|S]' + 
2645				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2646				'h1[E|align][#|S]' + 
2647				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2648				'map[B|C|A|name][X|form|Q|area]' + 
2649				'h2[E|align][#|S]' + 
2650				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2651				'h3[E|align][#|S]' + 
2652				'tt[E][#|S]' + 
2653				'i[E][#|S]' + 
2654				'b[E][#|S]' + 
2655				'u[E][#|S]' + 
2656				's[E][#|S]' + 
2657				'strike[E][#|S]' + 
2658				'big[E][#|S]' + 
2659				'small[E][#|S]' + 
2660				'font[A|B|size|color|face][#|S]' + 
2661				'basefont[id|size|color|face][]' + 
2662				'em[E][#|S]' + 
2663				'strong[E][#|S]' + 
2664				'dfn[E][#|S]' + 
2665				'code[E][#|S]' + 
2666				'q[E|cite][#|S]' + 
2667				'samp[E][#|S]' + 
2668				'kbd[E][#|S]' + 
2669				'var[E][#|S]' + 
2670				'cite[E][#|S]' + 
2671				'abbr[E][#|S]' + 
2672				'acronym[E][#|S]' + 
2673				'sub[E][#|S]' + 
2674				'sup[E][#|S]' + 
2675				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2676				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2677				'optgroup[E|disabled|label][option]' + 
2678				'option[E|selected|disabled|label|value][]' + 
2679				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2680				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2681				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2682				'h4[E|align][#|S]' + 
2683				'ins[E|cite|datetime][#|Y]' + 
2684				'h5[E|align][#|S]' + 
2685				'del[E|cite|datetime][#|Y]' + 
2686				'h6[E|align][#|S]' + 
2687				'div[E|align][#|Y]' + 
2688				'ul[E|type|compact][li]' + 
2689				'li[E|type|value][#|Y]' + 
2690				'ol[E|type|compact|start][li]' + 
2691				'dl[E|compact][dt|dd]' + 
2692				'dt[E][#|S]' + 
2693				'dd[E][#|Y]' + 
2694				'menu[E|compact][li]' + 
2695				'dir[E|compact][li]' + 
2696				'pre[E|width|xml:space][#|ZA]' + 
2697				'hr[E|align|noshade|size|width][]' + 
2698				'blockquote[E|cite][#|Y]' + 
2699				'address[E][#|S|p]' + 
2700				'center[E][#|Y]' + 
2701				'noframes[E][#|Y]' + 
2702				'isindex[A|B|prompt][]' + 
2703				'fieldset[E][#|legend|Y]' + 
2704				'legend[E|accesskey|align][#|S]' + 
2705				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2706				'caption[E|align][#|S]' + 
2707				'col[ZG][]' + 
2708				'colgroup[ZG][col]' + 
2709				'thead[ZF][tr]' + 
2710				'tr[ZF|bgcolor][th|td]' + 
2711				'th[E|ZE][#|Y]' + 
2712				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2713				'noscript[E][#|Y]' + 
2714				'td[E|ZE][#|Y]' + 
2715				'tfoot[ZF][tr]' + 
2716				'tbody[ZF][tr]' + 
2717				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2718				'base[id|href|target][]' + 
2719				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2720			);
2721		}
2722
2723		return html4;
2724	};
2725
2726	tinymce.html.Schema = function(settings) {
2727		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2728		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2729
2730		// Creates an lookup table map object for the specified option or the default value
2731		function createLookupTable(option, default_value, extend) {
2732			var value = settings[option];
2733
2734			if (!value) {
2735				// Get cached default map or make it if needed
2736				value = mapCache[option];
2737
2738				if (!value) {
2739					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2740					value = tinymce.extend(value, extend);
2741
2742					mapCache[option] = value;
2743				}
2744			} else {
2745				// Create custom map
2746				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2747			}
2748
2749			return value;
2750		};
2751
2752		settings = settings || {};
2753		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2754
2755		// Allow all elements and attributes if verify_html is set to false
2756		if (settings.verify_html === false)
2757			settings.valid_elements = '*[*]';
2758
2759		// Build styles list
2760		if (settings.valid_styles) {
2761			validStyles = {};
2762
2763			// Convert styles into a rule list
2764			each(settings.valid_styles, function(value, key) {
2765				validStyles[key] = tinymce.explode(value);
2766			});
2767		}
2768
2769		// Setup map objects
2770		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea');
2771		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2772		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2773		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2774		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
2775		textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 
2776						'blockquote center dir fieldset header footer article section hgroup aside nav figure');
2777		blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 
2778						'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap);
2779
2780		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2781		function patternToRegExp(str) {
2782			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2783		};
2784
2785		// Parses the specified valid_elements string and adds to the current rules
2786		// This function is a bit hard to read since it's heavily optimized for speed
2787		function addValidElements(valid_elements) {
2788			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2789				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2790				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2791				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2792				hasPatternsRegExp = /[*?+]/;
2793
2794			if (valid_elements) {
2795				// Split valid elements into an array with rules
2796				valid_elements = split(valid_elements);
2797
2798				if (elements['@']) {
2799					globalAttributes = elements['@'].attributes;
2800					globalAttributesOrder = elements['@'].attributesOrder;
2801				}
2802
2803				// Loop all rules
2804				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2805					// Parse element rule
2806					matches = elementRuleRegExp.exec(valid_elements[ei]);
2807					if (matches) {
2808						// Setup local names for matches
2809						prefix = matches[1];
2810						elementName = matches[2];
2811						outputName = matches[3];
2812						attrData = matches[4];
2813
2814						// Create new attributes and attributesOrder
2815						attributes = {};
2816						attributesOrder = [];
2817
2818						// Create the new element
2819						element = {
2820							attributes : attributes,
2821							attributesOrder : attributesOrder
2822						};
2823
2824						// Padd empty elements prefix
2825						if (prefix === '#')
2826							element.paddEmpty = true;
2827
2828						// Remove empty elements prefix
2829						if (prefix === '-')
2830							element.removeEmpty = true;
2831
2832						// Copy attributes from global rule into current rule
2833						if (globalAttributes) {
2834							for (key in globalAttributes)
2835								attributes[key] = globalAttributes[key];
2836
2837							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2838						}
2839
2840						// Attributes defined
2841						if (attrData) {
2842							attrData = split(attrData, '|');
2843							for (ai = 0, al = attrData.length; ai < al; ai++) {
2844								matches = attrRuleRegExp.exec(attrData[ai]);
2845								if (matches) {
2846									attr = {};
2847									attrType = matches[1];
2848									attrName = matches[2].replace(/::/g, ':');
2849									prefix = matches[3];
2850									value = matches[4];
2851
2852									// Required
2853									if (attrType === '!') {
2854										element.attributesRequired = element.attributesRequired || [];
2855										element.attributesRequired.push(attrName);
2856										attr.required = true;
2857									}
2858
2859									// Denied from global
2860									if (attrType === '-') {
2861										delete attributes[attrName];
2862										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2863										continue;
2864									}
2865
2866									// Default value
2867									if (prefix) {
2868										// Default value
2869										if (prefix === '=') {
2870											element.attributesDefault = element.attributesDefault || [];
2871											element.attributesDefault.push({name: attrName, value: value});
2872											attr.defaultValue = value;
2873										}
2874
2875										// Forced value
2876										if (prefix === ':') {
2877											element.attributesForced = element.attributesForced || [];
2878											element.attributesForced.push({name: attrName, value: value});
2879											attr.forcedValue = value;
2880										}
2881
2882										// Required values
2883										if (prefix === '<')
2884											attr.validValues = makeMap(value, '?');
2885									}
2886
2887									// Check for attribute patterns
2888									if (hasPatternsRegExp.test(attrName)) {
2889										element.attributePatterns = element.attributePatterns || [];
2890										attr.pattern = patternToRegExp(attrName);
2891										element.attributePatterns.push(attr);
2892									} else {
2893										// Add attribute to order list if it doesn't already exist
2894										if (!attributes[attrName])
2895											attributesOrder.push(attrName);
2896
2897										attributes[attrName] = attr;
2898									}
2899								}
2900							}
2901						}
2902
2903						// Global rule, store away these for later usage
2904						if (!globalAttributes && elementName == '@') {
2905							globalAttributes = attributes;
2906							globalAttributesOrder = attributesOrder;
2907						}
2908
2909						// Handle substitute elements such as b/strong
2910						if (outputName) {
2911							element.outputName = elementName;
2912							elements[outputName] = element;
2913						}
2914
2915						// Add pattern or exact element
2916						if (hasPatternsRegExp.test(elementName)) {
2917							element.pattern = patternToRegExp(elementName);
2918							patternElements.push(element);
2919						} else
2920							elements[elementName] = element;
2921					}
2922				}
2923			}
2924		};
2925
2926		function setValidElements(valid_elements) {
2927			elements = {};
2928			patternElements = [];
2929
2930			addValidElements(valid_elements);
2931
2932			each(schemaItems, function(element, name) {
2933				children[name] = element.children;
2934			});
2935		};
2936
2937		// Adds custom non HTML elements to the schema
2938		function addCustomElements(custom_elements) {
2939			var customElementRegExp = /^(~)?(.+)$/;
2940
2941			if (custom_elements) {
2942				each(split(custom_elements), function(rule) {
2943					var matches = customElementRegExp.exec(rule),
2944						inline = matches[1] === '~',
2945						cloneName = inline ? 'span' : 'div',
2946						name = matches[2];
2947
2948					children[name] = children[cloneName];
2949					customElementsMap[name] = cloneName;
2950
2951					// If it's not marked as inline then add it to valid block elements
2952					if (!inline) {
2953						blockElementsMap[name.toUpperCase()] = {};
2954						blockElementsMap[name] = {};
2955					}
2956
2957					// Add elements clone if needed
2958					if (!elements[name]) {
2959						elements[name] = elements[cloneName];
2960					}
2961
2962					// Add custom elements at span/div positions
2963					each(children, function(element, child) {
2964						if (element[cloneName])
2965							element[name] = element[cloneName];
2966					});
2967				});
2968			}
2969		};
2970
2971		// Adds valid children to the schema object
2972		function addValidChildren(valid_children) {
2973			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2974
2975			if (valid_children) {
2976				each(split(valid_children), function(rule) {
2977					var matches = childRuleRegExp.exec(rule), parent, prefix;
2978
2979					if (matches) {
2980						prefix = matches[1];
2981
2982						// Add/remove items from default
2983						if (prefix)
2984							parent = children[matches[2]];
2985						else
2986							parent = children[matches[2]] = {'#comment' : {}};
2987
2988						parent = children[matches[2]];
2989
2990						each(split(matches[3], '|'), function(child) {
2991							if (prefix === '-')
2992								delete parent[child];
2993							else
2994								parent[child] = {};
2995						});
2996					}
2997				});
2998			}
2999		};
3000
3001		function getElementRule(name) {
3002			var element = elements[name], i;
3003
3004			// Exact match found
3005			if (element)
3006				return element;
3007
3008			// No exact match then try the patterns
3009			i = patternElements.length;
3010			while (i--) {
3011				element = patternElements[i];
3012
3013				if (element.pattern.test(name))
3014					return element;
3015			}
3016		};
3017
3018		if (!settings.valid_elements) {
3019			// No valid elements defined then clone the elements from the schema spec
3020			each(schemaItems, function(element, name) {
3021				elements[name] = {
3022					attributes : element.attributes,
3023					attributesOrder : element.attributesOrder
3024				};
3025
3026				children[name] = element.children;
3027			});
3028
3029			// Switch these on HTML4
3030			if (settings.schema != "html5") {
3031				each(split('strong/b,em/i'), function(item) {
3032					item = split(item, '/');
3033					elements[item[1]].outputName = item[0];
3034				});
3035			}
3036
3037			// Add default alt attribute for images
3038			elements.img.attributesDefault = [{name: 'alt', value: ''}];
3039
3040			// Remove these if they are empty by default
3041			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
3042				if (elements[name]) {
3043					elements[name].removeEmpty = true;
3044				}
3045			});
3046
3047			// Padd these by default
3048			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
3049				elements[name].paddEmpty = true;
3050			});
3051		} else
3052			setValidElements(settings.valid_elements);
3053
3054		addCustomElements(settings.custom_elements);
3055		addValidChildren(settings.valid_children);
3056		addValidElements(settings.extended_valid_elements);
3057
3058		// Todo: Remove this when we fix list handling to be valid
3059		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
3060
3061		// Delete invalid elements
3062		if (settings.invalid_elements) {
3063			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
3064				if (elements[item])
3065					delete elements[item];
3066			});
3067		}
3068
3069		// If the user didn't allow span only allow internal spans
3070		if (!getElementRule('span'))
3071			addValidElements('span[!data-mce-type|*]');
3072
3073		self.children = children;
3074
3075		self.styles = validStyles;
3076
3077		self.getBoolAttrs = function() {
3078			return boolAttrMap;
3079		};
3080
3081		self.getBlockElements = function() {
3082			return blockElementsMap;
3083		};
3084
3085		self.getTextBlockElements = function() {
3086			return textBlockElementsMap;
3087		};
3088
3089		self.getShortEndedElements = function() {
3090			return shortEndedElementsMap;
3091		};
3092
3093		self.getSelfClosingElements = function() {
3094			return selfClosingElementsMap;
3095		};
3096
3097		self.getNonEmptyElements = function() {
3098			return nonEmptyElementsMap;
3099		};
3100
3101		self.getWhiteSpaceElements = function() {
3102			return whiteSpaceElementsMap;
3103		};
3104
3105		self.isValidChild = function(name, child) {
3106			var parent = children[name];
3107
3108			return !!(parent && parent[child]);
3109		};
3110
3111		self.isValid = function(name, attr) {
3112			var attrPatterns, i, rule = getElementRule(name);
3113
3114			// Check if it's a valid element
3115			if (rule) {
3116				if (attr) {
3117					// Check if attribute name exists
3118					if (rule.attributes[attr]) {
3119						return true;
3120					}
3121
3122					// Check if attribute matches a regexp pattern
3123					attrPatterns = rule.attributePatterns;
3124					if (attrPatterns) {
3125						i = attrPatterns.length;
3126						while (i--) {
3127							if (attrPatterns[i].pattern.test(name)) {
3128								return true;
3129							}
3130						}
3131					}
3132				} else {
3133					return true;
3134				}
3135			}
3136
3137			// No match
3138			return false;
3139		};
3140		
3141		self.getElementRule = getElementRule;
3142
3143		self.getCustomElements = function() {
3144			return customElementsMap;
3145		};
3146
3147		self.addValidElements = addValidElements;
3148
3149		self.setValidElements = setValidElements;
3150
3151		self.addCustomElements = addCustomElements;
3152
3153		self.addValidChildren = addValidChildren;
3154
3155		self.elements = elements;
3156	};
3157})(tinymce);
3158(function(tinymce) {
3159	tinymce.html.SaxParser = function(settings, schema) {
3160		var self = this, noop = function() {};
3161
3162		settings = settings || {};
3163		self.schema = schema = schema || new tinymce.html.Schema();
3164
3165		if (settings.fix_self_closing !== false)
3166			settings.fix_self_closing = true;
3167
3168		// Add handler functions from settings and setup default handlers
3169		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
3170			if (name)
3171				self[name] = settings[name] || noop;
3172		});
3173
3174		self.parse = function(html) {
3175			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
3176				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
3177				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
3178				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
3179
3180			function processEndTag(name) {
3181				var pos, i;
3182
3183				// Find position of parent of the same type
3184				pos = stack.length;
3185				while (pos--) {
3186					if (stack[pos].name === name)
3187						break;						
3188				}
3189
3190				// Found parent
3191				if (pos >= 0) {
3192					// Close all the open elements
3193					for (i = stack.length - 1; i >= pos; i--) {
3194						name = stack[i];
3195
3196						if (name.valid)
3197							self.end(name.name);
3198					}
3199
3200					// Remove the open elements from the stack
3201					stack.length = pos;
3202				}
3203			};
3204
3205			function parseAttribute(match, name, value, val2, val3) {
3206				var attrRule, i;
3207
3208				name = name.toLowerCase();
3209				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
3210
3211				// Validate name and value
3212				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
3213					attrRule = validAttributesMap[name];
3214
3215					// Find rule by pattern matching
3216					if (!attrRule && validAttributePatterns) {
3217						i = validAttributePatterns.length;
3218						while (i--) {
3219							attrRule = validAttributePatterns[i];
3220							if (attrRule.pattern.test(name))
3221								break;
3222						}
3223
3224						// No rule matched
3225						if (i === -1)
3226							attrRule = null;
3227					}
3228
3229					// No attribute rule found
3230					if (!attrRule)
3231						return;
3232
3233					// Validate value
3234					if (attrRule.validValues && !(value in attrRule.validValues))
3235						return;
3236				}
3237
3238				// Add attribute to list and map
3239				attrList.map[name] = value;
3240				attrList.push({
3241					name: name,
3242					value: value
3243				});
3244			};
3245
3246			// Precompile RegExps and map objects
3247			tokenRegExp = new RegExp('<(?:' +
3248				'(?:!--([\\w\\W]*?)-->)|' + // Comment
3249				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
3250				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
3251				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3252				'(?:\\/([^>]+)>)|' + // End element
3253				'(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3254			')', 'g');
3255
3256			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
3257			specialElements = {
3258				'script' : /<\/script[^>]*>/gi,
3259				'style' : /<\/style[^>]*>/gi,
3260				'noscript' : /<\/noscript[^>]*>/gi
3261			};
3262
3263			// Setup lookup tables for empty elements and boolean attributes
3264			shortEndedElements = schema.getShortEndedElements();
3265			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3266			fillAttrsMap = schema.getBoolAttrs();
3267			validate = settings.validate;
3268			removeInternalElements = settings.remove_internals;
3269			fixSelfClosing = settings.fix_self_closing;
3270			isIE = tinymce.isIE;
3271			invalidPrefixRegExp = /^:/;
3272
3273			while (matches = tokenRegExp.exec(html)) {
3274				// Text
3275				if (index < matches.index)
3276					self.text(decode(html.substr(index, matches.index - index)));
3277
3278				if (value = matches[6]) { // End element
3279					value = value.toLowerCase();
3280
3281					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3282					if (isIE && invalidPrefixRegExp.test(value))
3283						value = value.substr(1);
3284
3285					processEndTag(value);
3286				} else if (value = matches[7]) { // Start element
3287					value = value.toLowerCase();
3288
3289					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3290					if (isIE && invalidPrefixRegExp.test(value))
3291						value = value.substr(1);
3292
3293					isShortEnded = value in shortEndedElements;
3294
3295					// Is self closing tag for example an <li> after an open <li>
3296					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3297						processEndTag(value);
3298
3299					// Validate element
3300					if (!validate || (elementRule = schema.getElementRule(value))) {
3301						isValidElement = true;
3302
3303						// Grab attributes map and patters when validation is enabled
3304						if (validate) {
3305							validAttributesMap = elementRule.attributes;
3306							validAttributePatterns = elementRule.attributePatterns;
3307						}
3308
3309						// Parse attributes
3310						if (attribsValue = matches[8]) {
3311							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3312
3313							// If the element has internal attributes then remove it if we are told to do so
3314							if (isInternalElement && removeInternalElements)
3315								isValidElement = false;
3316
3317							attrList = [];
3318							attrList.map = {};
3319
3320							attribsValue.replace(attrRegExp, parseAttribute);
3321						} else {
3322							attrList = [];
3323							attrList.map = {};
3324						}
3325
3326						// Process attributes if validation is enabled
3327						if (validate && !isInternalElement) {
3328							attributesRequired = elementRule.attributesRequired;
3329							attributesDefault = elementRule.attributesDefault;
3330							attributesForced = elementRule.attributesForced;
3331
3332							// Handle forced attributes
3333							if (attributesForced) {
3334								i = attributesForced.length;
3335								while (i--) {
3336									attr = attributesForced[i];
3337									name = attr.name;
3338									attrValue = attr.value;
3339
3340									if (attrValue === '{$uid}')
3341										attrValue = 'mce_' + idCount++;
3342
3343									attrList.map[name] = attrValue;
3344									attrList.push({name: name, value: attrValue});
3345								}
3346							}
3347
3348							// Handle default attributes
3349							if (attributesDefault) {
3350								i = attributesDefault.length;
3351								while (i--) {
3352									attr = attributesDefault[i];
3353									name = attr.name;
3354
3355									if (!(name in attrList.map)) {
3356										attrValue = attr.value;
3357
3358										if (attrValue === '{$uid}')
3359											attrValue = 'mce_' + idCount++;
3360
3361										attrList.map[name] = attrValue;
3362										attrList.push({name: name, value: attrValue});
3363									}
3364								}
3365							}
3366
3367							// Handle required attributes
3368							if (attributesRequired) {
3369								i = attributesRequired.length;
3370								while (i--) {
3371									if (attributesRequired[i] in attrList.map)
3372										break;
3373								}
3374
3375								// None of the required attributes where found
3376								if (i === -1)
3377									isValidElement = false;
3378							}
3379
3380							// Invalidate element if it's marked as bogus
3381							if (attrList.map['data-mce-bogus'])
3382								isValidElement = false;
3383						}
3384
3385						if (isValidElement)
3386							self.start(value, attrList, isShortEnded);
3387					} else
3388						isValidElement = false;
3389
3390					// Treat script, noscript and style a bit different since they may include code that looks like elements
3391					if (endRegExp = specialElements[value]) {
3392						endRegExp.lastIndex = index = matches.index + matches[0].length;
3393
3394						if (matches = endRegExp.exec(html)) {
3395							if (isValidElement)
3396								text = html.substr(index, matches.index - index);
3397
3398							index = matches.index + matches[0].length;
3399						} else {
3400							text = html.substr(index);
3401							index = html.length;
3402						}
3403
3404						if (isValidElement && text.length > 0)
3405							self.text(text, true);
3406
3407						if (isValidElement)
3408							self.end(value);
3409
3410						tokenRegExp.lastIndex = index;
3411						continue;
3412					}
3413
3414					// Push value on to stack
3415					if (!isShortEnded) {
3416						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3417							stack.push({name: value, valid: isValidElement});
3418						else if (isValidElement)
3419							self.end(value);
3420					}
3421				} else if (value = matches[1]) { // Comment
3422					self.comment(value);
3423				} else if (value = matches[2]) { // CDATA
3424					self.cdata(value);
3425				} else if (value = matches[3]) { // DOCTYPE
3426					self.doctype(value);
3427				} else if (value = matches[4]) { // PI
3428					self.pi(value, matches[5]);
3429				}
3430
3431				index = matches.index + matches[0].length;
3432			}
3433
3434			// Text
3435			if (index < html.length)
3436				self.text(decode(html.substr(index)));
3437
3438			// Close any open elements
3439			for (i = stack.length - 1; i >= 0; i--) {
3440				value = stack[i];
3441
3442				if (value.valid)
3443					self.end(value.name);
3444			}
3445		};
3446	}
3447})(tinymce);
3448(function(tinymce) {
3449	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3450		'#text' : 3,
3451		'#comment' : 8,
3452		'#cdata' : 4,
3453		'#pi' : 7,
3454		'#doctype' : 10,
3455		'#document-fragment' : 11
3456	};
3457
3458	// Walks the tree left/right
3459	function walk(node, root_node, prev) {
3460		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3461
3462		// Walk into nodes if it has a start
3463		if (node[startName])
3464			return node[startName];
3465
3466		// Return the sibling if it has one
3467		if (node !== root_node) {
3468			sibling = node[siblingName];
3469
3470			if (sibling)
3471				return sibling;
3472
3473			// Walk up the parents to look for siblings
3474			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3475				sibling = parent[siblingName];
3476
3477				if (sibling)
3478					return sibling;
3479			}
3480		}
3481	};
3482
3483	function Node(name, type) {
3484		this.name = name;
3485		this.type = type;
3486
3487		if (type === 1) {
3488			this.attributes = [];
3489			this.attributes.map = {};
3490		}
3491	}
3492
3493	tinymce.extend(Node.prototype, {
3494		replace : function(node) {
3495			var self = this;
3496
3497			if (node.parent)
3498				node.remove();
3499
3500			self.insert(node, self);
3501			self.remove();
3502
3503			return self;
3504		},
3505
3506		attr : function(name, value) {
3507			var self = this, attrs, i, undef;
3508
3509			if (typeof name !== "string") {
3510				for (i in name)
3511					self.attr(i, name[i]);
3512
3513				return self;
3514			}
3515
3516			if (attrs = self.attributes) {
3517				if (value !== undef) {
3518					// Remove attribute
3519					if (value === null) {
3520						if (name in attrs.map) {
3521							delete attrs.map[name];
3522
3523							i = attrs.length;
3524							while (i--) {
3525								if (attrs[i].name === name) {
3526									attrs = attrs.splice(i, 1);
3527									return self;
3528								}
3529							}
3530						}
3531
3532						return self;
3533					}
3534
3535					// Set attribute
3536					if (name in attrs.map) {
3537						// Set attribute
3538						i = attrs.length;
3539						while (i--) {
3540							if (attrs[i].name === name) {
3541								attrs[i].value = value;
3542								break;
3543							}
3544						}
3545					} else
3546						attrs.push({name: name, value: value});
3547
3548					attrs.map[name] = value;
3549
3550					return self;
3551				} else {
3552					return attrs.map[name];
3553				}
3554			}
3555		},
3556
3557		clone : function() {
3558			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3559
3560			// Clone element attributes
3561			if (selfAttrs = self.attributes) {
3562				cloneAttrs = [];
3563				cloneAttrs.map = {};
3564
3565				for (i = 0, l = selfAttrs.length; i < l; i++) {
3566					selfAttr = selfAttrs[i];
3567
3568					// Clone everything except id
3569					if (selfAttr.name !== 'id') {
3570						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3571						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3572					}
3573				}
3574
3575				clone.attributes = cloneAttrs;
3576			}
3577
3578			clone.value = self.value;
3579			clone.shortEnded = self.shortEnded;
3580
3581			return clone;
3582		},
3583
3584		wrap : function(wrapper) {
3585			var self = this;
3586
3587			self.parent.insert(wrapper, self);
3588			wrapper.append(self);
3589
3590			return self;
3591		},
3592
3593		unwrap : function() {
3594			var self = this, node, next;
3595
3596			for (node = self.firstChild; node; ) {
3597				next = node.next;
3598				self.insert(node, self, true);
3599				node = next;
3600			}
3601
3602			self.remove();
3603		},
3604
3605		remove : function() {
3606			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3607
3608			if (parent) {
3609				if (parent.firstChild === self) {
3610					parent.firstChild = next;
3611
3612					if (next)
3613						next.prev = null;
3614				} else {
3615					prev.next = next;
3616				}
3617
3618				if (parent.lastChild === self) {
3619					parent.lastChild = prev;
3620
3621					if (prev)
3622						prev.next = null;
3623				} else {
3624					next.prev = prev;
3625				}
3626
3627				self.parent = self.next = self.prev = null;
3628			}
3629
3630			return self;
3631		},
3632
3633		append : function(node) {
3634			var self = this, last;
3635
3636			if (node.parent)
3637				node.remove();
3638
3639			last = self.lastChild;
3640			if (last) {
3641				last.next = node;
3642				node.prev = last;
3643				self.lastChild = node;
3644			} else
3645				self.lastChild = self.firstChild = node;
3646
3647			node.parent = self;
3648
3649			return node;
3650		},
3651
3652		insert : function(node, ref_node, before) {
3653			var parent;
3654
3655			if (node.parent)
3656				node.remove();
3657
3658			parent = ref_node.parent || this;
3659
3660			if (before) {
3661				if (ref_node === parent.firstChild)
3662					parent.firstChild = node;
3663				else
3664					ref_node.prev.next = node;
3665
3666				node.prev = ref_node.prev;
3667				node.next = ref_node;
3668				ref_node.prev = node;
3669			} else {
3670				if (ref_node === parent.lastChild)
3671					parent.lastChild = node;
3672				else
3673					ref_node.next.prev = node;
3674
3675				node.next = ref_node.next;
3676				node.prev = ref_node;
3677				ref_node.next = node;
3678			}
3679
3680			node.parent = parent;
3681
3682			return node;
3683		},
3684
3685		getAll : function(name) {
3686			var self = this, node, collection = [];
3687
3688			for (node = self.firstChild; node; node = walk(node, self)) {
3689				if (node.name === name)
3690					collection.push(node);
3691			}
3692
3693			return collection;
3694		},
3695
3696		empty : function() {
3697			var self = this, nodes, i, node;
3698
3699			// Remove all children
3700			if (self.firstChild) {
3701				nodes = [];
3702
3703				// Collect the children
3704				for (node = self.firstChild; node; node = walk(node, self))
3705					nodes.push(node);
3706
3707				// Remove the children
3708				i = nodes.length;
3709				while (i--) {
3710					node = nodes[i];
3711					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3712				}
3713			}
3714
3715			self.firstChild = self.lastChild = null;
3716
3717			return self;
3718		},
3719
3720		isEmpty : function(elements) {
3721			var self = this, node = self.firstChild, i, name;
3722
3723			if (node) {
3724				do {
3725					if (node.type === 1) {
3726						// Ignore bogus elements
3727						if (node.attributes.map['data-mce-bogus'])
3728							continue;
3729
3730						// Keep empty elements like <img />
3731						if (elements[node.name])
3732							return false;
3733
3734						// Keep elements with data attributes or name attribute like <a name="1"></a>
3735						i = node.attributes.length;
3736						while (i--) {
3737							name = node.attributes[i].name;
3738							if (name === "name" || name.indexOf('data-mce-') === 0)
3739								return false;
3740						}
3741					}
3742
3743					// Keep comments
3744					if (node.type === 8)
3745						return false;
3746					
3747					// Keep non whitespace text nodes
3748					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3749						return false;
3750				} while (node = walk(node, self));
3751			}
3752
3753			return true;
3754		},
3755
3756		walk : function(prev) {
3757			return walk(this, null, prev);
3758		}
3759	});
3760
3761	tinymce.extend(Node, {
3762		create : function(name, attrs) {
3763			var node, attrName;
3764
3765			// Create node
3766			node = new Node(name, typeLookup[name] || 1);
3767
3768			// Add attributes if needed
3769			if (attrs) {
3770				for (attrName in attrs)
3771					node.attr(attrName, attrs[attrName]);
3772			}
3773
3774			return node;
3775		}
3776	});
3777
3778	tinymce.html.Node = Node;
3779})(tinymce);
3780(function(tinymce) {
3781	var Node = tinymce.html.Node;
3782
3783	tinymce.html.DomParser = function(settings, schema) {
3784		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3785
3786		settings = settings || {};
3787		settings.validate = "validate" in settings ? settings.validate : true;
3788		settings.root_name = settings.root_name || 'body';
3789		self.schema = schema = schema || new tinymce.html.Schema();
3790
3791		function fixInvalidChildren(nodes) {
3792			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3793				childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
3794
3795			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3796			nonEmptyElements = schema.getNonEmptyElements();
3797			textBlockElements = schema.getTextBlockElements();
3798
3799			for (ni = 0; ni < nodes.length; ni++) {
3800				node = nodes[ni];
3801
3802				// Already removed or fixed
3803				if (!node.parent || node.fixed)
3804					continue;
3805
3806				// If the invalid element is a text block and the text block is within a parent LI element
3807				// Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
3808				if (textBlockElements[node.name] && node.parent.name == 'li') {
3809					// Move sibling text blocks after LI element
3810					sibling = node.next;
3811					while (sibling) {
3812						if (textBlockElements[sibling.name]) {
3813							sibling.name = 'li';
3814							sibling.fixed = true;
3815							node.parent.insert(sibling, node.parent);
3816						} else {
3817							break;
3818						}
3819
3820						sibling = sibling.next;
3821					}
3822
3823					// Unwrap current text block
3824					node.unwrap(node);
3825					continue;
3826				}
3827
3828				// Get list of all parent nodes until we find a valid parent to stick the child into
3829				parents = [node];
3830				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3831					parents.push(parent);
3832
3833				// Found a suitable parent
3834				if (parent && parents.length > 1) {
3835					// Reverse the array since it makes looping easier
3836					parents.reverse();
3837
3838					// Clone the related parent and insert that after the moved node
3839					newParent = currentNode = self.filterNode(parents[0].clone());
3840
3841					// Start cloning and moving children on the left side of the target node
3842					for (i = 0; i < parents.length - 1; i++) {
3843						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3844							tempNode = self.filterNode(parents[i].clone());
3845							currentNode.append(tempNode);
3846						} else
3847							tempNode = currentNode;
3848
3849						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3850							nextNode = childNode.next;
3851							tempNode.append(childNode);
3852							childNode = nextNode;
3853						}
3854
3855						currentNode = tempNode;
3856					}
3857
3858					if (!newParent.isEmpty(nonEmptyElements)) {
3859						parent.insert(newParent, parents[0], true);
3860						parent.insert(node, newParent);
3861					} else {
3862						parent.insert(node, parents[0], true);
3863					}
3864
3865					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3866					parent = parents[0];
3867					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3868						parent.empty().remove();
3869					}
3870				} else if (node.parent) {
3871					// If it's an LI try to find a UL/OL for it or wrap it
3872					if (node.name === 'li') {
3873						sibling = node.prev;
3874						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3875							sibling.append(node);
3876							continue;
3877						}
3878
3879						sibling = node.next;
3880						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3881							sibling.insert(node, sibling.firstChild, true);
3882							continue;
3883						}
3884
3885						node.wrap(self.filterNode(new Node('ul', 1)));
3886						continue;
3887					}
3888
3889					// Try wrapping the element in a DIV
3890					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3891						node.wrap(self.filterNode(new Node('div', 1)));
3892					} else {
3893						// We failed wrapping it, then remove or unwrap it
3894						if (node.name === 'style' || node.name === 'script')
3895							node.empty().remove();
3896						else
3897							node.unwrap();
3898					}
3899				}
3900			}
3901		};
3902
3903		self.filterNode = function(node) {
3904			var i, name, list;
3905
3906			// Run element filters
3907			if (name in nodeFilters) {
3908				list = matchedNodes[name];
3909
3910				if (list)
3911					list.push(node);
3912				else
3913					matchedNodes[name] = [node];
3914			}
3915
3916			// Run attribute filters
3917			i = attributeFilters.length;
3918			while (i--) {
3919				name = attributeFilters[i].name;
3920
3921				if (name in node.attributes.map) {
3922					list = matchedAttributes[name];
3923
3924					if (list)
3925						list.push(node);
3926					else
3927						matchedAttributes[name] = [node];
3928				}
3929			}
3930
3931			return node;
3932		};
3933
3934		self.addNodeFilter = function(name, callback) {
3935			tinymce.each(tinymce.explode(name), function(name) {
3936				var list = nodeFilters[name];
3937
3938				if (!list)
3939					nodeFilters[name] = list = [];
3940
3941				list.push(callback);
3942			});
3943		};
3944
3945		self.addAttributeFilter = function(name, callback) {
3946			tinymce.each(tinymce.explode(name), function(name) {
3947				var i;
3948
3949				for (i = 0; i < attributeFilters.length; i++) {
3950					if (attributeFilters[i].name === name) {
3951						attributeFilters[i].callbacks.push(callback);
3952						return;
3953					}
3954				}
3955
3956				attributeFilters.push({name: name, callbacks: [callback]});
3957			});
3958		};
3959
3960		self.parse = function(html, args) {
3961			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3962				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3963				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3964
3965			args = args || {};
3966			matchedNodes = {};
3967			matchedAttributes = {};
3968			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3969			nonEmptyElements = schema.getNonEmptyElements();
3970			children = schema.children;
3971			validate = settings.validate;
3972			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3973
3974			whiteSpaceElements = schema.getWhiteSpaceElements();
3975			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3976			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3977			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3978			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3979
3980			function addRootBlocks() {
3981				var node = rootNode.firstChild, next, rootBlockNode;
3982
3983				while (node) {
3984					next = node.next;
3985
3986					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3987						if (!rootBlockNode) {
3988							// Create a new root block element
3989							rootBlockNode = createNode(rootBlockName, 1);
3990							rootNode.insert(rootBlockNode, node);
3991							rootBlockNode.append(node);
3992						} else
3993							rootBlockNode.append(node);
3994					} else {
3995						rootBlockNode = null;
3996					}
3997
3998					node = next;
3999				};
4000			};
4001
4002			function createNode(name, type) {
4003				var node = new Node(name, type), list;
4004
4005				if (name in nodeFilters) {
4006					list = matchedNodes[name];
4007
4008					if (list)
4009						list.push(node);
4010					else
4011						matchedNodes[name] = [node];
4012				}
4013
4014				return node;
4015			};
4016
4017			function removeWhitespaceBefore(node) {
4018				var textNode, textVal, sibling;
4019
4020				for (textNode = node.prev; textNode && textNode.type === 3; ) {
4021					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
4022
4023					if (textVal.length > 0) {
4024						textNode.value = textVal;
4025						textNode = textNode.prev;
4026					} else {
4027						sibling = textNode.prev;
4028						textNode.remove();
4029						textNode = sibling;
4030					}
4031				}
4032			};
4033
4034			function cloneAndExcludeBlocks(input) {
4035				var name, output = {};
4036
4037				for (name in input) {
4038					if (name !== 'li' && name != 'p') {
4039						output[name] = input[name];
4040					}
4041				}
4042
4043				return output;
4044			};
4045
4046			parser = new tinymce.html.SaxParser({
4047				validate : validate,
4048
4049				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
4050				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
4051
4052				cdata: function(text) {
4053					node.append(createNode('#cdata', 4)).value = text;
4054				},
4055
4056				text: function(text, raw) {
4057					var textNode;
4058
4059					// Trim all redundant whitespace on non white space elements
4060					if (!isInWhiteSpacePreservedElement) {
4061						text = text.replace(allWhiteSpaceRegExp, ' ');
4062
4063						if (node.lastChild && blockElements[node.lastChild.name])
4064							text = text.replace(startWhiteSpaceRegExp, '');
4065					}
4066
4067					// Do we need to create the node
4068					if (text.length !== 0) {
4069						textNode = createNode('#text', 3);
4070						textNode.raw = !!raw;
4071						node.append(textNode).value = text;
4072					}
4073				},
4074
4075				comment: function(text) {
4076					node.append(createNode('#comment', 8)).value = text;
4077				},
4078
4079				pi: function(name, text) {
4080					node.append(createNode(name, 7)).value = text;
4081					removeWhitespaceBefore(node);
4082				},
4083
4084				doctype: function(text) {
4085					var newNode;
4086		
4087					newNode = node.append(createNode('#doctype', 10));
4088					newNode.value = text;
4089					removeWhitespaceBefore(node);
4090				},
4091
4092				start: function(name, attrs, empty) {
4093					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
4094
4095					elementRule = validate ? schema.getElementRule(name) : {};
4096					if (elementRule) {
4097						newNode = createNode(elementRule.outputName || name, 1);
4098						newNode.attributes = attrs;
4099						newNode.shortEnded = empty;
4100
4101						node.append(newNode);
4102
4103						// Check if node is valid child of the parent node is the child is
4104						// unknown we don't collect it since it's probably a custom element
4105						parent = children[node.name];
4106						if (parent && children[newNode.name] && !parent[newNode.name])
4107							invalidChildren.push(newNode);
4108
4109						attrFiltersLen = attributeFilters.length;
4110						while (attrFiltersLen--) {
4111							attrName = attributeFilters[attrFiltersLen].name;
4112
4113							if (attrName in attrs.map) {
4114								list = matchedAttributes[attrName];
4115
4116								if (list)
4117									list.push(newNode);
4118								else
4119									matchedAttributes[attrName] = [newNode];
4120							}
4121						}
4122
4123						// Trim whitespace before block
4124						if (blockElements[name])
4125							removeWhitespaceBefore(newNode);
4126
4127						// Change current node if the element wasn't empty i.e not <br /> or <img />
4128						if (!empty)
4129							node = newNode;
4130
4131						// Check if we are inside a whitespace preserved element
4132						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4133							isInWhiteSpacePreservedElement = true;
4134						}
4135					}
4136				},
4137
4138				end: function(name) {
4139					var textNode, elementRule, text, sibling, tempNode;
4140
4141					elementRule = validate ? schema.getElementRule(name) : {};
4142					if (elementRule) {
4143						if (blockElements[name]) {
4144							if (!isInWhiteSpacePreservedElement) {
4145								// Trim whitespace of the first node in a block
4146								textNode = node.firstChild;
4147								if (textNode && textNode.type === 3) {
4148									text = textNode.value.replace(startWhiteSpaceRegExp, '');
4149
4150									// Any characters left after trim or should we remove it
4151									if (text.length > 0) {
4152										textNode.value = text;
4153										textNode = textNode.next;
4154									} else {
4155										sibling = textNode.next;
4156										textNode.remove();
4157										textNode = sibling;
4158									}
4159
4160									// Remove any pure whitespace siblings
4161									while (textNode && textNode.type === 3) {
4162										text = textNode.value;
4163										sibling = textNode.next;
4164
4165										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4166											textNode.remove();
4167											textNode = sibling;
4168										}
4169
4170										textNode = sibling;
4171									}
4172								}
4173
4174								// Trim whitespace of the last node in a block
4175								textNode = node.lastChild;
4176								if (textNode && textNode.type === 3) {
4177									text = textNode.value.replace(endWhiteSpaceRegExp, '');
4178
4179									// Any characters left after trim or should we remove it
4180									if (text.length > 0) {
4181										textNode.value = text;
4182										textNode = textNode.prev;
4183									} else {
4184										sibling = textNode.prev;
4185										textNode.remove();
4186										textNode = sibling;
4187									}
4188
4189									// Remove any pure whitespace siblings
4190									while (textNode && textNode.type === 3) {
4191										text = textNode.value;
4192										sibling = textNode.prev;
4193
4194										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4195											textNode.remove();
4196											textNode = sibling;
4197										}
4198
4199										textNode = sibling;
4200									}
4201								}
4202							}
4203
4204							// Trim start white space
4205							// Removed due to: #5424
4206							/*textNode = node.prev;
4207							if (textNode && textNode.type === 3) {
4208								text = textNode.value.replace(startWhiteSpaceRegExp, '');
4209
4210								if (text.length > 0)
4211									textNode.value = text;
4212								else
4213									textNode.remove();
4214							}*/
4215						}
4216
4217						// Check if we exited a whitespace preserved element
4218						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4219							isInWhiteSpacePreservedElement = false;
4220						}
4221
4222						// Handle empty nodes
4223						if (elementRule.removeEmpty || elementRule.paddEmpty) {
4224							if (node.isEmpty(nonEmptyElements)) {
4225								if (elementRule.paddEmpty)
4226									node.empty().append(new Node('#text', '3')).value = '\u00a0';
4227								else {
4228									// Leave nodes that have a name like <a name="name">
4229									if (!node.attributes.map.name && !node.attributes.map.id) {
4230										tempNode = node.parent;
4231										node.empty().remove();
4232										node = tempNode;
4233										return;
4234									}
4235								}
4236							}
4237						}
4238
4239						node = node.parent;
4240					}
4241				}
4242			}, schema);
4243
4244			rootNode = node = new Node(args.context || settings.root_name, 11);
4245
4246			parser.parse(html);
4247
4248			// Fix invalid children or report invalid children in a contextual parsing
4249			if (validate && invalidChildren.length) {
4250				if (!args.context)
4251					fixInvalidChildren(invalidChildren);
4252				else
4253					args.invalid = true;
4254			}
4255
4256			// Wrap nodes in the root into block elements if the root is body
4257			if (rootBlockName && rootNode.name == 'body')
4258				addRootBlocks();
4259
4260			// Run filters only when the contents is valid
4261			if (!args.invalid) {
4262				// Run node filters
4263				for (name in matchedNodes) {
4264					list = nodeFilters[name];
4265					nodes = matchedNodes[name];
4266
4267					// Remove already removed children
4268					fi = nodes.length;
4269					while (fi--) {
4270						if (!nodes[fi].parent)
4271							nodes.splice(fi, 1);
4272					}
4273
4274					for (i = 0, l = list.length; i < l; i++)
4275						list[i](nodes, name, args);
4276				}
4277
4278				// Run attribute filters
4279				for (i = 0, l = attributeFilters.length; i < l; i++) {
4280					list = attributeFilters[i];
4281
4282					if (list.name in matchedAttributes) {
4283						nodes = matchedAttributes[list.name];
4284
4285						// Remove already removed children
4286						fi = nodes.length;
4287						while (fi--) {
4288							if (!nodes[fi].parent)
4289								nodes.splice(fi, 1);
4290						}
4291
4292						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4293							list.callbacks[fi](nodes, list.name, args);
4294					}
4295				}
4296			}
4297
4298			return rootNode;
4299		};
4300
4301		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4302		// make it possible to place the caret inside empty blocks. This logic tries to remove
4303		// these elements and keep br elements that where intended to be there intact
4304		if (settings.remove_trailing_brs) {
4305			self.addNodeFilter('br', function(nodes, name) {
4306				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4307					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4308
4309				// Remove brs from body element as well
4310				blockElements.body = 1;
4311
4312				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4313				for (i = 0; i < l; i++) {
4314					node = nodes[i];
4315					parent = node.parent;
4316
4317					if (blockElements[node.parent.name] && node === parent.lastChild) {
4318						// Loop all nodes to the left of the current node and check for other BR elements
4319						// excluding bookmarks since they are invisible
4320						prev = node.prev;
4321						while (prev) {
4322							prevName = prev.name;
4323
4324							// Ignore bookmarks
4325							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4326								// Found a non BR element
4327								if (prevName !== "br")
4328									break;
4329	
4330								// Found another br it's a <br><br> structure then don't remove anything
4331								if (prevName === 'br') {
4332									node = null;
4333									break;
4334								}
4335							}
4336
4337							prev = prev.prev;
4338						}
4339
4340						if (node) {
4341							node.remove();
4342
4343							// Is the parent to be considered empty after we removed the BR
4344							if (parent.isEmpty(nonEmptyElements)) {
4345								elementRule = schema.getElementRule(parent.name);
4346
4347								// Remove or padd the element depending on schema rule
4348								if (elementRule) {
4349									if (elementRule.removeEmpty)
4350										parent.remove();
4351									else if (elementRule.paddEmpty)
4352										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4353								}
4354							}
4355						}
4356					} else {
4357						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i>&nbsp;</i></b></p> 
4358						lastParent = node;
4359						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4360							lastParent = parent;
4361
4362							if (blockElements[parent.name]) {
4363								break;
4364							}
4365
4366							parent = parent.parent;
4367						}
4368
4369						if (lastParent === parent) {
4370							textNode = new tinymce.html.Node('#text', 3);
4371							textNode.value = '\u00a0';
4372							node.replace(textNode);
4373						}
4374					}
4375				}
4376			});
4377		}
4378
4379		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4380		if (!settings.allow_html_in_named_anchor) {
4381			self.addAttributeFilter('id,name', function(nodes, name) {
4382				var i = nodes.length, sibling, prevSibling, parent, node;
4383
4384				while (i--) {
4385					node = nodes[i];
4386					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4387						parent = node.parent;
4388
4389						// Move children after current node
4390						sibling = node.lastChild;
4391						do {
4392							prevSibling = sibling.prev;
4393							parent.insert(sibling, node);
4394							sibling = prevSibling;
4395						} while (sibling);
4396					}
4397				}
4398			});
4399		}
4400	}
4401})(tinymce);
4402tinymce.html.Writer = function(settings) {
4403	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4404
4405	settings = settings || {};
4406	indent = settings.indent;
4407	indentBefore = tinymce.makeMap(settings.indent_before || '');
4408	indentAfter = tinymce.makeMap(settings.indent_after || '');
4409	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4410	htmlOutput = settings.element_format == "html";
4411
4412	return {
4413		start: function(name, attrs, empty) {
4414			var i, l, attr, value;
4415
4416			if (indent && indentBefore[name] && html.length > 0) {
4417				value = html[html.length - 1];
4418
4419				if (value.length > 0 && value !== '\n')
4420					html.push('\n');
4421			}
4422
4423			html.push('<', name);
4424
4425			if (attrs) {
4426				for (i = 0, l = attrs.length; i < l; i++) {
4427					attr = attrs[i];
4428					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4429				}
4430			}
4431
4432			if (!empty || htmlOutput)
4433				html[html.length] = '>';
4434			else
4435				html[html.length] = ' />';
4436
4437			if (empty && indent && indentAfter[name] && html.length > 0) {
4438				value = html[html.length - 1];
4439
4440				if (value.length > 0 && value !== '\n')
4441					html.push('\n');
4442			}
4443		},
4444
4445		end: function(name) {
4446			var value;
4447
4448			/*if (indent && indentBefore[name] && html.length > 0) {
4449				value = html[html.length - 1];
4450
4451				if (value.length > 0 && value !== '\n')
4452					html.push('\n');
4453			}*/
4454
4455			html.push('</', name, '>');
4456
4457			if (indent && indentAfter[name] && html.length > 0) {
4458				value = html[html.length - 1];
4459
4460				if (value.length > 0 && value !== '\n')
4461					html.push('\n');
4462			}
4463		},
4464
4465		text: function(text, raw) {
4466			if (text.length > 0)
4467				html[html.length] = raw ? text : encode(text);
4468		},
4469
4470		cdata: function(text) {
4471			html.push('<![CDATA[', text, ']]>');
4472		},
4473
4474		comment: function(text) {
4475			html.push('<!--', text, '-->');
4476		},
4477
4478		pi: function(name, text) {
4479			if (text)
4480				html.push('<?', name, ' ', text, '?>');
4481			else
4482				html.push('<?', name, '?>');
4483
4484			if (indent)
4485				html.push('\n');
4486		},
4487
4488		doctype: function(text) {
4489			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4490		},
4491
4492		reset: function() {
4493			html.length = 0;
4494		},
4495
4496		getContent: function() {
4497			return html.join('').replace(/\n$/, '');
4498		}
4499	};
4500};
4501(function(tinymce) {
4502	tinymce.html.Serializer = function(settings, schema) {
4503		var self = this, writer = new tinymce.html.Writer(settings);
4504
4505		settings = settings || {};
4506		settings.validate = "validate" in settings ? settings.validate : true;
4507
4508		self.schema = schema = schema || new tinymce.html.Schema();
4509		self.writer = writer;
4510
4511		self.serialize = function(node) {
4512			var handlers, validate;
4513
4514			validate = settings.validate;
4515
4516			handlers = {
4517				// #text
4518				3: function(node, raw) {
4519					writer.text(node.value, node.raw);
4520				},
4521
4522				// #comment
4523				8: function(node) {
4524					writer.comment(node.value);
4525				},
4526
4527				// Processing instruction
4528				7: function(node) {
4529					writer.pi(node.name, node.value);
4530				},
4531
4532				// Doctype
4533				10: function(node) {
4534					writer.doctype(node.value);
4535				},
4536
4537				// CDATA
4538				4: function(node) {
4539					writer.cdata(node.value);
4540				},
4541
4542				// Document fragment
4543				11: function(node) {
4544					if ((node = node.firstChild)) {
4545						do {
4546							walk(node);
4547						} while (node = node.next);
4548					}
4549				}
4550			};
4551
4552			writer.reset();
4553
4554			function walk(node) {
4555				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4556
4557				if (!handler) {
4558					name = node.name;
4559					isEmpty = node.shortEnded;
4560					attrs = node.attributes;
4561
4562					// Sort attributes
4563					if (validate && attrs && attrs.length > 1) {
4564						sortedAttrs = [];
4565						sortedAttrs.map = {};
4566
4567						elementRule = schema.getElementRule(node.name);
4568						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4569							attrName = elementRule.attributesOrder[i];
4570
4571							if (attrName in attrs.map) {
4572								attrValue = attrs.map[attrName];
4573								sortedAttrs.map[attrName] = attrValue;
4574								sortedAttrs.push({name: attrName, value: attrValue});
4575							}
4576						}
4577
4578						for (i = 0, l = attrs.length; i < l; i++) {
4579							attrName = attrs[i].name;
4580
4581							if (!(attrName in sortedAttrs.map)) {
4582								attrValue = attrs.map[attrName];
4583								sortedAttrs.map[attrName] = attrValue;
4584								sortedAttrs.push({name: attrName, value: attrValue});
4585							}
4586						}
4587
4588						attrs = sortedAttrs;
4589					}
4590
4591					writer.start(node.name, attrs, isEmpty);
4592
4593					if (!isEmpty) {
4594						if ((node = node.firstChild)) {
4595							do {
4596								walk(node);
4597							} while (node = node.next);
4598						}
4599
4600						writer.end(name);
4601					}
4602				} else
4603					handler(node);
4604			}
4605
4606			// Serialize element and treat all non elements as fragments
4607			if (node.type == 1 && !settings.inner)
4608				walk(node);
4609			else
4610				handlers[11](node);
4611
4612			return writer.getContent();
4613		};
4614	}
4615})(tinymce);
4616// JSLint defined globals
4617/*global tinymce:false, window:false */
4618
4619tinymce.dom = {};
4620
4621(function(namespace, expando) {
4622	var w3cEventModel = !!document.addEventListener;
4623
4624	function addEvent(target, name, callback, capture) {
4625		if (target.addEventListener) {
4626			target.addEventListener(name, callback, capture || false);
4627		} else if (target.attachEvent) {
4628			target.attachEvent('on' + name, callback);
4629		}
4630	}
4631
4632	function removeEvent(target, name, callback, capture) {
4633		if (target.removeEventListener) {
4634			target.removeEventListener(name, callback, capture || false);
4635		} else if (target.detachEvent) {
4636			target.detachEvent('on' + name, callback);
4637		}
4638	}
4639
4640	function fix(original_event, data) {
4641		var name, event = data || {};
4642
4643		// Dummy function that gets replaced on the delegation state functions
4644		function returnFalse() {
4645			return false;
4646		}
4647
4648		// Dummy function that gets replaced on the delegation state functions
4649		function returnTrue() {
4650			return true;
4651		}
4652
4653		// Copy all properties from the original event
4654		for (name in original_event) {
4655			// layerX/layerY is deprecated in Chrome and produces a warning
4656			if (name !== "layerX" && name !== "layerY") {
4657				event[name] = original_event[name];
4658			}
4659		}
4660
4661		// Normalize target IE uses srcElement
4662		if (!event.target) {
4663			event.target = event.srcElement || document;
4664		}
4665
4666		// Add preventDefault method
4667		event.preventDefault = function() {
4668			event.isDefaultPrevented = returnTrue;
4669
4670			// Execute preventDefault on the original event object
4671			if (original_event) {
4672				if (original_event.preventDefault) {
4673					original_event.preventDefault();
4674				} else {
4675					original_event.returnValue = false; // IE
4676				}
4677			}
4678		};
4679
4680		// Add stopPropagation
4681		event.stopPropagation = function() {
4682			event.isPropagationStopped = returnTrue;
4683
4684			// Execute stopPropagation on the original event object
4685			if (original_event) {
4686				if (original_event.stopPropagation) {
4687					original_event.stopPropagation();
4688				} else {
4689					original_event.cancelBubble = true; // IE
4690				}
4691			 }
4692		};
4693
4694		// Add stopImmediatePropagation
4695		event.stopImmediatePropagation = function() {
4696			event.isImmediatePropagationStopped = returnTrue;
4697			event.stopPropagation();
4698		};
4699
4700		// Add event delegation states
4701		if (!event.isDefaultPrevented) {
4702			event.isDefaultPrevented = returnFalse;
4703			event.isPropagationStopped = returnFalse;
4704			event.isImmediatePropagationStopped = returnFalse;
4705		}
4706
4707		return event;
4708	}
4709
4710	function bindOnReady(win, callback, event_utils) {
4711		var doc = win.document, event = {type: 'ready'};
4712
4713		// Gets called when the DOM is ready
4714		function readyHandler() {
4715			if (!event_utils.domLoaded) {
4716				event_utils.domLoaded = true;
4717				callback(event);
4718			}
4719		}
4720
4721		// Page already loaded then fire it directly
4722		if (doc.readyState == "complete") {
4723			readyHandler();
4724			return;
4725		}
4726
4727		// Use W3C method
4728		if (w3cEventModel) {
4729			addEvent(win, 'DOMContentLoaded', readyHandler);
4730		} else {
4731			// Use IE method
4732			addEvent(doc, "readystatechange", function() {
4733				if (doc.readyState === "complete") {
4734					removeEvent(doc, "readystatechange", arguments.callee);
4735					readyHandler();
4736				}
4737			});
4738
4739			// Wait until we can scroll, when we can the DOM is initialized
4740			if (doc.documentElement.doScroll && win === win.top) {
4741				(function() {
4742					try {
4743						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4744						// http://javascript.nwbox.com/IEContentLoaded/
4745						doc.documentElement.doScroll("left");
4746					} catch (ex) {
4747						setTimeout(arguments.callee, 0);
4748						return;
4749					}
4750
4751					readyHandler();
4752				})();
4753			}
4754		}
4755
4756		// Fallback if any of the above methods should fail for some odd reason
4757		addEvent(win, 'load', readyHandler);
4758	}
4759
4760	function EventUtils(proxy) {
4761		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4762
4763		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4764		hasFocusIn = "onfocusin" in document.documentElement;
4765		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4766		count = 1;
4767
4768		// State if the DOMContentLoaded was executed or not
4769		self.domLoaded = false;
4770		self.events = events;
4771
4772		function executeHandlers(evt, id) {
4773			var callbackList, i, l, callback;
4774
4775			callbackList = events[id][evt.type];
4776			if (callbackList) {
4777				for (i = 0, l = callbackList.length; i < l; i++) {
4778					callback = callbackList[i];
4779					
4780					// Check if callback exists might be removed if a unbind is called inside the callback
4781					if (callback && callback.func.call(callback.scope, evt) === false) {
4782						evt.preventDefault();
4783					}
4784
4785					// Should we stop propagation to immediate listeners
4786					if (evt.isImmediatePropagationStopped()) {
4787						return;
4788					}
4789				}
4790			}
4791		}
4792
4793		self.bind = function(target, names, callback, scope) {
4794			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4795
4796			// Native event handler function patches the event and executes the callbacks for the expando
4797			function defaultNativeHandler(evt) {
4798				executeHandlers(fix(evt || win.event), id);
4799			}
4800
4801			// Don't bind to text nodes or comments
4802			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4803				return;
4804			}
4805
4806			// Create or get events id for the target
4807			if (!target[expando]) {
4808				id = count++;
4809				target[expando] = id;
4810				events[id] = {};
4811			} else {
4812				id = target[expando];
4813
4814				if (!events[id]) {
4815					events[id] = {};
4816				}
4817			}
4818
4819			// Setup the specified scope or use the target as a default
4820			scope = scope || target;
4821
4822			// Split names and bind each event, enables you to bind multiple events with one call
4823			names = names.split(' ');
4824			i = names.length;
4825			while (i--) {
4826				name = names[i];
4827				nativeHandler = defaultNativeHandler;
4828				fakeName = capture = false;
4829
4830				// Use ready instead of DOMContentLoaded
4831				if (name === "DOMContentLoaded") {
4832					name = "ready";
4833				}
4834
4835				// DOM is already ready
4836				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4837					self.domLoaded = true;
4838					callback.call(scope, fix({type: name}));
4839					continue;
4840				}
4841
4842				// Handle mouseenter/mouseleaver
4843				if (!hasMouseEnterLeave) {
4844					fakeName = mouseEnterLeave[name];
4845
4846					if (fakeName) {
4847						nativeHandler = function(evt) {
4848							var current, related;
4849
4850							current = evt.currentTarget;
4851							related = evt.relatedTarget;
4852
4853							// 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
4854							if (related && current.contains) {
4855								// Use contains for performance
4856								related = current.contains(related);
4857							} else {
4858								while (related && related !== current) {
4859									related = related.parentNode;
4860								}
4861							}
4862
4863							// Fire fake event
4864							if (!related) {
4865								evt = fix(evt || win.event);
4866								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4867								evt.target = current;
4868								executeHandlers(evt, id);
4869							}
4870						};
4871					}
4872				}
4873
4874				// Fake bubbeling of focusin/focusout
4875				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4876					capture = true;
4877					fakeName = name === "focusin" ? "focus" : "blur";
4878					nativeHandler = function(evt) {
4879						evt = fix(evt || win.event);
4880						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4881						executeHandlers(evt, id);
4882					};
4883				}
4884
4885				// Setup callback list and bind native event
4886				callbackList = events[id][name];
4887				if (!callbackList) {
4888					events[id][name] = callbackList = [{func: callback, scope: scope}];
4889					callbackList.fakeName = fakeName;
4890					callbackList.capture = capture;
4891
4892					// Add the nativeHandler to the callback list so that we can later unbind it
4893					callbackList.nativeHandler = nativeHandler;
4894					if (!w3cEventModel) {
4895						callbackList.proxyHandler = proxy(id);
4896					}
4897
4898					// Check if the target has native events support
4899					if (name === "ready") {
4900						bindOnReady(target, nativeHandler, self);
4901					} else {
4902						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4903					}
4904				} else {
4905					// If it already has an native handler then just push the callback
4906					callbackList.push({func: callback, scope: scope});
4907				}
4908			}
4909
4910			target = callbackList = 0; // Clean memory for IE
4911
4912			return callback;
4913		};
4914
4915		self.unbind = function(target, names, callback) {
4916			var id, callbackList, i, ci, name, eventMap;
4917
4918			// Don't bind to text nodes or comments
4919			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4920				return self;
4921			}
4922
4923			// Unbind event or events if the target has the expando
4924			id = target[expando];
4925			if (id) {
4926				eventMap = events[id];
4927
4928				// Specific callback
4929				if (names) {
4930					names = names.split(' ');
4931					i = names.length;
4932					while (i--) {
4933						name = names[i];
4934						callbackList = eventMap[name];
4935
4936						// Unbind the event if it exists in the map
4937						if (callbackList) {
4938							// Remove specified callback
4939							if (callback) {
4940								ci = callbackList.length;
4941								while (ci--) {
4942									if (callbackList[ci].func === callback) {
4943										callbackList.splice(ci, 1);
4944									}
4945								}
4946							}
4947
4948							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4949							if (!callback || callbackList.length === 0) {
4950								delete eventMap[name];
4951								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4952							}
4953						}
4954					}
4955				} else {
4956					// All events for a specific element
4957					for (name in eventMap) {
4958						callbackList = eventMap[name];
4959						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4960					}
4961
4962					eventMap = {};
4963				}
4964
4965				// Check if object is empty, if it isn't then we won't remove the expando map
4966				for (name in eventMap) {
4967					return self;
4968				}
4969
4970				// Delete event object
4971				delete events[id];
4972
4973				// Remove expando from target
4974				try {
4975					// IE will fail here since it can't delete properties from window
4976					delete target[expando];
4977				} catch (ex) {
4978					// IE will set it to null
4979					target[expando] = null;
4980				}
4981			}
4982
4983			return self;
4984		};
4985
4986		self.fire = function(target, name, args) {
4987			var id, event;
4988
4989			// Don't bind to text nodes or comments
4990			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4991				return self;
4992			}
4993
4994			// Build event object by patching the args
4995			event = fix(null, args);
4996			event.type = name;
4997
4998			do {
4999				// Found an expando that means there is listeners to execute
5000				id = target[expando];
5001				if (id) {
5002					executeHandlers(event, id);
5003				}
5004
5005				// Walk up the DOM
5006				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
5007			} while (target && !event.isPropagationStopped());
5008
5009			return self;
5010		};
5011
5012		self.clean = function(target) {
5013			var i, children, unbind = self.unbind;
5014	
5015			// Don't bind to text nodes or comments
5016			if (!target || target.nodeType === 3 || target.nodeType === 8) {
5017				return self;
5018			}
5019
5020			// Unbind any element on the specificed target
5021			if (target[expando]) {
5022				unbind(target);
5023			}
5024
5025			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
5026			if (!target.getElementsByTagName) {
5027				target = target.document;
5028			}
5029
5030			// Remove events from each child element
5031			if (target && target.getElementsByTagName) {
5032				unbind(target);
5033
5034				children = target.getElementsByTagName('*');
5035				i = children.length;
5036				while (i--) {
5037					target = children[i];
5038
5039					if (target[expando]) {
5040						unbind(target);
5041					}
5042				}
5043			}
5044
5045			return self;
5046		};
5047
5048		self.callNativeHandler = function(id, evt) {
5049			if (events) {
5050				events[id][evt.type].nativeHandler(evt);
5051			}
5052		};
5053
5054		self.destory = function() {
5055			events = {};
5056		};
5057
5058		// Legacy function calls
5059
5060		self.add = function(target, events, func, scope) {
5061			// Old API supported direct ID assignment
5062			if (typeof(target) === "string") {
5063				target = document.getElementById(target);
5064			}
5065
5066			// Old API supported multiple targets
5067			if (target && target instanceof Array) {
5068				var i = target.length;
5069
5070				while (i--) {
5071					self.add(target[i], events, func, scope);
5072				}
5073
5074				return;
5075			}
5076
5077			// Old API called ready init
5078			if (events === "init") {
5079				events = "ready";
5080			}
5081
5082			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
5083		};
5084
5085		self.remove = function(target, events, func, scope) {
5086			if (!target) {
5087				return self;
5088			}
5089
5090			// Old API supported direct ID assignment
5091			if (typeof(target) === "string") {
5092				target = document.getElementById(target);
5093			}
5094
5095			// Old API supported multiple targets
5096			if (target instanceof Array) {
5097				var i = target.length;
5098
5099				while (i--) {
5100					self.remove(target[i], events, func, scope);
5101				}
5102
5103				return self;
5104			}
5105
5106			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
5107		};
5108
5109		self.clear = function(target) {
5110			// Old API supported direct ID assignment
5111			if (typeof(target) === "string") {
5112				target = document.getElementById(target);
5113			}
5114
5115			return self.clean(target);
5116		};
5117
5118		self.cancel = function(e) {
5119			if (e) {
5120				self.prevent(e);
5121				self.stop(e);
5122			}
5123
5124			return false;
5125		};
5126
5127		self.prevent = function(e) {
5128			if (!e.preventDefault) {
5129				e = fix(e);
5130			}
5131
5132			e.preventDefault();
5133
5134			return false;
5135		};
5136
5137		self.stop = function(e) {
5138			if (!e.stopPropagation) {
5139				e = fix(e);
5140			}
5141
5142			e.stopPropagation();
5143
5144			return false;
5145		};
5146	}
5147
5148	namespace.EventUtils = EventUtils;
5149
5150	namespace.Event = new EventUtils(function(id) {
5151		return function(evt) {
5152			tinymce.dom.Event.callNativeHandler(id, evt);
5153		};
5154	});
5155
5156	// Bind ready event when tinymce script is loaded
5157	namespace.Event.bind(window, 'ready', function() {});
5158
5159	namespace = 0;
5160})(tinymce.dom, 'data-mce-expando'); // Namespace and expando
5161tinymce.dom.TreeWalker = function(start_node, root_node) {
5162	var node = start_node;
5163
5164	function findSibling(node, start_name, sibling_name, shallow) {
5165		var sibling, parent;
5166
5167		if (node) {
5168			// Walk into nodes if it has a start
5169			if (!shallow && node[start_name])
5170				return node[start_name];
5171
5172			// Return the sibling if it has one
5173			if (node != root_node) {
5174				sibling = node[sibling_name];
5175				if (sibling)
5176					return sibling;
5177
5178				// Walk up the parents to look for siblings
5179				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
5180					sibling = parent[sibling_name];
5181					if (sibling)
5182						return sibling;
5183				}
5184			}
5185		}
5186	};
5187
5188	this.current = function() {
5189		return node;
5190	};
5191
5192	this.next = function(shallow) {
5193		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
5194	};
5195
5196	this.prev = function(shallow) {
5197		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
5198	};
5199};
5200(function(tinymce) {
5201	// Shorten names
5202	var each = tinymce.each,
5203		is = tinymce.is,
5204		isWebKit = tinymce.isWebKit,
5205		isIE = tinymce.isIE,
5206		Entities = tinymce.html.Entities,
5207		simpleSelectorRe = /^([a-z0-9],?)+$/i,
5208		whiteSpaceRegExp = /^[ \t\r\n]*$/;
5209
5210	tinymce.create('tinymce.dom.DOMUtils', {
5211		doc : null,
5212		root : null,
5213		files : null,
5214		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
5215		props : {
5216			"for" : "htmlFor",
5217			"class" : "className",
5218			className : "className",
5219			checked : "checked",
5220			disabled : "disabled",
5221			maxlength : "maxLength",
5222			readonly : "readOnly",
5223			selected : "selected",
5224			value : "value",
5225			id : "id",
5226			name : "name",
5227			type : "type"
5228		},
5229
5230		DOMUtils : function(d, s) {
5231			var t = this, globalStyle, name, blockElementsMap;
5232
5233			t.doc = d;
5234			t.win = window;
5235			t.files = {};
5236			t.cssFlicker = false;
5237			t.counter = 0;
5238			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
5239			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
5240			t.hasOuterHTML = "outerHTML" in d.createElement("a");
5241
5242			t.settings = s = tinymce.extend({
5243				keep_values : false,
5244				hex_colors : 1
5245			}, s);
5246			
5247			t.schema = s.schema;
5248			t.styles = new tinymce.html.Styles({
5249				url_converter : s.url_converter,
5250				url_converter_scope : s.url_converter_scope
5251			}, s.schema);
5252
5253			// Fix IE6SP2 flicker and check it failed for pre SP2
5254			if (tinymce.isIE6) {
5255				try {
5256					d.execCommand('BackgroundImageCache', false, true);
5257				} catch (e) {
5258					t.cssFlicker = true;
5259				}
5260			}
5261
5262			t.fixDoc(d);
5263			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
5264			tinymce.addUnload(t.destroy, t);
5265			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
5266
5267			t.isBlock = function(node) {
5268				// Fix for #5446
5269				if (!node) {
5270					return false;
5271				}
5272
5273				// This function is called in module pattern style since it might be executed with the wrong this scope
5274				var type = node.nodeType;
5275
5276				// If it's a node then check the type and use the nodeName
5277				if (type)
5278					return !!(type === 1 && blockElementsMap[node.nodeName]);
5279
5280				return !!blockElementsMap[node];
5281			};
5282		},
5283
5284		fixDoc: function(doc) {
5285			var settings = this.settings, name;
5286
5287			if (isIE && !tinymce.isIE11 && settings.schema) {
5288				// Add missing HTML 4/5 elements to IE
5289				('abbr article aside audio canvas ' +
5290				'details figcaption figure footer ' +
5291				'header hgroup mark menu meter nav ' +
5292				'output progress section summary ' +
5293				'time video').replace(/\w+/g, function(name) {
5294					doc.createElement(name);
5295				});
5296
5297				// Create all custom elements
5298				for (name in settings.schema.getCustomElements()) {
5299					doc.createElement(name);
5300				}
5301			}
5302		},
5303
5304		clone: function(node, deep) {
5305			var self = this, clone, doc;
5306
5307			// TODO: Add feature detection here in the future
5308			if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) {
5309				return node.cloneNode(deep);
5310			}
5311
5312			doc = self.doc;
5313
5314			// Make a HTML5 safe shallow copy
5315			if (!deep) {
5316				clone = doc.createElement(node.nodeName);
5317
5318				// Copy attribs
5319				each(self.getAttribs(node), function(attr) {
5320					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5321				});
5322
5323				return clone;
5324			}
5325/*
5326			// Setup HTML5 patched document fragment
5327			if (!self.frag) {
5328				self.frag = doc.createDocumentFragment();
5329				self.fixDoc(self.frag);
5330			}
5331
5332			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5333			clone = doc.createElement('div');
5334			self.frag.appendChild(clone);
5335			clone.innerHTML = node.outerHTML;
5336			self.frag.removeChild(clone);
5337*/
5338			return clone.firstChild;
5339		},
5340
5341		getRoot : function() {
5342			var t = this, s = t.settings;
5343
5344			return (s && t.get(s.root_element)) || t.doc.body;
5345		},
5346
5347		getViewPort : function(w) {
5348			var d, b;
5349
5350			w = !w ? this.win : w;
5351			d = w.document;
5352			b = this.boxModel ? d.documentElement : d.body;
5353
5354			// Returns viewport size excluding scrollbars
5355			return {
5356				x : w.pageXOffset || b.scrollLeft,
5357				y : w.pageYOffset || b.scrollTop,
5358				w : w.innerWidth || b.clientWidth,
5359				h : w.innerHeight || b.clientHeight
5360			};
5361		},
5362
5363		getRect : function(e) {
5364			var p, t = this, sr;
5365
5366			e = t.get(e);
5367			p = t.getPos(e);
5368			sr = t.getSize(e);
5369
5370			return {
5371				x : p.x,
5372				y : p.y,
5373				w : sr.w,
5374				h : sr.h
5375			};
5376		},
5377
5378		getSize : function(e) {
5379			var t = this, w, h;
5380
5381			e = t.get(e);
5382			w = t.getStyle(e, 'width');
5383			h = t.getStyle(e, 'height');
5384
5385			// Non pixel value, then force offset/clientWidth
5386			if (w.indexOf('px') === -1)
5387				w = 0;
5388
5389			// Non pixel value, then force offset/clientWidth
5390			if (h.indexOf('px') === -1)
5391				h = 0;
5392
5393			return {
5394				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5395				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5396			};
5397		},
5398
5399		getParent : function(n, f, r) {
5400			return this.getParents(n, f, r, false);
5401		},
5402
5403		getParents : function(n, f, r, c) {
5404			var t = this, na, se = t.settings, o = [];
5405
5406			n = t.get(n);
5407			c = c === undefined;
5408
5409			if (se.strict_root)
5410				r = r || t.getRoot();
5411
5412			// Wrap node name as func
5413			if (is(f, 'string')) {
5414				na = f;
5415
5416				if (f === '*') {
5417					f = function(n) {return n.nodeType == 1;};
5418				} else {
5419					f = function(n) {
5420						return t.is(n, na);
5421					};
5422				}
5423			}
5424
5425			while (n) {
5426				if (n == r || !n.nodeType || n.nodeType === 9)
5427					break;
5428
5429				if (!f || f(n)) {
5430					if (c)
5431						o.push(n);
5432					else
5433						return n;
5434				}
5435
5436				n = n.parentNode;
5437			}
5438
5439			return c ? o : null;
5440		},
5441
5442		get : function(e) {
5443			var n;
5444
5445			if (e && this.doc && typeof(e) == 'string') {
5446				n = e;
5447				e = this.doc.getElementById(e);
5448
5449				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5450				if (e && e.id !== n)
5451					return this.doc.getElementsByName(n)[1];
5452			}
5453
5454			return e;
5455		},
5456
5457		getNext : function(node, selector) {
5458			return this._findSib(node, selector, 'nextSibling');
5459		},
5460
5461		getPrev : function(node, selector) {
5462			return this._findSib(node, selector, 'previousSibling');
5463		},
5464
5465
5466		select : function(pa, s) {
5467			var t = this;
5468
5469			return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5470		},
5471
5472		is : function(n, selector) {
5473			var i;
5474
5475			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5476			if (n.length === undefined) {
5477				// Simple all selector
5478				if (selector === '*')
5479					return n.nodeType == 1;
5480
5481				// Simple selector just elements
5482				if (simpleSelectorRe.test(selector)) {
5483					selector = selector.toLowerCase().split(/,/);
5484					n = n.nodeName.toLowerCase();
5485
5486					for (i = selector.length - 1; i >= 0; i--) {
5487						if (selector[i] == n)
5488							return true;
5489					}
5490
5491					return false;
5492				}
5493			}
5494
5495			return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5496		},
5497
5498
5499		add : function(p, n, a, h, c) {
5500			var t = this;
5501
5502			return this.run(p, function(p) {
5503				var e, k;
5504
5505				e = is(n, 'string') ? t.doc.createElement(n) : n;
5506				t.setAttribs(e, a);
5507
5508				if (h) {
5509					if (h.nodeType)
5510						e.appendChild(h);
5511					else
5512						t.setHTML(e, h);
5513				}
5514
5515				return !c ? p.appendChild(e) : e;
5516			});
5517		},
5518
5519		create : function(n, a, h) {
5520			return this.add(this.doc.createElement(n), n, a, h, 1);
5521		},
5522
5523		createHTML : function(n, a, h) {
5524			var o = '', t = this, k;
5525
5526			o += '<' + n;
5527
5528			for (k in a) {
5529				if (a.hasOwnProperty(k))
5530					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5531			}
5532
5533			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5534			if (typeof(h) != "undefined")
5535				return o + '>' + h + '</' + n + '>';
5536
5537			return o + ' />';
5538		},
5539
5540		remove : function(node, keep_children) {
5541			return this.run(node, function(node) {
5542				var child, parent = node.parentNode;
5543
5544				if (!parent)
5545					return null;
5546
5547				if (keep_children) {
5548					while (child = node.firstChild) {
5549						// IE 8 will crash if you don't remove completely empty text nodes
5550						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5551							parent.insertBefore(child, node);
5552						else
5553							node.removeChild(child);
5554					}
5555				}
5556
5557				return parent.removeChild(node);
5558			});
5559		},
5560
5561		setStyle : function(n, na, v) {
5562			var t = this;
5563
5564			return t.run(n, function(e) {
5565				var s, i;
5566
5567				s = e.style;
5568
5569				// Camelcase it, if needed
5570				na = na.replace(/-(\D)/g, function(a, b){
5571					return b.toUpperCase();
5572				});
5573
5574				// Default px suffix on these
5575				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5576					v += 'px';
5577
5578				switch (na) {
5579					case 'opacity':
5580						// IE specific opacity
5581						if (isIE && ! tinymce.isIE11) {
5582							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5583
5584							if (!n.currentStyle || !n.currentStyle.hasLayout)
5585								s.display = 'inline-block';
5586						}
5587
5588						// Fix for older browsers
5589						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5590						break;
5591
5592					case 'float':
5593						(isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v;
5594						break;
5595					
5596					default:
5597						s[na] = v || '';
5598				}
5599
5600				// Force update of the style data
5601				if (t.settings.update_styles)
5602					t.setAttrib(e, 'data-mce-style');
5603			});
5604		},
5605
5606		getStyle : function(n, na, c) {
5607			n = this.get(n);
5608
5609			if (!n)
5610				return;
5611
5612			// Gecko
5613			if (this.doc.defaultView && c) {
5614				// Remove camelcase
5615				na = na.replace(/[A-Z]/g, function(a){
5616					return '-' + a;
5617				});
5618
5619				try {
5620					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5621				} catch (ex) {
5622					// Old safari might fail
5623					return null;
5624				}
5625			}
5626
5627			// Camelcase it, if needed
5628			na = na.replace(/-(\D)/g, function(a, b){
5629				return b.toUpperCase();
5630			});
5631
5632			if (na == 'float')
5633				na = isIE ? 'styleFloat' : 'cssFloat';
5634
5635			// IE & Opera
5636			if (n.currentStyle && c)
5637				return n.currentStyle[na];
5638
5639			return n.style ? n.style[na] : undefined;
5640		},
5641
5642		setStyles : function(e, o) {
5643			var t = this, s = t.settings, ol;
5644
5645			ol = s.update_styles;
5646			s.update_styles = 0;
5647
5648			each(o, function(v, n) {
5649				t.setStyle(e, n, v);
5650			});
5651
5652			// Update style info
5653			s.update_styles = ol;
5654			if (s.update_styles)
5655				t.setAttrib(e, s.cssText);
5656		},
5657
5658		removeAllAttribs: function(e) {
5659			return this.run(e, function(e) {
5660				var i, attrs = e.attributes;
5661				for (i = attrs.length - 1; i >= 0; i--) {
5662					e.removeAttributeNode(attrs.item(i));
5663				}
5664			});
5665		},
5666
5667		setAttrib : function(e, n, v) {
5668			var t = this;
5669
5670			// Whats the point
5671			if (!e || !n)
5672				return;
5673
5674			// Strict XML mode
5675			if (t.settings.strict)
5676				n = n.toLowerCase();
5677
5678			return this.run(e, function(e) {
5679				var s = t.settings;
5680				var originalValue = e.getAttribute(n);
5681				if (v !== null) {
5682					switch (n) {
5683						case "style":
5684							if (!is(v, 'string')) {
5685								each(v, function(v, n) {
5686									t.setStyle(e, n, v);
5687								});
5688
5689								return;
5690							}
5691
5692							// No mce_style for elements with these since they might get resized by the user
5693							if (s.keep_values) {
5694								if (v && !t._isRes(v))
5695									e.setAttribute('data-mce-style', v, 2);
5696								else
5697									e.removeAttribute('data-mce-style', 2);
5698							}
5699
5700							e.style.cssText = v;
5701							break;
5702
5703						case "class":
5704							e.className = v || ''; // Fix IE null bug
5705							break;
5706
5707						case "src":
5708						case "href":
5709							if (s.keep_values) {
5710								if (s.url_converter)
5711									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5712
5713								t.setAttrib(e, 'data-mce-' + n, v, 2);
5714							}
5715
5716							break;
5717
5718						case "shape":
5719							e.setAttribute('data-mce-style', v);
5720							break;
5721					}
5722				}
5723				if (is(v) && v !== null && v.length !== 0)
5724					e.setAttribute(n, '' + v, 2);
5725				else
5726					e.removeAttribute(n, 2);
5727
5728				// fire onChangeAttrib event for attributes that have changed
5729				if (tinyMCE.activeEditor && originalValue != v) {
5730					var ed = tinyMCE.activeEditor;
5731					ed.onSetAttrib.dispatch(ed, e, n, v);
5732				}
5733			});
5734		},
5735
5736		setAttribs : function(e, o) {
5737			var t = this;
5738
5739			return this.run(e, function(e) {
5740				each(o, function(v, n) {
5741					t.setAttrib(e, n, v);
5742				});
5743			});
5744		},
5745
5746		getAttrib : function(e, n, dv) {
5747			var v, t = this, undef;
5748
5749			e = t.get(e);
5750
5751			if (!e || e.nodeType !== 1)
5752				return dv === undef ? false : dv;
5753
5754			if (!is(dv))
5755				dv = '';
5756
5757			// Try the mce variant for these
5758			if (/^(src|href|style|coords|shape)$/.test(n)) {
5759				v = e.getAttribute("data-mce-" + n);
5760
5761				if (v)
5762					return v;
5763			}
5764
5765			if (isIE && t.props[n]) {
5766				v = e[t.props[n]];
5767				v = v && v.nodeValue ? v.nodeValue : v;
5768			}
5769
5770			if (!v)
5771				v = e.getAttribute(n, 2);
5772
5773			// Check boolean attribs
5774			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5775				if (e[t.props[n]] === true && v === '')
5776					return n;
5777
5778				return v ? n : '';
5779			}
5780
5781			// Inner input elements will override attributes on form elements
5782			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5783				return e.getAttributeNode(n).nodeValue;
5784
5785			if (n === 'style') {
5786				v = v || e.style.cssText;
5787
5788				if (v) {
5789					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5790
5791					if (t.settings.keep_values && !t._isRes(v))
5792						e.setAttribute('data-mce-style', v);
5793				}
5794			}
5795
5796			// Remove Apple and WebKit stuff
5797			if (isWebKit && n === "class" && v)
5798				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5799
5800			// Handle IE issues
5801			if (isIE) {
5802				switch (n) {
5803					case 'rowspan':
5804					case 'colspan':
5805						// IE returns 1 as default value
5806						if (v === 1)
5807							v = '';
5808
5809						break;
5810
5811					case 'size':
5812						// IE returns +0 as default value for size
5813						if (v === '+0' || v === 20 || v === 0)
5814							v = '';
5815
5816						break;
5817
5818					case 'width':
5819					case 'height':
5820					case 'vspace':
5821					case 'checked':
5822					case 'disabled':
5823					case 'readonly':
5824						if (v === 0)
5825							v = '';
5826
5827						break;
5828
5829					case 'hspace':
5830						// IE returns -1 as default value
5831						if (v === -1)
5832							v = '';
5833
5834						break;
5835
5836					case 'maxlength':
5837					case 'tabindex':
5838						// IE returns default value
5839						if (v === 32768 || v === 2147483647 || v === '32768')
5840							v = '';
5841
5842						break;
5843
5844					case 'multiple':
5845					case 'compact':
5846					case 'noshade':
5847					case 'nowrap':
5848						if (v === 65535)
5849							return n;
5850
5851						return dv;
5852
5853					case 'shape':
5854						v = v.toLowerCase();
5855						break;
5856
5857					default:
5858						// IE has odd anonymous function for event attributes
5859						if (n.indexOf('on') === 0 && v)
5860							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5861				}
5862			}
5863
5864			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5865		},
5866
5867		getPos : function(n, ro) {
5868			var t = this, x = 0, y = 0, e, d = t.doc, r;
5869
5870			n = t.get(n);
5871			ro = ro || d.body;
5872
5873			if (n) {
5874				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5875				if (n.getBoundingClientRect) {
5876					n = n.getBoundingClientRect();
5877					e = t.boxModel ? d.documentElement : d.body;
5878
5879					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5880					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5881					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5882					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5883
5884					return {x : x, y : y};
5885				}
5886
5887				r = n;
5888				while (r && r != ro && r.nodeType) {
5889					x += r.offsetLeft || 0;
5890					y += r.offsetTop || 0;
5891					r = r.offsetParent;
5892				}
5893
5894				r = n.parentNode;
5895				while (r && r != ro && r.nodeType) {
5896					x -= r.scrollLeft || 0;
5897					y -= r.scrollTop || 0;
5898					r = r.parentNode;
5899				}
5900			}
5901
5902			return {x : x, y : y};
5903		},
5904
5905		parseStyle : function(st) {
5906			return this.styles.parse(st);
5907		},
5908
5909		serializeStyle : function(o, name) {
5910			return this.styles.serialize(o, name);
5911		},
5912
5913		addStyle: function(cssText) {
5914			var doc = this.doc, head;
5915
5916			// Create style element if needed
5917			styleElm = doc.getElementById('mceDefaultStyles');
5918			if (!styleElm) {
5919				styleElm = doc.createElement('style'),
5920				styleElm.id = 'mceDefaultStyles';
5921				styleElm.type = 'text/css';
5922
5923				head = doc.getElementsByTagName('head')[0];
5924				if (head.firstChild) {
5925					head.insertBefore(styleElm, head.firstChild);
5926				} else {
5927					head.appendChild(styleElm);
5928				}
5929			}
5930
5931			// Append style data to old or new style element
5932			if (styleElm.styleSheet) {
5933				styleElm.styleSheet.cssText += cssText;
5934			} else {
5935				styleElm.appendChild(doc.createTextNode(cssText));
5936			}
5937		},
5938
5939		loadCSS : function(u) {
5940			var t = this, d = t.doc, head;
5941
5942			if (!u)
5943				u = '';
5944
5945			head = d.getElementsByTagName('head')[0];
5946
5947			each(u.split(','), function(u) {
5948				var link;
5949
5950				if (t.files[u])
5951					return;
5952
5953				t.files[u] = true;
5954				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5955
5956				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5957				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5958				// It's ugly but it seems to work fine.
5959				if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) {
5960					link.onload = function() {
5961						if (d.recalc)
5962							d.recalc();
5963
5964						link.onload = null;
5965					};
5966				}
5967
5968				head.appendChild(link);
5969			});
5970		},
5971
5972		addClass : function(e, c) {
5973			return this.run(e, function(e) {
5974				var o;
5975
5976				if (!c)
5977					return 0;
5978
5979				if (this.hasClass(e, c))
5980					return e.className;
5981
5982				o = this.removeClass(e, c);
5983
5984				return e.className = (o != '' ? (o + ' ') : '') + c;
5985			});
5986		},
5987
5988		removeClass : function(e, c) {
5989			var t = this, re;
5990
5991			return t.run(e, function(e) {
5992				var v;
5993
5994				if (t.hasClass(e, c)) {
5995					if (!re)
5996						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5997
5998					v = e.className.replace(re, ' ');
5999					v = tinymce.trim(v != ' ' ? v : '');
6000
6001					e.className = v;
6002
6003					// Empty class attr
6004					if (!v) {
6005						e.removeAttribute('class');
6006						e.removeAttribute('className');
6007					}
6008
6009					return v;
6010				}
6011
6012				return e.className;
6013			});
6014		},
6015
6016		hasClass : function(n, c) {
6017			n = this.get(n);
6018
6019			if (!n || !c)
6020				return false;
6021
6022			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
6023		},
6024
6025		show : function(e) {
6026			return this.setStyle(e, 'display', 'block');
6027		},
6028
6029		hide : function(e) {
6030			return this.setStyle(e, 'display', 'none');
6031		},
6032
6033		isHidden : function(e) {
6034			e = this.get(e);
6035
6036			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
6037		},
6038
6039		uniqueId : function(p) {
6040			return (!p ? 'mce_' : p) + (this.counter++);
6041		},
6042
6043		setHTML : function(element, html) {
6044			var self = this;
6045
6046			return self.run(element, function(element) {
6047				if (isIE) {
6048					// Remove all child nodes, IE keeps empty text nodes in DOM
6049					while (element.firstChild)
6050						element.removeChild(element.firstChild);
6051
6052					try {
6053						// IE will remove comments from the beginning
6054						// unless you padd the contents with something
6055						element.innerHTML = '<br />' + html;
6056						element.removeChild(element.firstChild);
6057					} catch (ex) {
6058						// 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
6059						// This seems to fix this problem
6060
6061						// Create new div with HTML contents and a BR infront to keep comments
6062						var newElement = self.create('div');
6063						newElement.innerHTML = '<br />' + html;
6064
6065						// Add all children from div to target
6066						each (tinymce.grep(newElement.childNodes), function(node, i) {
6067							// Skip br element
6068							if (i && element.canHaveHTML)
6069								element.appendChild(node);
6070						});
6071					}
6072				} else
6073					element.innerHTML = html;
6074
6075				return html;
6076			});
6077		},
6078
6079		getOuterHTML : function(elm) {
6080			var doc, self = this;
6081
6082			elm = self.get(elm);
6083
6084			if (!elm)
6085				return null;
6086
6087			if (elm.nodeType === 1 && self.hasOuterHTML)
6088				return elm.outerHTML;
6089
6090			doc = (elm.ownerDocument || self.doc).createElement("body");
6091			doc.appendChild(elm.cloneNode(true));
6092
6093			return doc.innerHTML;
6094		},
6095
6096		setOuterHTML : function(e, h, d) {
6097			var t = this;
6098
6099			function setHTML(e, h, d) {
6100				var n, tp;
6101
6102				tp = d.createElement("body");
6103				tp.innerHTML = h;
6104
6105				n = tp.lastChild;
6106				while (n) {
6107					t.insertAfter(n.cloneNode(true), e);
6108					n = n.previousSibling;
6109				}
6110
6111				t.remove(e);
6112			};
6113
6114			return this.run(e, function(e) {
6115				e = t.get(e);
6116
6117				// Only set HTML on elements
6118				if (e.nodeType == 1) {
6119					d = d || e.ownerDocument || t.doc;
6120
6121					if (isIE) {
6122						try {
6123							// Try outerHTML for IE it sometimes produces an unknown runtime error
6124							if (isIE && e.nodeType == 1)
6125								e.outerHTML = h;
6126							else
6127								setHTML(e, h, d);
6128						} catch (ex) {
6129							// Fix for unknown runtime error
6130							setHTML(e, h, d);
6131						}
6132					} else
6133						setHTML(e, h, d);
6134				}
6135			});
6136		},
6137
6138		decode : Entities.decode,
6139
6140		encode : Entities.encodeAllRaw,
6141
6142		insertAfter : function(node, reference_node) {
6143			reference_node = this.get(reference_node);
6144
6145			return this.run(node, function(node) {
6146				var parent, nextSibling;
6147
6148				parent = reference_node.parentNode;
6149				nextSibling = reference_node.nextSibling;
6150
6151				if (nextSibling)
6152					parent.insertBefore(node, nextSibling);
6153				else
6154					parent.appendChild(node);
6155
6156				return node;
6157			});
6158		},
6159
6160		replace : function(n, o, k) {
6161			var t = this;
6162
6163			if (is(o, 'array'))
6164				n = n.cloneNode(true);
6165
6166			return t.run(o, function(o) {
6167				if (k) {
6168					each(tinymce.grep(o.childNodes), function(c) {
6169						n.appendChild(c);
6170					});
6171				}
6172
6173				return o.parentNode.replaceChild(n, o);
6174			});
6175		},
6176
6177		rename : function(elm, name) {
6178			var t = this, newElm;
6179
6180			if (elm.nodeName != name.toUpperCase()) {
6181				// Rename block element
6182				newElm = t.create(name);
6183
6184				// Copy attribs to new block
6185				each(t.getAttribs(elm), function(attr_node) {
6186					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
6187				});
6188
6189				// Replace block
6190				t.replace(newElm, elm, 1);
6191			}
6192
6193			return newElm || elm;
6194		},
6195
6196		findCommonAncestor : function(a, b) {
6197			var ps = a, pe;
6198
6199			while (ps) {
6200				pe = b;
6201
6202				while (pe && ps != pe)
6203					pe = pe.parentNode;
6204
6205				if (ps == pe)
6206					break;
6207
6208				ps = ps.parentNode;
6209			}
6210
6211			if (!ps && a.ownerDocument)
6212				return a.ownerDocument.documentElement;
6213
6214			return ps;
6215		},
6216
6217		toHex : function(s) {
6218			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
6219
6220			function hex(s) {
6221				s = parseInt(s, 10).toString(16);
6222
6223				return s.length > 1 ? s : '0' + s; // 0 -> 00
6224			};
6225
6226			if (c) {
6227				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
6228
6229				return s;
6230			}
6231
6232			return s;
6233		},
6234
6235		getClasses : function() {
6236			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
6237
6238			if (t.classes)
6239				return t.classes;
6240
6241			function addClasses(s) {
6242				// IE style imports
6243				each(s.imports, function(r) {
6244					addClasses(r);
6245				});
6246
6247				each(s.cssRules || s.rules, function(r) {
6248					// Real type or fake it on IE
6249					switch (r.type || 1) {
6250						// Rule
6251						case 1:
6252							if (r.selectorText) {
6253								each(r.selectorText.split(','), function(v) {
6254									v = v.replace(/^\s*|\s*$|^\s\./g, "");
6255
6256									// Is internal or it doesn't contain a class
6257									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
6258										return;
6259
6260									// Remove everything but class name
6261									ov = v;
6262									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
6263
6264									// Filter classes
6265									if (f && !(v = f(v, ov)))
6266										return;
6267
6268									if (!lo[v]) {
6269										cl.push({'class' : v});
6270										lo[v] = 1;
6271									}
6272								});
6273							}
6274							break;
6275
6276						// Import
6277						case 3:
6278							try {
6279								addClasses(r.styleSheet);
6280							} catch (ex) {
6281								// Ignore
6282							}
6283
6284							break;
6285					}
6286				});
6287			};
6288
6289			try {
6290				each(t.doc.styleSheets, addClasses);
6291			} catch (ex) {
6292				// Ignore
6293			}
6294
6295			if (cl.length > 0)
6296				t.classes = cl;
6297
6298			return cl;
6299		},
6300
6301		run : function(e, f, s) {
6302			var t = this, o;
6303
6304			if (t.doc && typeof(e) === 'string')
6305				e = t.get(e);
6306
6307			if (!e)
6308				return false;
6309
6310			s = s || this;
6311			if (!e.nodeType && (e.length || e.length === 0)) {
6312				o = [];
6313
6314				each(e, function(e, i) {
6315					if (e) {
6316						if (typeof(e) == 'string')
6317							e = t.doc.getElementById(e);
6318
6319						o.push(f.call(s, e, i));
6320					}
6321				});
6322
6323				return o;
6324			}
6325
6326			return f.call(s, e);
6327		},
6328
6329		getAttribs : function(n) {
6330			var o;
6331
6332			n = this.get(n);
6333
6334			if (!n)
6335				return [];
6336
6337			if (isIE) {
6338				o = [];
6339
6340				// Object will throw exception in IE
6341				if (n.nodeName == 'OBJECT')
6342					return n.attributes;
6343
6344				// IE doesn't keep the selected attribute if you clone option elements
6345				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6346					o.push({specified : 1, nodeName : 'selected'});
6347
6348				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6349				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6350					o.push({specified : 1, nodeName : a});
6351				});
6352
6353				return o;
6354			}
6355
6356			return n.attributes;
6357		},
6358
6359		isEmpty : function(node, elements) {
6360			var self = this, i, attributes, type, walker, name, brCount = 0;
6361
6362			node = node.firstChild;
6363			if (node) {
6364				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6365				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6366
6367				do {
6368					type = node.nodeType;
6369
6370					if (type === 1) {
6371						// Ignore bogus elements
6372						if (node.getAttribute('data-mce-bogus'))
6373							continue;
6374
6375						// Keep empty elements like <img />
6376						name = node.nodeName.toLowerCase();
6377						if (elements && elements[name]) {
6378							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6379							if (name === 'br') {
6380								brCount++;
6381								continue;
6382							}
6383
6384							return false;
6385						}
6386
6387						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6388						attributes = self.getAttribs(node);
6389						i = node.attributes.length;
6390						while (i--) {
6391							name = node.attributes[i].nodeName;
6392							if (name === "name" || name === 'data-mce-bookmark')
6393								return false;
6394						}
6395					}
6396
6397					// Keep comment nodes
6398					if (type == 8)
6399						return false;
6400
6401					// Keep non whitespace text nodes
6402					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6403						return false;
6404				} while (node = walker.next());
6405			}
6406
6407			return brCount <= 1;
6408		},
6409
6410		destroy : function(s) {
6411			var t = this;
6412
6413			t.win = t.doc = t.root = t.events = t.frag = null;
6414
6415			// Manual destroy then remove unload handler
6416			if (!s)
6417				tinymce.removeUnload(t.destroy);
6418		},
6419
6420		createRng : function() {
6421			var d = this.doc;
6422
6423			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6424		},
6425
6426		nodeIndex : function(node, normalized) {
6427			var idx = 0, lastNodeType, lastNode, nodeType;
6428
6429			if (node) {
6430				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6431					nodeType = node.nodeType;
6432
6433					// Normalize text nodes
6434					if (normalized && nodeType == 3) {
6435						if (nodeType == lastNodeType || !node.nodeValue.length)
6436							continue;
6437					}
6438					idx++;
6439					lastNodeType = nodeType;
6440				}
6441			}
6442
6443			return idx;
6444		},
6445
6446		split : function(pe, e, re) {
6447			var t = this, r = t.createRng(), bef, aft, pa;
6448
6449			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6450			// but we don't want that in our code since it serves no purpose for the end user
6451			// For example if this is chopped:
6452			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6453			// would produce:
6454			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6455			// this function will then trim of empty edges and produce:
6456			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6457			function trim(node) {
6458				var i, children = node.childNodes, type = node.nodeType;
6459
6460				function surroundedBySpans(node) {
6461					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6462					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6463					return previousIsSpan && nextIsSpan;
6464				}
6465
6466				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6467					return;
6468
6469				for (i = children.length - 1; i >= 0; i--)
6470					trim(children[i]);
6471
6472				if (type != 9) {
6473					// Keep non whitespace text nodes
6474					if (type == 3 && node.nodeValue.length > 0) {
6475						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6476						// Also keep text nodes with only spaces if surrounded by spans.
6477						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6478						var trimmedLength = tinymce.trim(node.nodeValue).length;
6479						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6480							return;
6481					} else if (type == 1) {
6482						// If the only child is a bookmark then move it up
6483						children = node.childNodes;
6484						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6485							node.parentNode.insertBefore(children[0], node);
6486
6487						// Keep non empty elements or img, hr etc
6488						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6489							return;
6490					}
6491
6492					t.remove(node);
6493				}
6494
6495				return node;
6496			};
6497
6498			if (pe && e) {
6499				// Get before chunk
6500				r.setStart(pe.parentNode, t.nodeIndex(pe));
6501				r.setEnd(e.parentNode, t.nodeIndex(e));
6502				bef = r.extractContents();
6503
6504				// Get after chunk
6505				r = t.createRng();
6506				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6507				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6508				aft = r.extractContents();
6509
6510				// Insert before chunk
6511				pa = pe.parentNode;
6512				pa.insertBefore(trim(bef), pe);
6513
6514				// Insert middle chunk
6515				if (re)
6516				pa.replaceChild(re, e);
6517			else
6518				pa.insertBefore(e, pe);
6519
6520				// Insert after chunk
6521				pa.insertBefore(trim(aft), pe);
6522				t.remove(pe);
6523
6524				return re || e;
6525			}
6526		},
6527
6528		bind : function(target, name, func, scope) {
6529			return this.events.add(target, name, func, scope || this);
6530		},
6531
6532		unbind : function(target, name, func) {
6533			return this.events.remove(target, name, func);
6534		},
6535
6536		fire : function(target, name, evt) {
6537			return this.events.fire(target, name, evt);
6538		},
6539
6540		// Returns the content editable state of a node
6541		getContentEditable: function(node) {
6542			var contentEditable;
6543
6544			// Check type
6545			if (node.nodeType != 1) {
6546				return null;
6547			}
6548
6549			// Check for fake content editable
6550			contentEditable = node.getAttribute("data-mce-contenteditable");
6551			if (contentEditable && contentEditable !== "inherit") {
6552				return contentEditable;
6553			}
6554
6555			// Check for real content editable
6556			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6557		},
6558
6559
6560		_findSib : function(node, selector, name) {
6561			var t = this, f = selector;
6562
6563			if (node) {
6564				// If expression make a function of it using is
6565				if (is(f, 'string')) {
6566					f = function(node) {
6567						return t.is(node, selector);
6568					};
6569				}
6570
6571				// Loop all siblings
6572				for (node = node[name]; node; node = node[name]) {
6573					if (f(node))
6574						return node;
6575				}
6576			}
6577
6578			return null;
6579		},
6580
6581		_isRes : function(c) {
6582			// Is live resizble element
6583			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6584		}
6585
6586		/*
6587		walk : function(n, f, s) {
6588			var d = this.doc, w;
6589
6590			if (d.createTreeWalker) {
6591				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6592
6593				while ((n = w.nextNode()) != null)
6594					f.call(s || this, n);
6595			} else
6596				tinymce.walk(n, f, 'childNodes', s);
6597		}
6598		*/
6599
6600		/*
6601		toRGB : function(s) {
6602			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6603
6604			if (c) {
6605				// #FFF -> #FFFFFF
6606				if (!is(c[3]))
6607					c[3] = c[2] = c[1];
6608
6609				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6610			}
6611
6612			return s;
6613		}
6614		*/
6615	});
6616
6617	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6618})(tinymce);
6619(function(ns) {
6620	// Range constructor
6621	function Range(dom) {
6622		var t = this,
6623			doc = dom.doc,
6624			EXTRACT = 0,
6625			CLONE = 1,
6626			DELETE = 2,
6627			TRUE = true,
6628			FALSE = false,
6629			START_OFFSET = 'startOffset',
6630			START_CONTAINER = 'startContainer',
6631			END_CONTAINER = 'endContainer',
6632			END_OFFSET = 'endOffset',
6633			extend = tinymce.extend,
6634			nodeIndex = dom.nodeIndex;
6635
6636		extend(t, {
6637			// Inital states
6638			startContainer : doc,
6639			startOffset : 0,
6640			endContainer : doc,
6641			endOffset : 0,
6642			collapsed : TRUE,
6643			commonAncestorContainer : doc,
6644
6645			// Range constants
6646			START_TO_START : 0,
6647			START_TO_END : 1,
6648			END_TO_END : 2,
6649			END_TO_START : 3,
6650
6651			// Public methods
6652			setStart : setStart,
6653			setEnd : setEnd,
6654			setStartBefore : setStartBefore,
6655			setStartAfter : setStartAfter,
6656			setEndBefore : setEndBefore,
6657			setEndAfter : setEndAfter,
6658			collapse : collapse,
6659			selectNode : selectNode,
6660			selectNodeContents : selectNodeContents,
6661			compareBoundaryPoints : compareBoundaryPoints,
6662			deleteContents : deleteContents,
6663			extractContents : extractContents,
6664			cloneContents : cloneContents,
6665			insertNode : insertNode,
6666			surroundContents : surroundContents,
6667			cloneRange : cloneRange,
6668			toStringIE : toStringIE
6669		});
6670
6671		function createDocumentFragment() {
6672			return doc.createDocumentFragment();
6673		};
6674
6675		function setStart(n, o) {
6676			_setEndPoint(TRUE, n, o);
6677		};
6678
6679		function setEnd(n, o) {
6680			_setEndPoint(FALSE, n, o);
6681		};
6682
6683		function setStartBefore(n) {
6684			setStart(n.parentNode, nodeIndex(n));
6685		};
6686
6687		function setStartAfter(n) {
6688			setStart(n.parentNode, nodeIndex(n) + 1);
6689		};
6690
6691		function setEndBefore(n) {
6692			setEnd(n.parentNode, nodeIndex(n));
6693		};
6694
6695		function setEndAfter(n) {
6696			setEnd(n.parentNode, nodeIndex(n) + 1);
6697		};
6698
6699		function collapse(ts) {
6700			if (ts) {
6701				t[END_CONTAINER] = t[START_CONTAINER];
6702				t[END_OFFSET] = t[START_OFFSET];
6703			} else {
6704				t[START_CONTAINER] = t[END_CONTAINER];
6705				t[START_OFFSET] = t[END_OFFSET];
6706			}
6707
6708			t.collapsed = TRUE;
6709		};
6710
6711		function selectNode(n) {
6712			setStartBefore(n);
6713			setEndAfter(n);
6714		};
6715
6716		function selectNodeContents(n) {
6717			setStart(n, 0);
6718			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6719		};
6720
6721		function compareBoundaryPoints(h, r) {
6722			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6723			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6724
6725			// Check START_TO_START
6726			if (h === 0)
6727				return _compareBoundaryPoints(sc, so, rsc, rso);
6728	
6729			// Check START_TO_END
6730			if (h === 1)
6731				return _compareBoundaryPoints(ec, eo, rsc, rso);
6732	
6733			// Check END_TO_END
6734			if (h === 2)
6735				return _compareBoundaryPoints(ec, eo, rec, reo);
6736	
6737			// Check END_TO_START
6738			if (h === 3) 
6739				return _compareBoundaryPoints(sc, so, rec, reo);
6740		};
6741
6742		function deleteContents() {
6743			_traverse(DELETE);
6744		};
6745
6746		function extractContents() {
6747			return _traverse(EXTRACT);
6748		};
6749
6750		function cloneContents() {
6751			return _traverse(CLONE);
6752		};
6753
6754		function insertNode(n) {
6755			var startContainer = this[START_CONTAINER],
6756				startOffset = this[START_OFFSET], nn, o;
6757
6758			// Node is TEXT_NODE or CDATA
6759			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6760				if (!startOffset) {
6761					// At the start of text
6762					startContainer.parentNode.insertBefore(n, startContainer);
6763				} else if (startOffset >= startContainer.nodeValue.length) {
6764					// At the end of text
6765					dom.insertAfter(n, startContainer);
6766				} else {
6767					// Middle, need to split
6768					nn = startContainer.splitText(startOffset);
6769					startContainer.parentNode.insertBefore(n, nn);
6770				}
6771			} else {
6772				// Insert element node
6773				if (startContainer.childNodes.length > 0)
6774					o = startContainer.childNodes[startOffset];
6775
6776				if (o)
6777					startContainer.insertBefore(n, o);
6778				else
6779					startContainer.appendChild(n);
6780			}
6781		};
6782
6783		function surroundContents(n) {
6784			var f = t.extractContents();
6785
6786			t.insertNode(n);
6787			n.appendChild(f);
6788			t.selectNode(n);
6789		};
6790
6791		function cloneRange() {
6792			return extend(new Range(dom), {
6793				startContainer : t[START_CONTAINER],
6794				startOffset : t[START_OFFSET],
6795				endContainer : t[END_CONTAINER],
6796				endOffset : t[END_OFFSET],
6797				collapsed : t.collapsed,
6798				commonAncestorContainer : t.commonAncestorContainer
6799			});
6800		};
6801
6802		// Private methods
6803
6804		function _getSelectedNode(container, offset) {
6805			var child;
6806
6807			if (container.nodeType == 3 /* TEXT_NODE */)
6808				return container;
6809
6810			if (offset < 0)
6811				return container;
6812
6813			child = container.firstChild;
6814			while (child && offset > 0) {
6815				--offset;
6816				child = child.nextSibling;
6817			}
6818
6819			if (child)
6820				return child;
6821
6822			return container;
6823		};
6824
6825		function _isCollapsed() {
6826			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6827		};
6828
6829		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6830			var c, offsetC, n, cmnRoot, childA, childB;
6831			
6832			// In the first case the boundary-points have the same container. A is before B
6833			// if its offset is less than the offset of B, A is equal to B if its offset is
6834			// equal to the offset of B, and A is after B if its offset is greater than the
6835			// offset of B.
6836			if (containerA == containerB) {
6837				if (offsetA == offsetB)
6838					return 0; // equal
6839
6840				if (offsetA < offsetB)
6841					return -1; // before
6842
6843				return 1; // after
6844			}
6845
6846			// In the second case a child node C of the container of A is an ancestor
6847			// container of B. In this case, A is before B if the offset of A is less than or
6848			// equal to the index of the child node C and A is after B otherwise.
6849			c = containerB;
6850			while (c && c.parentNode != containerA)
6851				c = c.parentNode;
6852
6853			if (c) {
6854				offsetC = 0;
6855				n = containerA.firstChild;
6856
6857				while (n != c && offsetC < offsetA) {
6858					offsetC++;
6859					n = n.nextSibling;
6860				}
6861
6862				if (offsetA <= offsetC)
6863					return -1; // before
6864
6865				return 1; // after
6866			}
6867
6868			// In the third case a child node C of the container of B is an ancestor container
6869			// of A. In this case, A is before B if the index of the child node C is less than
6870			// the offset of B and A is after B otherwise.
6871			c = containerA;
6872			while (c && c.parentNode != containerB) {
6873				c = c.parentNode;
6874			}
6875
6876			if (c) {
6877				offsetC = 0;
6878				n = containerB.firstChild;
6879
6880				while (n != c && offsetC < offsetB) {
6881					offsetC++;
6882					n = n.nextSibling;
6883				}
6884
6885				if (offsetC < offsetB)
6886					return -1; // before
6887
6888				return 1; // after
6889			}
6890
6891			// In the fourth case, none of three other cases hold: the containers of A and B
6892			// are siblings or descendants of sibling nodes. In this case, A is before B if
6893			// the container of A is before the container of B in a pre-order traversal of the
6894			// Ranges' context tree and A is after B otherwise.
6895			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6896			childA = containerA;
6897
6898			while (childA && childA.parentNode != cmnRoot)
6899				childA = childA.parentNode;
6900
6901			if (!childA)
6902				childA = cmnRoot;
6903
6904			childB = containerB;
6905			while (childB && childB.parentNode != cmnRoot)
6906				childB = childB.parentNode;
6907
6908			if (!childB)
6909				childB = cmnRoot;
6910
6911			if (childA == childB)
6912				return 0; // equal
6913
6914			n = cmnRoot.firstChild;
6915			while (n) {
6916				if (n == childA)
6917					return -1; // before
6918
6919				if (n == childB)
6920					return 1; // after
6921
6922				n = n.nextSibling;
6923			}
6924		};
6925
6926		function _setEndPoint(st, n, o) {
6927			var ec, sc;
6928
6929			if (st) {
6930				t[START_CONTAINER] = n;
6931				t[START_OFFSET] = o;
6932			} else {
6933				t[END_CONTAINER] = n;
6934				t[END_OFFSET] = o;
6935			}
6936
6937			// If one boundary-point of a Range is set to have a root container
6938			// other than the current one for the Range, the Range is collapsed to
6939			// the new position. This enforces the restriction that both boundary-
6940			// points of a Range must have the same root container.
6941			ec = t[END_CONTAINER];
6942			while (ec.parentNode)
6943				ec = ec.parentNode;
6944
6945			sc = t[START_CONTAINER];
6946			while (sc.parentNode)
6947				sc = sc.parentNode;
6948
6949			if (sc == ec) {
6950				// The start position of a Range is guaranteed to never be after the
6951				// end position. To enforce this restriction, if the start is set to
6952				// be at a position after the end, the Range is collapsed to that
6953				// position.
6954				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6955					t.collapse(st);
6956			} else
6957				t.collapse(st);
6958
6959			t.collapsed = _isCollapsed();
6960			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6961		};
6962
6963		function _traverse(how) {
6964			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6965
6966			if (t[START_CONTAINER] == t[END_CONTAINER])
6967				return _traverseSameContainer(how);
6968
6969			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6970				if (p == t[START_CONTAINER])
6971					return _traverseCommonStartContainer(c, how);
6972
6973				++endContainerDepth;
6974			}
6975
6976			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6977				if (p == t[END_CONTAINER])
6978					return _traverseCommonEndContainer(c, how);
6979
6980				++startContainerDepth;
6981			}
6982
6983			depthDiff = startContainerDepth - endContainerDepth;
6984
6985			startNode = t[START_CONTAINER];
6986			while (depthDiff > 0) {
6987				startNode = startNode.parentNode;
6988				depthDiff--;
6989			}
6990
6991			endNode = t[END_CONTAINER];
6992			while (depthDiff < 0) {
6993				endNode = endNode.parentNode;
6994				depthDiff++;
6995			}
6996
6997			// ascend the ancestor hierarchy until we have a common parent.
6998			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6999				startNode = sp;
7000				endNode = ep;
7001			}
7002
7003			return _traverseCommonAncestors(startNode, endNode, how);
7004		};
7005
7006		 function _traverseSameContainer(how) {
7007			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
7008
7009			if (how != DELETE)
7010				frag = createDocumentFragment();
7011
7012			// If selection is empty, just return the fragment
7013			if (t[START_OFFSET] == t[END_OFFSET])
7014				return frag;
7015
7016			// Text node needs special case handling
7017			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
7018				// get the substring
7019				s = t[START_CONTAINER].nodeValue;
7020				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
7021
7022				// set the original text node to its new value
7023				if (how != CLONE) {
7024					n = t[START_CONTAINER];
7025					start = t[START_OFFSET];
7026					len = t[END_OFFSET] - t[START_OFFSET];
7027
7028					if (start === 0 && len >= n.nodeValue.length - 1) {
7029						n.parentNode.removeChild(n);
7030					} else {
7031						n.deleteData(start, len);
7032					}
7033
7034					// Nothing is partially selected, so collapse to start point
7035					t.collapse(TRUE);
7036				}
7037
7038				if (how == DELETE)
7039					return;
7040
7041				if (sub.length > 0) {
7042					frag.appendChild(doc.createTextNode(sub));
7043				}
7044
7045				return frag;
7046			}
7047
7048			// Copy nodes between the start/end offsets.
7049			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
7050			cnt = t[END_OFFSET] - t[START_OFFSET];
7051
7052			while (n && cnt > 0) {
7053				sibling = n.nextSibling;
7054				xferNode = _traverseFullySelected(n, how);
7055
7056				if (frag)
7057					frag.appendChild( xferNode );
7058
7059				--cnt;
7060				n = sibling;
7061			}
7062
7063			// Nothing is partially selected, so collapse to start point
7064			if (how != CLONE)
7065				t.collapse(TRUE);
7066
7067			return frag;
7068		};
7069
7070		function _traverseCommonStartContainer(endAncestor, how) {
7071			var frag, n, endIdx, cnt, sibling, xferNode;
7072
7073			if (how != DELETE)
7074				frag = createDocumentFragment();
7075
7076			n = _traverseRightBoundary(endAncestor, how);
7077
7078			if (frag)
7079				frag.appendChild(n);
7080
7081			endIdx = nodeIndex(endAncestor);
7082			cnt = endIdx - t[START_OFFSET];
7083
7084			if (cnt <= 0) {
7085				// Collapse to just before the endAncestor, which
7086				// is partially selected.
7087				if (how != CLONE) {
7088					t.setEndBefore(endAncestor);
7089					t.collapse(FALSE);
7090				}
7091
7092				return frag;
7093			}
7094
7095			n = endAncestor.previousSibling;
7096			while (cnt > 0) {
7097				sibling = n.previousSibling;
7098				xferNode = _traverseFullySelected(n, how);
7099
7100				if (frag)
7101					frag.insertBefore(xferNode, frag.firstChild);
7102
7103				--cnt;
7104				n = sibling;
7105			}
7106
7107			// Collapse to just before the endAncestor, which
7108			// is partially selected.
7109			if (how != CLONE) {
7110				t.setEndBefore(endAncestor);
7111				t.collapse(FALSE);
7112			}
7113
7114			return frag;
7115		};
7116
7117		function _traverseCommonEndContainer(startAncestor, how) {
7118			var frag, startIdx, n, cnt, sibling, xferNode;
7119
7120			if (how != DELETE)
7121				frag = createDocumentFragment();
7122
7123			n = _traverseLeftBoundary(startAncestor, how);
7124			if (frag)
7125				frag.appendChild(n);
7126
7127			startIdx = nodeIndex(startAncestor);
7128			++startIdx; // Because we already traversed it
7129
7130			cnt = t[END_OFFSET] - startIdx;
7131			n = startAncestor.nextSibling;
7132			while (n && cnt > 0) {
7133				sibling = n.nextSibling;
7134				xferNode = _traverseFullySelected(n, how);
7135
7136				if (frag)
7137					frag.appendChild(xferNode);
7138
7139				--cnt;
7140				n = sibling;
7141			}
7142
7143			if (how != CLONE) {
7144				t.setStartAfter(startAncestor);
7145				t.collapse(TRUE);
7146			}
7147
7148			return frag;
7149		};
7150
7151		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
7152			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
7153
7154			if (how != DELETE)
7155				frag = createDocumentFragment();
7156
7157			n = _traverseLeftBoundary(startAncestor, how);
7158			if (frag)
7159				frag.appendChild(n);
7160
7161			commonParent = startAncestor.parentNode;
7162			startOffset = nodeIndex(startAncestor);
7163			endOffset = nodeIndex(endAncestor);
7164			++startOffset;
7165
7166			cnt = endOffset - startOffset;
7167			sibling = startAncestor.nextSibling;
7168
7169			while (cnt > 0) {
7170				nextSibling = sibling.nextSibling;
7171				n = _traverseFullySelected(sibling, how);
7172
7173				if (frag)
7174					frag.appendChild(n);
7175
7176				sibling = nextSibling;
7177				--cnt;
7178			}
7179
7180			n = _traverseRightBoundary(endAncestor, how);
7181
7182			if (frag)
7183				frag.appendChild(n);
7184
7185			if (how != CLONE) {
7186				t.setStartAfter(startAncestor);
7187				t.collapse(TRUE);
7188			}
7189
7190			return frag;
7191		};
7192
7193		function _traverseRightBoundary(root, how) {
7194			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
7195
7196			if (next == root)
7197				return _traverseNode(next, isFullySelected, FALSE, how);
7198
7199			parent = next.parentNode;
7200			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
7201
7202			while (parent) {
7203				while (next) {
7204					prevSibling = next.previousSibling;
7205					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
7206
7207					if (how != DELETE)
7208						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
7209
7210					isFullySelected = TRUE;
7211					next = prevSibling;
7212				}
7213
7214				if (parent == root)
7215					return clonedParent;
7216
7217				next = parent.previousSibling;
7218				parent = parent.parentNode;
7219
7220				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
7221
7222				if (how != DELETE)
7223					clonedGrandParent.appendChild(clonedParent);
7224
7225				clonedParent = clonedGrandParent;
7226			}
7227		};
7228
7229		function _traverseLeftBoundary(root, how) {
7230			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
7231
7232			if (next == root)
7233				return _traverseNode(next, isFullySelected, TRUE, how);
7234
7235			parent = next.parentNode;
7236			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
7237
7238			while (parent) {
7239				while (next) {
7240					nextSibling = next.nextSibling;
7241					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
7242
7243					if (how != DELETE)
7244						clonedParent.appendChild(clonedChild);
7245
7246					isFullySelected = TRUE;
7247					next = nextSibling;
7248				}
7249
7250				if (parent == root)
7251					return clonedParent;
7252
7253				next = parent.nextSibling;
7254				parent = parent.parentNode;
7255
7256				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
7257
7258				if (how != DELETE)
7259					clonedGrandParent.appendChild(clonedParent);
7260
7261				clonedParent = clonedGrandParent;
7262			}
7263		};
7264
7265		function _traverseNode(n, isFullySelected, isLeft, how) {
7266			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
7267
7268			if (isFullySelected)
7269				return _traverseFullySelected(n, how);
7270
7271			if (n.nodeType == 3 /* TEXT_NODE */) {
7272				txtValue = n.nodeValue;
7273
7274				if (isLeft) {
7275					offset = t[START_OFFSET];
7276					newNodeValue = txtValue.substring(offset);
7277					oldNodeValue = txtValue.substring(0, offset);
7278				} else {
7279					offset = t[END_OFFSET];
7280					newNodeValue = txtValue.substring(0, offset);
7281					oldNodeValue = txtValue.substring(offset);
7282				}
7283
7284				if (how != CLONE)
7285					n.nodeValue = oldNodeValue;
7286
7287				if (how == DELETE)
7288					return;
7289
7290				newNode = dom.clone(n, FALSE);
7291				newNode.nodeValue = newNodeValue;
7292
7293				return newNode;
7294			}
7295
7296			if (how == DELETE)
7297				return;
7298
7299			return dom.clone(n, FALSE);
7300		};
7301
7302		function _traverseFullySelected(n, how) {
7303			if (how != DELETE)
7304				return how == CLONE ? dom.clone(n, TRUE) : n;
7305
7306			n.parentNode.removeChild(n);
7307		};
7308
7309		function toStringIE() {
7310			return dom.create('body', null, cloneContents()).outerText;
7311		}
7312		
7313		return t;
7314	};
7315
7316	ns.Range = Range;
7317
7318	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7319	Range.prototype.toString = function() {
7320		return this.toStringIE();
7321	};
7322})(tinymce.dom);
7323(function() {
7324	function Selection(selection) {
7325		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7326
7327		function getPosition(rng, start) {
7328			var checkRng, startIndex = 0, endIndex, inside,
7329				children, child, offset, index, position = -1, parent;
7330
7331			// Setup test range, collapse it and get the parent
7332			checkRng = rng.duplicate();
7333			checkRng.collapse(start);
7334			parent = checkRng.parentElement();
7335
7336			// Check if the selection is within the right document
7337			if (parent.ownerDocument !== selection.dom.doc)
7338				return;
7339
7340			// IE will report non editable elements as it's parent so look for an editable one
7341			while (parent.contentEditable === "false") {
7342				parent = parent.parentNode;
7343			}
7344
7345			// If parent doesn't have any children then return that we are inside the element
7346			if (!parent.hasChildNodes()) {
7347				return {node : parent, inside : 1};
7348			}
7349
7350			// Setup node list and endIndex
7351			children = parent.children;
7352			endIndex = children.length - 1;
7353
7354			// Perform a binary search for the position
7355			while (startIndex <= endIndex) {
7356				index = Math.floor((startIndex + endIndex) / 2);
7357
7358				// Move selection to node and compare the ranges
7359				child = children[index];
7360				checkRng.moveToElementText(child);
7361				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7362
7363				// Before/after or an exact match
7364				if (position > 0) {
7365					endIndex = index - 1;
7366				} else if (position < 0) {
7367					startIndex = index + 1;
7368				} else {
7369					return {node : child};
7370				}
7371			}
7372
7373			// Check if child position is before or we didn't find a position
7374			if (position < 0) {
7375				// No element child was found use the parent element and the offset inside that
7376				if (!child) {
7377					checkRng.moveToElementText(parent);
7378					checkRng.collapse(true);
7379					child = parent;
7380					inside = true;
7381				} else
7382					checkRng.collapse(false);
7383
7384				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7385				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7386				offset = 0;
7387				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7388					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7389						break;
7390					}
7391
7392					offset++;
7393				}
7394			} else {
7395				// Child position is after the selection endpoint
7396				checkRng.collapse(true);
7397
7398				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7399				offset = 0;
7400				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7401					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7402						break;
7403					}
7404
7405					offset++;
7406				}
7407			}
7408
7409			return {node : child, position : position, offset : offset, inside : inside};
7410		};
7411
7412		// Returns a W3C DOM compatible range object by using the IE Range API
7413		function getRange() {
7414			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7415
7416			// If selection is outside the current document just return an empty range
7417			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7418			if (element.ownerDocument != dom.doc)
7419				return domRange;
7420
7421			collapsed = selection.isCollapsed();
7422
7423			// Handle control selection
7424			if (ieRange.item) {
7425				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7426				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7427
7428				return domRange;
7429			}
7430
7431			function findEndPoint(start) {
7432				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7433
7434				container = endPoint.node;
7435				offset = endPoint.offset;
7436
7437				if (endPoint.inside && !container.hasChildNodes()) {
7438					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7439					return;
7440				}
7441
7442				if (offset === undef) {
7443					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7444					return;
7445				}
7446
7447				if (endPoint.position < 0) {
7448					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7449
7450					if (!sibling) {
7451						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7452						return;
7453					}
7454
7455					if (!offset) {
7456						if (sibling.nodeType == 3)
7457							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7458						else
7459							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7460
7461						return;
7462					}
7463
7464					// Find the text node and offset
7465					while (sibling) {
7466						nodeValue = sibling.nodeValue;
7467						textNodeOffset += nodeValue.length;
7468
7469						// We are at or passed the position we where looking for
7470						if (textNodeOffset >= offset) {
7471							container = sibling;
7472							textNodeOffset -= offset;
7473							textNodeOffset = nodeValue.length - textNodeOffset;
7474							break;
7475						}
7476
7477						sibling = sibling.nextSibling;
7478					}
7479				} else {
7480					// Find the text node and offset
7481					sibling = container.previousSibling;
7482
7483					if (!sibling)
7484						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7485
7486					// If there isn't any text to loop then use the first position
7487					if (!offset) {
7488						if (container.nodeType == 3)
7489							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7490						else
7491							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7492
7493						return;
7494					}
7495
7496					while (sibling) {
7497						textNodeOffset += sibling.nodeValue.length;
7498
7499						// We are at or passed the position we where looking for
7500						if (textNodeOffset >= offset) {
7501							container = sibling;
7502							textNodeOffset -= offset;
7503							break;
7504						}
7505
7506						sibling = sibling.previousSibling;
7507					}
7508				}
7509
7510				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7511			};
7512
7513			try {
7514				// Find start point
7515				findEndPoint(true);
7516
7517				// Find end point if needed
7518				if (!collapsed)
7519					findEndPoint();
7520			} catch (ex) {
7521				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7522				// access the nodeValue or other properties of text nodes. This seems to happend when
7523				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7524				if (ex.number == -2147024809) {
7525					// Get the current selection
7526					bookmark = self.getBookmark(2);
7527
7528					// Get start element
7529					tmpRange = ieRange.duplicate();
7530					tmpRange.collapse(true);
7531					element = tmpRange.parentElement();
7532
7533					// Get end element
7534					if (!collapsed) {
7535						tmpRange = ieRange.duplicate();
7536						tmpRange.collapse(false);
7537						element2 = tmpRange.parentElement();
7538						element2.innerHTML = element2.innerHTML;
7539					}
7540
7541					// Remove the broken elements
7542					element.innerHTML = element.innerHTML;
7543
7544					// Restore the selection
7545					self.moveToBookmark(bookmark);
7546
7547					// Since the range has moved we need to re-get it
7548					ieRange = selection.getRng();
7549
7550					// Find start point
7551					findEndPoint(true);
7552
7553					// Find end point if needed
7554					if (!collapsed)
7555						findEndPoint();
7556				} else
7557					throw ex; // Throw other errors
7558			}
7559
7560			return domRange;
7561		};
7562
7563		this.getBookmark = function(type) {
7564			var rng = selection.getRng(), start, end, bookmark = {};
7565
7566			function getIndexes(node) {
7567				var parent, root, children, i, indexes = [];
7568
7569				parent = node.parentNode;
7570				root = dom.getRoot().parentNode;
7571
7572				while (parent != root && parent.nodeType !== 9) {
7573					children = parent.children;
7574
7575					i = children.length;
7576					while (i--) {
7577						if (node === children[i]) {
7578							indexes.push(i);
7579							break;
7580						}
7581					}
7582
7583					node = parent;
7584					parent = parent.parentNode;
7585				}
7586
7587				return indexes;
7588			};
7589
7590			function getBookmarkEndPoint(start) {
7591				var position;
7592
7593				position = getPosition(rng, start);
7594				if (position) {
7595					return {
7596						position : position.position,
7597						offset : position.offset,
7598						indexes : getIndexes(position.node),
7599						inside : position.inside
7600					};
7601				}
7602			};
7603
7604			// Non ubstructive bookmark
7605			if (type === 2) {
7606				// Handle text selection
7607				if (!rng.item) {
7608					bookmark.start = getBookmarkEndPoint(true);
7609
7610					if (!selection.isCollapsed())
7611						bookmark.end = getBookmarkEndPoint();
7612				} else
7613					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7614			}
7615
7616			return bookmark;
7617		};
7618
7619		this.moveToBookmark = function(bookmark) {
7620			var rng, body = dom.doc.body;
7621
7622			function resolveIndexes(indexes) {
7623				var node, i, idx, children;
7624
7625				node = dom.getRoot();
7626				for (i = indexes.length - 1; i >= 0; i--) {
7627					children = node.children;
7628					idx = indexes[i];
7629
7630					if (idx <= children.length - 1) {
7631						node = children[idx];
7632					}
7633				}
7634
7635				return node;
7636			};
7637			
7638			function setBookmarkEndPoint(start) {
7639				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7640
7641				if (endPoint) {
7642					moveLeft = endPoint.position > 0;
7643
7644					moveRng = body.createTextRange();
7645					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7646
7647					offset = endPoint.offset;
7648					if (offset !== undef) {
7649						moveRng.collapse(endPoint.inside || moveLeft);
7650						moveRng.moveStart('character', moveLeft ? -offset : offset);
7651					} else
7652						moveRng.collapse(start);
7653
7654					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7655
7656					if (start)
7657						rng.collapse(true);
7658				}
7659			};
7660
7661			if (bookmark.start) {
7662				if (bookmark.start.ctrl) {
7663					rng = body.createControlRange();
7664					rng.addElement(resolveIndexes(bookmark.start.indexes));
7665					rng.select();
7666				} else {
7667					rng = body.createTextRange();
7668					setBookmarkEndPoint(true);
7669					setBookmarkEndPoint();
7670					rng.select();
7671				}
7672			}
7673		};
7674
7675		this.addRange = function(rng) {
7676			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
7677				doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
7678
7679			function setEndPoint(start) {
7680				var container, offset, marker, tmpRng, nodes;
7681
7682				marker = dom.create('a');
7683				container = start ? startContainer : endContainer;
7684				offset = start ? startOffset : endOffset;
7685				tmpRng = ieRng.duplicate();
7686
7687				if (container == doc || container == doc.documentElement) {
7688					container = body;
7689					offset = 0;
7690				}
7691
7692				if (container.nodeType == 3) {
7693					container.parentNode.insertBefore(marker, container);
7694					tmpRng.moveToElementText(marker);
7695					tmpRng.moveStart('character', offset);
7696					dom.remove(marker);
7697					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7698				} else {
7699					nodes = container.childNodes;
7700
7701					if (nodes.length) {
7702						if (offset >= nodes.length) {
7703							dom.insertAfter(marker, nodes[nodes.length - 1]);
7704						} else {
7705							container.insertBefore(marker, nodes[offset]);
7706						}
7707
7708						tmpRng.moveToElementText(marker);
7709					} else if (container.canHaveHTML) {
7710						// Empty node selection for example <div>|</div>
7711						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7712						container.innerHTML = '<span>\uFEFF</span>';
7713						marker = container.firstChild;
7714						tmpRng.moveToElementText(marker);
7715						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7716					}
7717
7718					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7719					dom.remove(marker);
7720				}
7721			}
7722
7723			// Setup some shorter versions
7724			startContainer = rng.startContainer;
7725			startOffset = rng.startOffset;
7726			endContainer = rng.endContainer;
7727			endOffset = rng.endOffset;
7728			ieRng = body.createTextRange();
7729
7730			// If single element selection then try making a control selection out of it
7731			if (startContainer == endContainer && startContainer.nodeType == 1) {
7732				// Trick to place the caret inside an empty block element like <p></p>
7733				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7734					if (startContainer.canHaveHTML) {
7735						// Check if previous sibling is an empty block if it is then we need to render it
7736						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7737						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7738						sibling = startContainer.previousSibling;
7739						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7740							sibling.innerHTML = '\uFEFF';
7741						} else {
7742							sibling = null;
7743						}
7744
7745						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7746						ieRng.moveToElementText(startContainer.lastChild);
7747						ieRng.select();
7748						dom.doc.selection.clear();
7749						startContainer.innerHTML = '';
7750
7751						if (sibling) {
7752							sibling.innerHTML = '';
7753						}
7754						return;
7755					} else {
7756						startOffset = dom.nodeIndex(startContainer);
7757						startContainer = startContainer.parentNode;
7758					}
7759				}
7760
7761				if (startOffset == endOffset - 1) {
7762					try {
7763						ctrlElm = startContainer.childNodes[startOffset];
7764						ctrlRng = body.createControlRange();
7765						ctrlRng.addElement(ctrlElm);
7766						ctrlRng.select();
7767
7768						// Check if the range produced is on the correct element and is a control range
7769						// On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
7770						nativeRng = selection.getRng();
7771						if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
7772							return;
7773						}
7774					} catch (ex) {
7775						// Ignore
7776					}
7777				}
7778			}
7779
7780			// Set start/end point of selection
7781			setEndPoint(true);
7782			setEndPoint();
7783
7784			// Select the new range and scroll it into view
7785			ieRng.select();
7786		};
7787
7788		// Expose range method
7789		this.getRangeAt = getRange;
7790	};
7791
7792	// Expose the selection object
7793	tinymce.dom.TridentSelection = Selection;
7794})();
7795
7796/*
7797 * Sizzle CSS Selector Engine
7798 *  Copyright, The Dojo Foundation
7799 *  Released under the MIT, BSD, and GPL Licenses.
7800 *  More information: http://sizzlejs.com/
7801 */
7802(function(){
7803
7804var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7805	expando = "sizcache",
7806	done = 0,
7807	toString = Object.prototype.toString,
7808	hasDuplicate = false,
7809	baseHasDuplicate = true,
7810	rBackslash = /\\/g,
7811	rReturn = /\r\n/g,
7812	rNonWord = /\W/;
7813
7814// Here we check if the JavaScript engine is using some sort of
7815// optimization where it does not always call our comparision
7816// function. If that is the case, discard the hasDuplicate value.
7817//   Thus far that includes Google Chrome.
7818[0, 0].sort(function() {
7819	baseHasDuplicate = false;
7820	return 0;
7821});
7822
7823var Sizzle = function( selector, context, results, seed ) {
7824	results = results || [];
7825	context = context || document;
7826
7827	var origContext = context;
7828
7829	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7830		return [];
7831	}
7832
7833	if ( !selector || typeof selector !== "string" ) {
7834		return results;
7835	}
7836
7837	var m, set, checkSet, extra, ret, cur, pop, i,
7838		prune = true,
7839		contextXML = Sizzle.isXML( context ),
7840		parts = [],
7841		soFar = selector;
7842
7843	// Reset the position of the chunker regexp (start from head)
7844	do {
7845		chunker.exec( "" );
7846		m = chunker.exec( soFar );
7847
7848		if ( m ) {
7849			soFar = m[3];
7850
7851			parts.push( m[1] );
7852
7853			if ( m[2] ) {
7854				extra = m[3];
7855				break;
7856			}
7857		}
7858	} while ( m );
7859
7860	if ( parts.length > 1 && origPOS.exec( selector ) ) {
7861
7862		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7863			set = posProcess( parts[0] + parts[1], context, seed );
7864
7865		} else {
7866			set = Expr.relative[ parts[0] ] ?
7867				[ context ] :
7868				Sizzle( parts.shift(), context );
7869
7870			while ( parts.length ) {
7871				selector = parts.shift();
7872
7873				if ( Expr.relative[ selector ] ) {
7874					selector += parts.shift();
7875				}
7876
7877				set = posProcess( selector, set, seed );
7878			}
7879		}
7880
7881	} else {
7882		// Take a shortcut and set the context if the root selector is an ID
7883		// (but not if it'll be faster if the inner selector is an ID)
7884		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7885				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7886
7887			ret = Sizzle.find( parts.shift(), context, contextXML );
7888			context = ret.expr ?
7889				Sizzle.filter( ret.expr, ret.set )[0] :
7890				ret.set[0];
7891		}
7892
7893		if ( context ) {
7894			ret = seed ?
7895				{ expr: parts.pop(), set: makeArray(seed) } :
7896				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7897
7898			set = ret.expr ?
7899				Sizzle.filter( ret.expr, ret.set ) :
7900				ret.set;
7901
7902			if ( parts.length > 0 ) {
7903				checkSet = makeArray( set );
7904
7905			} else {
7906				prune = false;
7907			}
7908
7909			while ( parts.length ) {
7910				cur = parts.pop();
7911				pop = cur;
7912
7913				if ( !Expr.relative[ cur ] ) {
7914					cur = "";
7915				} else {
7916					pop = parts.pop();
7917				}
7918
7919				if ( pop == null ) {
7920					pop = context;
7921				}
7922
7923				Expr.relative[ cur ]( checkSet, pop, contextXML );
7924			}
7925
7926		} else {
7927			checkSet = parts = [];
7928		}
7929	}
7930
7931	if ( !checkSet ) {
7932		checkSet = set;
7933	}
7934
7935	if ( !checkSet ) {
7936		Sizzle.error( cur || selector );
7937	}
7938
7939	if ( toString.call(checkSet) === "[object Array]" ) {
7940		if ( !prune ) {
7941			results.push.apply( results, checkSet );
7942
7943		} else if ( context && context.nodeType === 1 ) {
7944			for ( i = 0; checkSet[i] != null; i++ ) {
7945				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7946					results.push( set[i] );
7947				}
7948			}
7949
7950		} else {
7951			for ( i = 0; checkSet[i] != null; i++ ) {
7952				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7953					results.push( set[i] );
7954				}
7955			}
7956		}
7957
7958	} else {
7959		makeArray( checkSet, results );
7960	}
7961
7962	if ( extra ) {
7963		Sizzle( extra, origContext, results, seed );
7964		Sizzle.uniqueSort( results );
7965	}
7966
7967	return results;
7968};
7969
7970Sizzle.uniqueSort = function( results ) {
7971	if ( sortOrder ) {
7972		hasDuplicate = baseHasDuplicate;
7973		results.sort( sortOrder );
7974
7975		if ( hasDuplicate ) {
7976			for ( var i = 1; i < results.length; i++ ) {
7977				if ( results[i] === results[ i - 1 ] ) {
7978					results.splice( i--, 1 );
7979				}
7980			}
7981		}
7982	}
7983
7984	return results;
7985};
7986
7987Sizzle.matches = function( expr, set ) {
7988	return Sizzle( expr, null, null, set );
7989};
7990
7991Sizzle.matchesSelector = function( node, expr ) {
7992	return Sizzle( expr, null, null, [node] ).length > 0;
7993};
7994
7995Sizzle.find = function( expr, context, isXML ) {
7996	var set, i, len, match, type, left;
7997
7998	if ( !expr ) {
7999		return [];
8000	}
8001
8002	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
8003		type = Expr.order[i];
8004
8005		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
8006			left = match[1];
8007			match.splice( 1, 1 );
8008
8009			if ( left.substr( left.length - 1 ) !== "\\" ) {
8010				match[1] = (match[1] || "").replace( rBackslash, "" );
8011				set = Expr.find[ type ]( match, context, isXML );
8012
8013				if ( set != null ) {
8014					expr = expr.replace( Expr.match[ type ], "" );
8015					break;
8016				}
8017			}
8018		}
8019	}
8020
8021	if ( !set ) {
8022		set = typeof context.getElementsByTagName !== "undefined" ?
8023			context.getElementsByTagName( "*" ) :
8024			[];
8025	}
8026
8027	return { set: set, expr: expr };
8028};
8029
8030Sizzle.filter = function( expr, set, inplace, not ) {
8031	var match, anyFound,
8032		type, found, item, filter, left,
8033		i, pass,
8034		old = expr,
8035		result = [],
8036		curLoop = set,
8037		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
8038
8039	while ( expr && set.length ) {
8040		for ( type in Expr.filter ) {
8041			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
8042				filter = Expr.filter[ type ];
8043				left = match[1];
8044
8045				anyFound = false;
8046
8047				match.splice(1,1);
8048
8049				if ( left.substr( left.length - 1 ) === "\\" ) {
8050					continue;
8051				}
8052
8053				if ( curLoop === result ) {
8054					result = [];
8055				}
8056
8057				if ( Expr.preFilter[ type ] ) {
8058					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
8059
8060					if ( !match ) {
8061						anyFound = found = true;
8062
8063					} else if ( match === true ) {
8064						continue;
8065					}
8066				}
8067
8068				if ( match ) {
8069					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
8070						if ( item ) {
8071							found = filter( item, match, i, curLoop );
8072							pass = not ^ found;
8073
8074							if ( inplace && found != null ) {
8075								if ( pass ) {
8076									anyFound = true;
8077
8078								} else {
8079									curLoop[i] = false;
8080								}
8081
8082							} else if ( pass ) {
8083								result.push( item );
8084								anyFound = true;
8085							}
8086						}
8087					}
8088				}
8089
8090				if ( found !== undefined ) {
8091					if ( !inplace ) {
8092						curLoop = result;
8093					}
8094
8095					expr = expr.replace( Expr.match[ type ], "" );
8096
8097					if ( !anyFound ) {
8098						return [];
8099					}
8100
8101					break;
8102				}
8103			}
8104		}
8105
8106		// Improper expression
8107		if ( expr === old ) {
8108			if ( anyFound == null ) {
8109				Sizzle.error( expr );
8110
8111			} else {
8112				break;
8113			}
8114		}
8115
8116		old = expr;
8117	}
8118
8119	return curLoop;
8120};
8121
8122Sizzle.error = function( msg ) {
8123	throw new Error( "Syntax error, unrecognized expression: " + msg );
8124};
8125
8126var getText = Sizzle.getText = function( elem ) {
8127    var i, node,
8128		nodeType = elem.nodeType,
8129		ret = "";
8130
8131	if ( nodeType ) {
8132		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
8133			// Use textContent || innerText for elements
8134			if ( typeof elem.textContent === 'string' ) {
8135				return elem.textContent;
8136			} else if ( typeof elem.innerText === 'string' ) {
8137				// Replace IE's carriage returns
8138				return elem.innerText.replace( rReturn, '' );
8139			} else {
8140				// Traverse it's children
8141				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
8142					ret += getText( elem );
8143				}
8144			}
8145		} else if ( nodeType === 3 || nodeType === 4 ) {
8146			return elem.nodeValue;
8147		}
8148	} else {
8149
8150		// If no nodeType, this is expected to be an array
8151		for ( i = 0; (node = elem[i]); i++ ) {
8152			// Do not traverse comment nodes
8153			if ( node.nodeType !== 8 ) {
8154				ret += getText( node );
8155			}
8156		}
8157	}
8158	return ret;
8159};
8160
8161var Expr = Sizzle.selectors = {
8162	order: [ "ID", "NAME", "TAG" ],
8163
8164	match: {
8165		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
8166		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
8167		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
8168		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
8169		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
8170		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
8171		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
8172		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
8173	},
8174
8175	leftMatch: {},
8176
8177	attrMap: {
8178		"class": "className",
8179		"for": "htmlFor"
8180	},
8181
8182	attrHandle: {
8183		href: function( elem ) {
8184			return elem.getAttribute( "href" );
8185		},
8186		type: function( elem ) {
8187			return elem.getAttribute( "type" );
8188		}
8189	},
8190
8191	relative: {
8192		"+": function(checkSet, part){
8193			var isPartStr = typeof part === "string",
8194				isTag = isPartStr && !rNonWord.test( part ),
8195				isPartStrNotTag = isPartStr && !isTag;
8196
8197			if ( isTag ) {
8198				part = part.toLowerCase();
8199			}
8200
8201			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
8202				if ( (elem = checkSet[i]) ) {
8203					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
8204
8205					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
8206						elem || false :
8207						elem === part;
8208				}
8209			}
8210
8211			if ( isPartStrNotTag ) {
8212				Sizzle.filter( part, checkSet, true );
8213			}
8214		},
8215
8216		">": function( checkSet, part ) {
8217			var elem,
8218				isPartStr = typeof part === "string",
8219				i = 0,
8220				l = checkSet.length;
8221
8222			if ( isPartStr && !rNonWord.test( part ) ) {
8223				part = part.toLowerCase();
8224
8225				for ( ; i < l; i++ ) {
8226					elem = checkSet[i];
8227
8228					if ( elem ) {
8229						var parent = elem.parentNode;
8230						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
8231					}
8232				}
8233
8234			} else {
8235				for ( ; i < l; i++ ) {
8236					elem = checkSet[i];
8237
8238					if ( elem ) {
8239						checkSet[i] = isPartStr ?
8240							elem.parentNode :
8241							elem.parentNode === part;
8242					}
8243				}
8244
8245				if ( isPartStr ) {
8246					Sizzle.filter( part, checkSet, true );
8247				}
8248			}
8249		},
8250
8251		"": function(checkSet, part, isXML){
8252			var nodeCheck,
8253				doneName = done++,
8254				checkFn = dirCheck;
8255
8256			if ( typeof part === "string" && !rNonWord.test( part ) ) {
8257				part = part.toLowerCase();
8258				nodeCheck = part;
8259				checkFn = dirNodeCheck;
8260			}
8261
8262			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
8263		},
8264
8265		"~": function( checkSet, part, isXML ) {
8266			var nodeCheck,
8267				doneName = done++,
8268				checkFn = dirCheck;
8269
8270			if ( typeof part === "string" && !rNonWord.test( part ) ) {
8271				part = part.toLowerCase();
8272				nodeCheck = part;
8273				checkFn = dirNodeCheck;
8274			}
8275
8276			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
8277		}
8278	},
8279
8280	find: {
8281		ID: function( match, context, isXML ) {
8282			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8283				var m = context.getElementById(match[1]);
8284				// Check parentNode to catch when Blackberry 4.6 returns
8285				// nodes that are no longer in the document #6963
8286				return m && m.parentNode ? [m] : [];
8287			}
8288		},
8289
8290		NAME: function( match, context ) {
8291			if ( typeof context.getElementsByName !== "undefined" ) {
8292				var ret = [],
8293					results = context.getElementsByName( match[1] );
8294
8295				for ( var i = 0, l = results.length; i < l; i++ ) {
8296					if ( results[i].getAttribute("name") === match[1] ) {
8297						ret.push( results[i] );
8298					}
8299				}
8300
8301				return ret.length === 0 ? null : ret;
8302			}
8303		},
8304
8305		TAG: function( match, context ) {
8306			if ( typeof context.getElementsByTagName !== "undefined" ) {
8307				return context.getElementsByTagName( match[1] );
8308			}
8309		}
8310	},
8311	preFilter: {
8312		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
8313			match = " " + match[1].replace( rBackslash, "" ) + " ";
8314
8315			if ( isXML ) {
8316				return match;
8317			}
8318
8319			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8320				if ( elem ) {
8321					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8322						if ( !inplace ) {
8323							result.push( elem );
8324						}
8325
8326					} else if ( inplace ) {
8327						curLoop[i] = false;
8328					}
8329				}
8330			}
8331
8332			return false;
8333		},
8334
8335		ID: function( match ) {
8336			return match[1].replace( rBackslash, "" );
8337		},
8338
8339		TAG: function( match, curLoop ) {
8340			return match[1].replace( rBackslash, "" ).toLowerCase();
8341		},
8342
8343		CHILD: function( match ) {
8344			if ( match[1] === "nth" ) {
8345				if ( !match[2] ) {
8346					Sizzle.error( match[0] );
8347				}
8348
8349				match[2] = match[2].replace(/^\+|\s*/g, '');
8350
8351				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8352				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8353					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8354					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8355
8356				// calculate the numbers (first)n+(last) including if they are negative
8357				match[2] = (test[1] + (test[2] || 1)) - 0;
8358				match[3] = test[3] - 0;
8359			}
8360			else if ( match[2] ) {
8361				Sizzle.error( match[0] );
8362			}
8363
8364			// TODO: Move to normal caching system
8365			match[0] = done++;
8366
8367			return match;
8368		},
8369
8370		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8371			var name = match[1] = match[1].replace( rBackslash, "" );
8372
8373			if ( !isXML && Expr.attrMap[name] ) {
8374				match[1] = Expr.attrMap[name];
8375			}
8376
8377			// Handle if an un-quoted value was used
8378			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8379
8380			if ( match[2] === "~=" ) {
8381				match[4] = " " + match[4] + " ";
8382			}
8383
8384			return match;
8385		},
8386
8387		PSEUDO: function( match, curLoop, inplace, result, not ) {
8388			if ( match[1] === "not" ) {
8389				// If we're dealing with a complex expression, or a simple one
8390				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8391					match[3] = Sizzle(match[3], null, null, curLoop);
8392
8393				} else {
8394					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8395
8396					if ( !inplace ) {
8397						result.push.apply( result, ret );
8398					}
8399
8400					return false;
8401				}
8402
8403			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8404				return true;
8405			}
8406
8407			return match;
8408		},
8409
8410		POS: function( match ) {
8411			match.unshift( true );
8412
8413			return match;
8414		}
8415	},
8416
8417	filters: {
8418		enabled: function( elem ) {
8419			return elem.disabled === false && elem.type !== "hidden";
8420		},
8421
8422		disabled: function( elem ) {
8423			return elem.disabled === true;
8424		},
8425
8426		checked: function( elem ) {
8427			return elem.checked === true;
8428		},
8429
8430		selected: function( elem ) {
8431			// Accessing this property makes selected-by-default
8432			// options in Safari work properly
8433			if ( elem.parentNode ) {
8434				elem.parentNode.selectedIndex;
8435			}
8436
8437			return elem.selected === true;
8438		},
8439
8440		parent: function( elem ) {
8441			return !!elem.firstChild;
8442		},
8443
8444		empty: function( elem ) {
8445			return !elem.firstChild;
8446		},
8447
8448		has: function( elem, i, match ) {
8449			return !!Sizzle( match[3], elem ).length;
8450		},
8451
8452		header: function( elem ) {
8453			return (/h\d/i).test( elem.nodeName );
8454		},
8455
8456		text: function( elem ) {
8457			var attr = elem.getAttribute( "type" ), type = elem.type;
8458			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8459			// use getAttribute instead to test this case
8460			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8461		},
8462
8463		radio: function( elem ) {
8464			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8465		},
8466
8467		checkbox: function( elem ) {
8468			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8469		},
8470
8471		file: function( elem ) {
8472			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8473		},
8474
8475		password: function( elem ) {
8476			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8477		},
8478
8479		submit: function( elem ) {
8480			var name = elem.nodeName.toLowerCase();
8481			return (name === "input" || name === "button") && "submit" === elem.type;
8482		},
8483
8484		image: function( elem ) {
8485			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8486		},
8487
8488		reset: function( elem ) {
8489			var name = elem.nodeName.toLowerCase();
8490			return (name === "input" || name === "button") && "reset" === elem.type;
8491		},
8492
8493		button: function( elem ) {
8494			var name = elem.nodeName.toLowerCase();
8495			return name === "input" && "button" === elem.type || name === "button";
8496		},
8497
8498		input: function( elem ) {
8499			return (/input|select|textarea|button/i).test( elem.nodeName );
8500		},
8501
8502		focus: function( elem ) {
8503			return elem === elem.ownerDocument.activeElement;
8504		}
8505	},
8506	setFilters: {
8507		first: function( elem, i ) {
8508			return i === 0;
8509		},
8510
8511		last: function( elem, i, match, array ) {
8512			return i === array.length - 1;
8513		},
8514
8515		even: function( elem, i ) {
8516			return i % 2 === 0;
8517		},
8518
8519		odd: function( elem, i ) {
8520			return i % 2 === 1;
8521		},
8522
8523		lt: function( elem, i, match ) {
8524			return i < match[3] - 0;
8525		},
8526
8527		gt: function( elem, i, match ) {
8528			return i > match[3] - 0;
8529		},
8530
8531		nth: function( elem, i, match ) {
8532			return match[3] - 0 === i;
8533		},
8534
8535		eq: function( elem, i, match ) {
8536			return match[3] - 0 === i;
8537		}
8538	},
8539	filter: {
8540		PSEUDO: function( elem, match, i, array ) {
8541			var name = match[1],
8542				filter = Expr.filters[ name ];
8543
8544			if ( filter ) {
8545				return filter( elem, i, match, array );
8546
8547			} else if ( name === "contains" ) {
8548				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8549
8550			} else if ( name === "not" ) {
8551				var not = match[3];
8552
8553				for ( var j = 0, l = not.length; j < l; j++ ) {
8554					if ( not[j] === elem ) {
8555						return false;
8556					}
8557				}
8558
8559				return true;
8560
8561			} else {
8562				Sizzle.error( name );
8563			}
8564		},
8565
8566		CHILD: function( elem, match ) {
8567			var first, last,
8568				doneName, parent, cache,
8569				count, diff,
8570				type = match[1],
8571				node = elem;
8572
8573			switch ( type ) {
8574				case "only":
8575				case "first":
8576					while ( (node = node.previousSibling) ) {
8577						if ( node.nodeType === 1 ) {
8578							return false;
8579						}
8580					}
8581
8582					if ( type === "first" ) {
8583						return true;
8584					}
8585
8586					node = elem;
8587
8588					/* falls through */
8589				case "last":
8590					while ( (node = node.nextSibling) ) {
8591						if ( node.nodeType === 1 ) {
8592							return false;
8593						}
8594					}
8595
8596					return true;
8597
8598				case "nth":
8599					first = match[2];
8600					last = match[3];
8601
8602					if ( first === 1 && last === 0 ) {
8603						return true;
8604					}
8605
8606					doneName = match[0];
8607					parent = elem.parentNode;
8608
8609					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8610						count = 0;
8611
8612						for ( node = parent.firstChild; node; node = node.nextSibling ) {
8613							if ( node.nodeType === 1 ) {
8614								node.nodeIndex = ++count;
8615							}
8616						}
8617
8618						parent[ expando ] = doneName;
8619					}
8620
8621					diff = elem.nodeIndex - last;
8622
8623					if ( first === 0 ) {
8624						return diff === 0;
8625
8626					} else {
8627						return ( diff % first === 0 && diff / first >= 0 );
8628					}
8629			}
8630		},
8631
8632		ID: function( elem, match ) {
8633			return elem.nodeType === 1 && elem.getAttribute("id") === match;
8634		},
8635
8636		TAG: function( elem, match ) {
8637			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8638		},
8639
8640		CLASS: function( elem, match ) {
8641			return (" " + (elem.className || elem.getAttribute("class")) + " ")
8642				.indexOf( match ) > -1;
8643		},
8644
8645		ATTR: function( elem, match ) {
8646			var name = match[1],
8647				result = Sizzle.attr ?
8648					Sizzle.attr( elem, name ) :
8649					Expr.attrHandle[ name ] ?
8650					Expr.attrHandle[ name ]( elem ) :
8651					elem[ name ] != null ?
8652						elem[ name ] :
8653						elem.getAttribute( name ),
8654				value = result + "",
8655				type = match[2],
8656				check = match[4];
8657
8658			return result == null ?
8659				type === "!=" :
8660				!type && Sizzle.attr ?
8661				result != null :
8662				type === "=" ?
8663				value === check :
8664				type === "*=" ?
8665				value.indexOf(check) >= 0 :
8666				type === "~=" ?
8667				(" " + value + " ").indexOf(check) >= 0 :
8668				!check ?
8669				value && result !== false :
8670				type === "!=" ?
8671				value !== check :
8672				type === "^=" ?
8673				value.indexOf(check) === 0 :
8674				type === "$=" ?
8675				value.substr(value.length - check.length) === check :
8676				type === "|=" ?
8677				value === check || value.substr(0, check.length + 1) === check + "-" :
8678				false;
8679		},
8680
8681		POS: function( elem, match, i, array ) {
8682			var name = match[2],
8683				filter = Expr.setFilters[ name ];
8684
8685			if ( filter ) {
8686				return filter( elem, i, match, array );
8687			}
8688		}
8689	}
8690};
8691
8692var origPOS = Expr.match.POS,
8693	fescape = function(all, num){
8694		return "\\" + (num - 0 + 1);
8695	};
8696
8697for ( var type in Expr.match ) {
8698	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8699	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8700}
8701// Expose origPOS
8702// "global" as in regardless of relation to brackets/parens
8703Expr.match.globalPOS = origPOS;
8704
8705var makeArray = function( array, results ) {
8706	array = Array.prototype.slice.call( array, 0 );
8707
8708	if ( results ) {
8709		results.push.apply( results, array );
8710		return results;
8711	}
8712
8713	return array;
8714};
8715
8716// Perform a simple check to determine if the browser is capable of
8717// converting a NodeList to an array using builtin methods.
8718// Also verifies that the returned array holds DOM nodes
8719// (which is not the case in the Blackberry browser)
8720try {
8721	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8722
8723// Provide a fallback method if it does not work
8724} catch( e ) {
8725	makeArray = function( array, results ) {
8726		var i = 0,
8727			ret = results || [];
8728
8729		if ( toString.call(array) === "[object Array]" ) {
8730			Array.prototype.push.apply( ret, array );
8731
8732		} else {
8733			if ( typeof array.length === "number" ) {
8734				for ( var l = array.length; i < l; i++ ) {
8735					ret.push( array[i] );
8736				}
8737
8738			} else {
8739				for ( ; array[i]; i++ ) {
8740					ret.push( array[i] );
8741				}
8742			}
8743		}
8744
8745		return ret;
8746	};
8747}
8748
8749var sortOrder, siblingCheck;
8750
8751if ( document.documentElement.compareDocumentPosition ) {
8752	sortOrder = function( a, b ) {
8753		if ( a === b ) {
8754			hasDuplicate = true;
8755			return 0;
8756		}
8757
8758		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8759			return a.compareDocumentPosition ? -1 : 1;
8760		}
8761
8762		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8763	};
8764
8765} else {
8766	sortOrder = function( a, b ) {
8767		// The nodes are identical, we can exit early
8768		if ( a === b ) {
8769			hasDuplicate = true;
8770			return 0;
8771
8772		// Fallback to using sourceIndex (in IE) if it's available on both nodes
8773		} else if ( a.sourceIndex && b.sourceIndex ) {
8774			return a.sourceIndex - b.sourceIndex;
8775		}
8776
8777		var al, bl,
8778			ap = [],
8779			bp = [],
8780			aup = a.parentNode,
8781			bup = b.parentNode,
8782			cur = aup;
8783
8784		// If the nodes are siblings (or identical) we can do a quick check
8785		if ( aup === bup ) {
8786			return siblingCheck( a, b );
8787
8788		// If no parents were found then the nodes are disconnected
8789		} else if ( !aup ) {
8790			return -1;
8791
8792		} else if ( !bup ) {
8793			return 1;
8794		}
8795
8796		// Otherwise they're somewhere else in the tree so we need
8797		// to build up a full list of the parentNodes for comparison
8798		while ( cur ) {
8799			ap.unshift( cur );
8800			cur = cur.parentNode;
8801		}
8802
8803		cur = bup;
8804
8805		while ( cur ) {
8806			bp.unshift( cur );
8807			cur = cur.parentNode;
8808		}
8809
8810		al = ap.length;
8811		bl = bp.length;
8812
8813		// Start walking down the tree looking for a discrepancy
8814		for ( var i = 0; i < al && i < bl; i++ ) {
8815			if ( ap[i] !== bp[i] ) {
8816				return siblingCheck( ap[i], bp[i] );
8817			}
8818		}
8819
8820		// We ended someplace up the tree so do a sibling check
8821		return i === al ?
8822			siblingCheck( a, bp[i], -1 ) :
8823			siblingCheck( ap[i], b, 1 );
8824	};
8825
8826	siblingCheck = function( a, b, ret ) {
8827		if ( a === b ) {
8828			return ret;
8829		}
8830
8831		var cur = a.nextSibling;
8832
8833		while ( cur ) {
8834			if ( cur === b ) {
8835				return -1;
8836			}
8837
8838			cur = cur.nextSibling;
8839		}
8840
8841		return 1;
8842	};
8843}
8844
8845// Check to see if the browser returns elements by name when
8846// querying by getElementById (and provide a workaround)
8847(function(){
8848	// We're going to inject a fake input element with a specified name
8849	var form = document.createElement("div"),
8850		id = "script" + (new Date()).getTime(),
8851		root = document.documentElement;
8852
8853	form.innerHTML = "<a name='" + id + "'/>";
8854
8855	// Inject it into the root element, check its status, and remove it quickly
8856	root.insertBefore( form, root.firstChild );
8857
8858	// The workaround has to do additional checks after a getElementById
8859	// Which slows things down for other browsers (hence the branching)
8860	if ( document.getElementById( id ) ) {
8861		Expr.find.ID = function( match, context, isXML ) {
8862			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8863				var m = context.getElementById(match[1]);
8864
8865				return m ?
8866					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8867						[m] :
8868						undefined :
8869					[];
8870			}
8871		};
8872
8873		Expr.filter.ID = function( elem, match ) {
8874			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8875
8876			return elem.nodeType === 1 && node && node.nodeValue === match;
8877		};
8878	}
8879
8880	root.removeChild( form );
8881
8882	// release memory in IE
8883	root = form = null;
8884})();
8885
8886(function(){
8887	// Check to see if the browser returns only elements
8888	// when doing getElementsByTagName("*")
8889
8890	// Create a fake element
8891	var div = document.createElement("div");
8892	div.appendChild( document.createComment("") );
8893
8894	// Make sure no comments are found
8895	if ( div.getElementsByTagName("*").length > 0 ) {
8896		Expr.find.TAG = function( match, context ) {
8897			var results = context.getElementsByTagName( match[1] );
8898
8899			// Filter out possible comments
8900			if ( match[1] === "*" ) {
8901				var tmp = [];
8902
8903				for ( var i = 0; results[i]; i++ ) {
8904					if ( results[i].nodeType === 1 ) {
8905						tmp.push( results[i] );
8906					}
8907				}
8908
8909				results = tmp;
8910			}
8911
8912			return results;
8913		};
8914	}
8915
8916	// Check to see if an attribute returns normalized href attributes
8917	div.innerHTML = "<a href='#'></a>";
8918
8919	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8920			div.firstChild.getAttribute("href") !== "#" ) {
8921
8922		Expr.attrHandle.href = function( elem ) {
8923			return elem.getAttribute( "href", 2 );
8924		};
8925	}
8926
8927	// release memory in IE
8928	div = null;
8929})();
8930
8931if ( document.querySelectorAll ) {
8932	(function(){
8933		var oldSizzle = Sizzle,
8934			div = document.createElement("div"),
8935			id = "__sizzle__";
8936
8937		div.innerHTML = "<p class='TEST'></p>";
8938
8939		// Safari can't handle uppercase or unicode characters when
8940		// in quirks mode.
8941		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8942			return;
8943		}
8944
8945		Sizzle = function( query, context, extra, seed ) {
8946			context = context || document;
8947
8948			// Only use querySelectorAll on non-XML documents
8949			// (ID selectors don't work in non-HTML documents)
8950			if ( !seed && !Sizzle.isXML(context) ) {
8951				// See if we find a selector to speed up
8952				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8953
8954				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8955					// Speed-up: Sizzle("TAG")
8956					if ( match[1] ) {
8957						return makeArray( context.getElementsByTagName( query ), extra );
8958
8959					// Speed-up: Sizzle(".CLASS")
8960					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8961						return makeArray( context.getElementsByClassName( match[2] ), extra );
8962					}
8963				}
8964
8965				if ( context.nodeType === 9 ) {
8966					// Speed-up: Sizzle("body")
8967					// The body element only exists once, optimize finding it
8968					if ( query === "body" && context.body ) {
8969						return makeArray( [ context.body ], extra );
8970
8971					// Speed-up: Sizzle("#ID")
8972					} else if ( match && match[3] ) {
8973						var elem = context.getElementById( match[3] );
8974
8975						// Check parentNode to catch when Blackberry 4.6 returns
8976						// nodes that are no longer in the document #6963
8977						if ( elem && elem.parentNode ) {
8978							// Handle the case where IE and Opera return items
8979							// by name instead of ID
8980							if ( elem.id === match[3] ) {
8981								return makeArray( [ elem ], extra );
8982							}
8983
8984						} else {
8985							return makeArray( [], extra );
8986						}
8987					}
8988
8989					try {
8990						return makeArray( context.querySelectorAll(query), extra );
8991					} catch(qsaError) {}
8992
8993				// qSA works strangely on Element-rooted queries
8994				// We can work around this by specifying an extra ID on the root
8995				// and working up from there (Thanks to Andrew Dupont for the technique)
8996				// IE 8 doesn't work on object elements
8997				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8998					var oldContext = context,
8999						old = context.getAttribute( "id" ),
9000						nid = old || id,
9001						hasParent = context.parentNode,
9002						relativeHierarchySelector = /^\s*[+~]/.test( query );
9003
9004					if ( !old ) {
9005						context.setAttribute( "id", nid );
9006					} else {
9007						nid = nid.replace( /'/g, "\\$&" );
9008					}
9009					if ( relativeHierarchySelector && hasParent ) {
9010						context = context.parentNode;
9011					}
9012
9013					try {
9014						if ( !relativeHierarchySelector || hasParent ) {
9015							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
9016						}
9017
9018					} catch(pseudoError) {
9019					} finally {
9020						if ( !old ) {
9021							oldContext.removeAttribute( "id" );
9022						}
9023					}
9024				}
9025			}
9026
9027			return oldSizzle(query, context, extra, seed);
9028		};
9029
9030		for ( var prop in oldSizzle ) {
9031			Sizzle[ prop ] = oldSizzle[ prop ];
9032		}
9033
9034		// release memory in IE
9035		div = null;
9036	})();
9037}
9038
9039(function(){
9040	var html = document.documentElement,
9041		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
9042
9043	if ( matches ) {
9044		// Check to see if it's possible to do matchesSelector
9045		// on a disconnected node (IE 9 fails this)
9046		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
9047			pseudoWorks = false;
9048
9049		try {
9050			// This should fail with an exception
9051			// Gecko does not error, returns false instead
9052			matches.call( document.documentElement, "[test!='']:sizzle" );
9053
9054		} catch( pseudoError ) {
9055			pseudoWorks = true;
9056		}
9057
9058		Sizzle.matchesSelector = function( node, expr ) {
9059			// Make sure that attribute selectors are quoted
9060			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
9061
9062			if ( !Sizzle.isXML( node ) ) {
9063				try {
9064					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
9065						var ret = matches.call( node, expr );
9066
9067						// IE 9's matchesSelector returns false on disconnected nodes
9068						if ( ret || !disconnectedMatch ||
9069								// As well, disconnected nodes are said to be in a document
9070								// fragment in IE 9, so check for that
9071								node.document && node.document.nodeType !== 11 ) {
9072							return ret;
9073						}
9074					}
9075				} catch(e) {}
9076			}
9077
9078			return Sizzle(expr, null, null, [node]).length > 0;
9079		};
9080	}
9081})();
9082
9083(function(){
9084	var div = document.createElement("div");
9085
9086	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
9087
9088	// Opera can't find a second classname (in 9.6)
9089	// Also, make sure that getElementsByClassName actually exists
9090	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
9091		return;
9092	}
9093
9094	// Safari caches class attributes, doesn't catch changes (in 3.2)
9095	div.lastChild.className = "e";
9096
9097	if ( div.getElementsByClassName("e").length === 1 ) {
9098		return;
9099	}
9100
9101	Expr.order.splice(1, 0, "CLASS");
9102	Expr.find.CLASS = function( match, context, isXML ) {
9103		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
9104			return context.getElementsByClassName(match[1]);
9105		}
9106	};
9107
9108	// release memory in IE
9109	div = null;
9110})();
9111
9112function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
9113	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
9114		var elem = checkSet[i];
9115
9116		if ( elem ) {
9117			var match = false;
9118
9119			elem = elem[dir];
9120
9121			while ( elem ) {
9122				if ( elem[ expando ] === doneName ) {
9123					match = checkSet[elem.sizset];
9124					break;
9125				}
9126
9127				if ( elem.nodeType === 1 && !isXML ){
9128					elem[ expando ] = doneName;
9129					elem.sizset = i;
9130				}
9131
9132				if ( elem.nodeName.toLowerCase() === cur ) {
9133					match = elem;
9134					break;
9135				}
9136
9137				elem = elem[dir];
9138			}
9139
9140			checkSet[i] = match;
9141		}
9142	}
9143}
9144
9145function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
9146	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
9147		var elem = checkSet[i];
9148
9149		if ( elem ) {
9150			var match = false;
9151
9152			elem = elem[dir];
9153
9154			while ( elem ) {
9155				if ( elem[ expando ] === doneName ) {
9156					match = checkSet[elem.sizset];
9157					break;
9158				}
9159
9160				if ( elem.nodeType === 1 ) {
9161					if ( !isXML ) {
9162						elem[ expando ] = doneName;
9163						elem.sizset = i;
9164					}
9165
9166					if ( typeof cur !== "string" ) {
9167						if ( elem === cur ) {
9168							match = true;
9169							break;
9170						}
9171
9172					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
9173						match = elem;
9174						break;
9175					}
9176				}
9177
9178				elem = elem[dir];
9179			}
9180
9181			checkSet[i] = match;
9182		}
9183	}
9184}
9185
9186if ( document.documentElement.contains ) {
9187	Sizzle.contains = function( a, b ) {
9188		return a !== b && (a.contains ? a.contains(b) : true);
9189	};
9190
9191} else if ( document.documentElement.compareDocumentPosition ) {
9192	Sizzle.contains = function( a, b ) {
9193		return !!(a.compareDocumentPosition(b) & 16);
9194	};
9195
9196} else {
9197	Sizzle.contains = function() {
9198		return false;
9199	};
9200}
9201
9202Sizzle.isXML = function( elem ) {
9203	// documentElement is verified for cases where it doesn't yet exist
9204	// (such as loading iframes in IE - #4833)
9205	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
9206
9207	return documentElement ? documentElement.nodeName !== "HTML" : false;
9208};
9209
9210var posProcess = function( selector, context, seed ) {
9211	var match,
9212		tmpSet = [],
9213		later = "",
9214		root = context.nodeType ? [context] : context;
9215
9216	// Position selectors must be done after the filter
9217	// And so must :not(positional) so we move all PSEUDOs to the end
9218	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
9219		later += match[0];
9220		selector = selector.replace( Expr.match.PSEUDO, "" );
9221	}
9222
9223	selector = Expr.relative[selector] ? selector + "*" : selector;
9224
9225	for ( var i = 0, l = root.length; i < l; i++ ) {
9226		Sizzle( selector, root[i], tmpSet, seed );
9227	}
9228
9229	return Sizzle.filter( later, tmpSet );
9230};
9231
9232// EXPOSE
9233
9234window.tinymce.dom.Sizzle = Sizzle;
9235
9236})();
9237
9238(function(tinymce) {
9239	tinymce.dom.Element = function(id, settings) {
9240		var t = this, dom, el;
9241
9242		t.settings = settings = settings || {};
9243		t.id = id;
9244		t.dom = dom = settings.dom || tinymce.DOM;
9245
9246		// Only IE leaks DOM references, this is a lot faster
9247		if (!tinymce.isIE)
9248			el = dom.get(t.id);
9249
9250		tinymce.each(
9251				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
9252				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
9253				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
9254				'isHidden,setHTML,get').split(/,/), function(k) {
9255					t[k] = function() {
9256						var a = [id], i;
9257
9258						for (i = 0; i < arguments.length; i++)
9259							a.push(arguments[i]);
9260
9261						a = dom[k].apply(dom, a);
9262						t.update(k);
9263
9264						return a;
9265					};
9266			}
9267		);
9268
9269		tinymce.extend(t, {
9270			on : function(n, f, s) {
9271				return tinymce.dom.Event.add(t.id, n, f, s);
9272			},
9273
9274			getXY : function() {
9275				return {
9276					x : parseInt(t.getStyle('left')),
9277					y : parseInt(t.getStyle('top'))
9278				};
9279			},
9280
9281			getSize : function() {
9282				var n = dom.get(t.id);
9283
9284				return {
9285					w : parseInt(t.getStyle('width') || n.clientWidth),
9286					h : parseInt(t.getStyle('height') || n.clientHeight)
9287				};
9288			},
9289
9290			moveTo : function(x, y) {
9291				t.setStyles({left : x, top : y});
9292			},
9293
9294			moveBy : function(x, y) {
9295				var p = t.getXY();
9296
9297				t.moveTo(p.x + x, p.y + y);
9298			},
9299
9300			resizeTo : function(w, h) {
9301				t.setStyles({width : w, height : h});
9302			},
9303
9304			resizeBy : function(w, h) {
9305				var s = t.getSize();
9306
9307				t.resizeTo(s.w + w, s.h + h);
9308			},
9309
9310			update : function(k) {
9311				var b;
9312
9313				if (tinymce.isIE6 && settings.blocker) {
9314					k = k || '';
9315
9316					// Ignore getters
9317					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9318						return;
9319
9320					// Remove blocker on remove
9321					if (k == 'remove') {
9322						dom.remove(t.blocker);
9323						return;
9324					}
9325
9326					if (!t.blocker) {
9327						t.blocker = dom.uniqueId();
9328						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9329						dom.setStyle(b, 'opacity', 0);
9330					} else
9331						b = dom.get(t.blocker);
9332
9333					dom.setStyles(b, {
9334						left : t.getStyle('left', 1),
9335						top : t.getStyle('top', 1),
9336						width : t.getStyle('width', 1),
9337						height : t.getStyle('height', 1),
9338						display : t.getStyle('display', 1),
9339						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9340					});
9341				}
9342			}
9343		});
9344	};
9345})(tinymce);
9346(function(tinymce) {
9347	function trimNl(s) {
9348		return s.replace(/[\n\r]+/g, '');
9349	};
9350
9351	// Shorten names
9352	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9353
9354	tinymce.create('tinymce.dom.Selection', {
9355		Selection : function(dom, win, serializer, editor) {
9356			var t = this;
9357
9358			t.dom = dom;
9359			t.win = win;
9360			t.serializer = serializer;
9361			t.editor = editor;
9362
9363			// Add events
9364			each([
9365				'onBeforeSetContent',
9366
9367				'onBeforeGetContent',
9368
9369				'onSetContent',
9370
9371				'onGetContent'
9372			], function(e) {
9373				t[e] = new tinymce.util.Dispatcher(t);
9374			});
9375
9376			// No W3C Range support
9377			if (!t.win.getSelection)
9378				t.tridentSel = new tinymce.dom.TridentSelection(t);
9379
9380			if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel)
9381				this._fixIESelection();
9382
9383			// Prevent leaks
9384			tinymce.addUnload(t.destroy, t);
9385		},
9386
9387		setCursorLocation: function(node, offset) {
9388			var t = this; var r = t.dom.createRng();
9389			r.setStart(node, offset);
9390			r.setEnd(node, offset);
9391			t.setRng(r);
9392			t.collapse(false);
9393		},
9394		getContent : function(s) {
9395			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9396
9397			s = s || {};
9398			wb = wa = '';
9399			s.get = true;
9400			s.format = s.format || 'html';
9401			s.forced_root_block = '';
9402			t.onBeforeGetContent.dispatch(t, s);
9403
9404			if (s.format == 'text')
9405				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9406
9407			if (r.cloneContents) {
9408				n = r.cloneContents();
9409
9410				if (n)
9411					e.appendChild(n);
9412			} else if (is(r.item) || is(r.htmlText)) {
9413				// IE will produce invalid markup if elements are present that
9414				// it doesn't understand like custom elements or HTML5 elements.
9415				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
9416				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9417				e.removeChild(e.firstChild);
9418			} else
9419				e.innerHTML = r.toString();
9420
9421			// Keep whitespace before and after
9422			if (/^\s/.test(e.innerHTML))
9423				wb = ' ';
9424
9425			if (/\s+$/.test(e.innerHTML))
9426				wa = ' ';
9427
9428			s.getInner = true;
9429
9430			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9431			t.onGetContent.dispatch(t, s);
9432
9433			return s.content;
9434		},
9435
9436		setContent : function(content, args) {
9437			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9438
9439			args = args || {format : 'html'};
9440			args.set = true;
9441			content = args.content = content;
9442
9443			// Dispatch before set content event
9444			if (!args.no_events)
9445				self.onBeforeSetContent.dispatch(self, args);
9446
9447			content = args.content;
9448
9449			if (rng.insertNode) {
9450				// Make caret marker since insertNode places the caret in the beginning of text after insert
9451				content += '<span id="__caret">_</span>';
9452
9453				// Delete and insert new node
9454				if (rng.startContainer == doc && rng.endContainer == doc) {
9455					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9456					doc.body.innerHTML = content;
9457				} else {
9458					rng.deleteContents();
9459
9460					if (doc.body.childNodes.length === 0) {
9461						doc.body.innerHTML = content;
9462					} else {
9463						// createContextualFragment doesn't exists in IE 9 DOMRanges
9464						if (rng.createContextualFragment) {
9465							rng.insertNode(rng.createContextualFragment(content));
9466						} else {
9467							// Fake createContextualFragment call in IE 9
9468							frag = doc.createDocumentFragment();
9469							temp = doc.createElement('div');
9470
9471							frag.appendChild(temp);
9472							temp.outerHTML = content;
9473
9474							rng.insertNode(frag);
9475						}
9476					}
9477				}
9478
9479				// Move to caret marker
9480				caretNode = self.dom.get('__caret');
9481
9482				// Make sure we wrap it compleatly, Opera fails with a simple select call
9483				rng = doc.createRange();
9484				rng.setStartBefore(caretNode);
9485				rng.setEndBefore(caretNode);
9486				self.setRng(rng);
9487
9488				// Remove the caret position
9489				self.dom.remove('__caret');
9490
9491				try {
9492					self.setRng(rng);
9493				} catch (ex) {
9494					// Might fail on Opera for some odd reason
9495				}
9496			} else {
9497				if (rng.item) {
9498					// Delete content and get caret text selection
9499					doc.execCommand('Delete', false, null);
9500					rng = self.getRng();
9501				}
9502
9503				// Explorer removes spaces from the beginning of pasted contents
9504				if (/^\s+/.test(content)) {
9505					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9506					self.dom.remove('__mce_tmp');
9507				} else
9508					rng.pasteHTML(content);
9509			}
9510
9511			// Dispatch set content event
9512			if (!args.no_events)
9513				self.onSetContent.dispatch(self, args);
9514		},
9515
9516		getStart : function() {
9517			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9518
9519			if (rng.duplicate || rng.item) {
9520				// Control selection, return first item
9521				if (rng.item)
9522					return rng.item(0);
9523
9524				// Get start element
9525				checkRng = rng.duplicate();
9526				checkRng.collapse(1);
9527				startElement = checkRng.parentElement();
9528				if (startElement.ownerDocument !== self.dom.doc) {
9529					startElement = self.dom.getRoot();
9530				}
9531
9532				// Check if range parent is inside the start element, then return the inner parent element
9533				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9534				parentElement = node = rng.parentElement();
9535				while (node = node.parentNode) {
9536					if (node == startElement) {
9537						startElement = parentElement;
9538						break;
9539					}
9540				}
9541
9542				return startElement;
9543			} else {
9544				startElement = rng.startContainer;
9545
9546				if (startElement.nodeType == 1 && startElement.hasChildNodes())
9547					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9548
9549				if (startElement && startElement.nodeType == 3)
9550					return startElement.parentNode;
9551
9552				return startElement;
9553			}
9554		},
9555
9556		getEnd : function() {
9557			var self = this, rng = self.getRng(), endElement, endOffset;
9558
9559			if (rng.duplicate || rng.item) {
9560				if (rng.item)
9561					return rng.item(0);
9562
9563				rng = rng.duplicate();
9564				rng.collapse(0);
9565				endElement = rng.parentElement();
9566				if (endElement.ownerDocument !== self.dom.doc) {
9567					endElement = self.dom.getRoot();
9568				}
9569
9570				if (endElement && endElement.nodeName == 'BODY')
9571					return endElement.lastChild || endElement;
9572
9573				return endElement;
9574			} else {
9575				endElement = rng.endContainer;
9576				endOffset = rng.endOffset;
9577
9578				if (endElement.nodeType == 1 && endElement.hasChildNodes())
9579					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9580
9581				if (endElement && endElement.nodeType == 3)
9582					return endElement.parentNode;
9583
9584				return endElement;
9585			}
9586		},
9587
9588		getBookmark : function(type, normalized) {
9589			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9590
9591			function findIndex(name, element) {
9592				var index = 0;
9593
9594				each(dom.select(name), function(node, i) {
9595					if (node == element)
9596						index = i;
9597				});
9598
9599				return index;
9600			};
9601
9602			function normalizeTableCellSelection(rng) {
9603				function moveEndPoint(start) {
9604					var container, offset, childNodes, prefix = start ? 'start' : 'end';
9605
9606					container = rng[prefix + 'Container'];
9607					offset = rng[prefix + 'Offset'];
9608
9609					if (container.nodeType == 1 && container.nodeName == "TR") {
9610						childNodes = container.childNodes;
9611						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9612						if (container) {
9613							offset = start ? 0 : container.childNodes.length;
9614							rng['set' + (start ? 'Start' : 'End')](container, offset);
9615						}
9616					}
9617				};
9618
9619				moveEndPoint(true);
9620				moveEndPoint();
9621
9622				return rng;
9623			};
9624
9625			function getLocation() {
9626				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9627
9628				function getPoint(rng, start) {
9629					var container = rng[start ? 'startContainer' : 'endContainer'],
9630						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9631
9632					if (container.nodeType == 3) {
9633						if (normalized) {
9634							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9635								offset += node.nodeValue.length;
9636						}
9637
9638						point.push(offset);
9639					} else {
9640						childNodes = container.childNodes;
9641
9642						if (offset >= childNodes.length && childNodes.length) {
9643							after = 1;
9644							offset = Math.max(0, childNodes.length - 1);
9645						}
9646
9647						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9648					}
9649
9650					for (; container && container != root; container = container.parentNode)
9651						point.push(t.dom.nodeIndex(container, normalized));
9652
9653					return point;
9654				};
9655
9656				bookmark.start = getPoint(rng, true);
9657
9658				if (!t.isCollapsed())
9659					bookmark.end = getPoint(rng);
9660
9661				return bookmark;
9662			};
9663
9664			if (type == 2) {
9665				if (t.tridentSel)
9666					return t.tridentSel.getBookmark(type);
9667
9668				return getLocation();
9669			}
9670
9671			// Handle simple range
9672			if (type) {
9673				rng = t.getRng();
9674
9675				if (rng.setStart) {
9676					rng = {
9677						startContainer: rng.startContainer,
9678						startOffset: rng.startOffset,
9679						endContainer: rng.endContainer,
9680						endOffset: rng.endOffset
9681					};
9682				}
9683
9684				return {rng : rng};
9685			}
9686
9687			rng = t.getRng();
9688			id = dom.uniqueId();
9689			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9690			styles = 'overflow:hidden;line-height:0px';
9691
9692			// Explorer method
9693			if (rng.duplicate || rng.item) {
9694				// Text selection
9695				if (!rng.item) {
9696					rng2 = rng.duplicate();
9697
9698					try {
9699						// Insert start marker
9700						rng.collapse();
9701						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9702
9703						// Insert end marker
9704						if (!collapsed) {
9705							rng2.collapse(false);
9706
9707							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9708							rng.moveToElementText(rng2.parentElement());
9709							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9710								rng2.move('character', -1);
9711
9712							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9713						}
9714					} catch (ex) {
9715						// IE might throw unspecified error so lets ignore it
9716						return null;
9717					}
9718				} else {
9719					// Control selection
9720					element = rng.item(0);
9721					name = element.nodeName;
9722
9723					return {name : name, index : findIndex(name, element)};
9724				}
9725			} else {
9726				element = t.getNode();
9727				name = element.nodeName;
9728				if (name == 'IMG')
9729					return {name : name, index : findIndex(name, element)};
9730
9731				// W3C method
9732				rng2 = normalizeTableCellSelection(rng.cloneRange());
9733
9734				// Insert end marker
9735				if (!collapsed) {
9736					rng2.collapse(false);
9737					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9738				}
9739
9740				rng = normalizeTableCellSelection(rng);
9741				rng.collapse(true);
9742				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9743			}
9744
9745			t.moveToBookmark({id : id, keep : 1});
9746
9747			return {id : id};
9748		},
9749
9750		moveToBookmark : function(bookmark) {
9751			var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset;
9752
9753			function setEndPoint(start) {
9754				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9755
9756				if (point) {
9757					offset = point[0];
9758
9759					// Find container node
9760					for (node = root, i = point.length - 1; i >= 1; i--) {
9761						children = node.childNodes;
9762
9763						if (point[i] > children.length - 1)
9764							return;
9765
9766						node = children[point[i]];
9767					}
9768
9769					// Move text offset to best suitable location
9770					if (node.nodeType === 3)
9771						offset = Math.min(point[0], node.nodeValue.length);
9772
9773					// Move element offset to best suitable location
9774					if (node.nodeType === 1)
9775						offset = Math.min(point[0], node.childNodes.length);
9776
9777					// Set offset within container node
9778					if (start)
9779						rng.setStart(node, offset);
9780					else
9781						rng.setEnd(node, offset);
9782				}
9783
9784				return true;
9785			};
9786
9787			function restoreEndPoint(suffix) {
9788				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9789
9790				if (marker) {
9791					node = marker.parentNode;
9792
9793					if (suffix == 'start') {
9794						if (!keep) {
9795							idx = dom.nodeIndex(marker);
9796						} else {
9797							node = marker.firstChild;
9798							idx = 1;
9799						}
9800
9801						startContainer = endContainer = node;
9802						startOffset = endOffset = idx;
9803					} else {
9804						if (!keep) {
9805							idx = dom.nodeIndex(marker);
9806						} else {
9807							node = marker.firstChild;
9808							idx = 1;
9809						}
9810
9811						endContainer = node;
9812						endOffset = idx;
9813					}
9814
9815					if (!keep) {
9816						prev = marker.previousSibling;
9817						next = marker.nextSibling;
9818
9819						// Remove all marker text nodes
9820						each(tinymce.grep(marker.childNodes), function(node) {
9821							if (node.nodeType == 3)
9822								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9823						});
9824
9825						// Remove marker but keep children if for example contents where inserted into the marker
9826						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9827						while (marker = dom.get(bookmark.id + '_' + suffix))
9828							dom.remove(marker, 1);
9829
9830						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9831						// 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
9832						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9833							idx = prev.nodeValue.length;
9834							prev.appendData(next.nodeValue);
9835							dom.remove(next);
9836
9837							if (suffix == 'start') {
9838								startContainer = endContainer = prev;
9839								startOffset = endOffset = idx;
9840							} else {
9841								endContainer = prev;
9842								endOffset = idx;
9843							}
9844						}
9845					}
9846				}
9847			};
9848
9849			function addBogus(node) {
9850				// Adds a bogus BR element for empty block elements
9851				if (dom.isBlock(node) && !node.innerHTML && !isIE)
9852					node.innerHTML = '<br data-mce-bogus="1" />';
9853
9854				return node;
9855			};
9856
9857			if (bookmark) {
9858				if (bookmark.start) {
9859					rng = dom.createRng();
9860					root = dom.getRoot();
9861
9862					if (t.tridentSel)
9863						return t.tridentSel.moveToBookmark(bookmark);
9864
9865					if (setEndPoint(true) && setEndPoint()) {
9866						t.setRng(rng);
9867					}
9868				} else if (bookmark.id) {
9869					// Restore start/end points
9870					restoreEndPoint('start');
9871					restoreEndPoint('end');
9872
9873					if (startContainer) {
9874						rng = dom.createRng();
9875						rng.setStart(addBogus(startContainer), startOffset);
9876						rng.setEnd(addBogus(endContainer), endOffset);
9877						t.setRng(rng);
9878					}
9879				} else if (bookmark.name) {
9880					t.select(dom.select(bookmark.name)[bookmark.index]);
9881				} else if (bookmark.rng) {
9882					rng = bookmark.rng;
9883
9884					if (rng.startContainer) {
9885						rng2 = t.dom.createRng();
9886
9887						try {
9888							rng2.setStart(rng.startContainer, rng.startOffset);
9889							rng2.setEnd(rng.endContainer, rng.endOffset);
9890						} catch (e) {
9891							// Might fail with index error
9892						}
9893
9894						rng = rng2;
9895					}
9896
9897					t.setRng(rng);
9898				}
9899			}
9900		},
9901
9902		select : function(node, content) {
9903			var t = this, dom = t.dom, rng = dom.createRng(), idx;
9904
9905			function setPoint(node, start) {
9906				var walker = new TreeWalker(node, node);
9907
9908				do {
9909					// Text node
9910					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9911						if (start)
9912							rng.setStart(node, 0);
9913						else
9914							rng.setEnd(node, node.nodeValue.length);
9915
9916						return;
9917					}
9918
9919					// BR element
9920					if (node.nodeName == 'BR') {
9921						if (start)
9922							rng.setStartBefore(node);
9923						else
9924							rng.setEndBefore(node);
9925
9926						return;
9927					}
9928				} while (node = (start ? walker.next() : walker.prev()));
9929			};
9930
9931			if (node) {
9932				idx = dom.nodeIndex(node);
9933				rng.setStart(node.parentNode, idx);
9934				rng.setEnd(node.parentNode, idx + 1);
9935
9936				// Find first/last text node or BR element
9937				if (content) {
9938					setPoint(node, 1);
9939					setPoint(node);
9940				}
9941
9942				t.setRng(rng);
9943			}
9944
9945			return node;
9946		},
9947
9948		isCollapsed : function() {
9949			var t = this, r = t.getRng(), s = t.getSel();
9950
9951			if (!r || r.item)
9952				return false;
9953
9954			if (r.compareEndPoints)
9955				return r.compareEndPoints('StartToEnd', r) === 0;
9956
9957			return !s || r.collapsed;
9958		},
9959
9960		collapse : function(to_start) {
9961			var self = this, rng = self.getRng(), node;
9962
9963			// Control range on IE
9964			if (rng.item) {
9965				node = rng.item(0);
9966				rng = self.win.document.body.createTextRange();
9967				rng.moveToElementText(node);
9968			}
9969
9970			rng.collapse(!!to_start);
9971			self.setRng(rng);
9972		},
9973
9974		getSel : function() {
9975			var t = this, w = this.win;
9976
9977			return w.getSelection ? w.getSelection() : w.document.selection;
9978		},
9979
9980		getRng : function(w3c) {
9981			var self = this, selection, rng, elm, doc = self.win.document;
9982
9983			// Workaround for IE 11 not being able to select images properly see #6613 see quirk fix
9984			if (self.fakeRng) {
9985				return self.fakeRng;
9986			}
9987
9988			// Found tridentSel object then we need to use that one
9989			if (w3c && self.tridentSel) {
9990				return self.tridentSel.getRangeAt(0);
9991			}
9992
9993			try {
9994				if (selection = self.getSel()) {
9995					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9996				}
9997			} catch (ex) {
9998				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9999			}
10000
10001			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
10002			if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) {
10003				elm = doc.selection.createRange().item(0);
10004				rng = doc.createRange();
10005				rng.setStartBefore(elm);
10006				rng.setEndAfter(elm);
10007			}
10008
10009			// No range found then create an empty one
10010			// This can occur when the editor is placed in a hidden container element on Gecko
10011			// Or on IE when there was an exception
10012			if (!rng) {
10013				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
10014			}
10015
10016			// If range is at start of document then move it to start of body
10017			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
10018				elm = self.dom.getRoot();
10019				rng.setStart(elm, 0);
10020				rng.setEnd(elm, 0);
10021			}
10022
10023			if (self.selectedRange && self.explicitRange) {
10024				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
10025					// Safari, Opera and Chrome only ever select text which causes the range to change.
10026					// This lets us use the originally set range if the selection hasn't been changed by the user.
10027					rng = self.explicitRange;
10028				} else {
10029					self.selectedRange = null;
10030					self.explicitRange = null;
10031				}
10032			}
10033
10034			return rng;
10035		},
10036
10037		setRng : function(r, forward) {
10038			var s, t = this;
10039
10040			if (!t.tridentSel) {
10041				s = t.getSel();
10042
10043				if (s) {
10044					t.explicitRange = r;
10045
10046					try {
10047						s.removeAllRanges();
10048					} catch (ex) {
10049						// IE9 might throw errors here don't know why
10050					}
10051
10052					s.addRange(r);
10053
10054					// Forward is set to false and we have an extend function
10055					if (forward === false && s.extend) {
10056						s.collapse(r.endContainer, r.endOffset);
10057						s.extend(r.startContainer, r.startOffset);
10058					}
10059
10060					// adding range isn't always successful so we need to check range count otherwise an exception can occur
10061					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
10062				}
10063			} else {
10064				// Is W3C Range
10065				if (r.cloneRange) {
10066					try {
10067						t.tridentSel.addRange(r);
10068						return;
10069					} catch (ex) {
10070						//IE9 throws an error here if called before selection is placed in the editor
10071					}
10072				}
10073
10074				// Is IE specific range
10075				try {
10076					r.select();
10077				} catch (ex) {
10078					// Needed for some odd IE bug #1843306
10079				}
10080			}
10081		},
10082
10083		setNode : function(n) {
10084			var t = this;
10085
10086			t.setContent(t.dom.getOuterHTML(n));
10087
10088			return n;
10089		},
10090
10091		getNode : function() {
10092			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
10093
10094			function skipEmptyTextNodes(n, forwards) {
10095				var orig = n;
10096				while (n && n.nodeType === 3 && n.length === 0) {
10097					n = forwards ? n.nextSibling : n.previousSibling;
10098				}
10099				return n || orig;
10100			};
10101
10102			// Range maybe lost after the editor is made visible again
10103			if (!rng)
10104				return t.dom.getRoot();
10105
10106			if (rng.setStart) {
10107				elm = rng.commonAncestorContainer;
10108
10109				// Handle selection a image or other control like element such as anchors
10110				if (!rng.collapsed) {
10111					if (rng.startContainer == rng.endContainer) {
10112						if (rng.endOffset - rng.startOffset < 2) {
10113							if (rng.startContainer.hasChildNodes())
10114								elm = rng.startContainer.childNodes[rng.startOffset];
10115						}
10116					}
10117
10118					// If the anchor node is a element instead of a text node then return this element
10119					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
10120					//	return sel.anchorNode.childNodes[sel.anchorOffset];
10121
10122					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
10123					// This happens when you double click an underlined word in FireFox.
10124					if (start.nodeType === 3 && end.nodeType === 3) {
10125						if (start.length === rng.startOffset) {
10126							start = skipEmptyTextNodes(start.nextSibling, true);
10127						} else {
10128							start = start.parentNode;
10129						}
10130						if (rng.endOffset === 0) {
10131							end = skipEmptyTextNodes(end.previousSibling, false);
10132						} else {
10133							end = end.parentNode;
10134						}
10135
10136						if (start && start === end)
10137							return start;
10138					}
10139				}
10140
10141				if (elm && elm.nodeType == 3)
10142					return elm.parentNode;
10143
10144				return elm;
10145			}
10146
10147			return rng.item ? rng.item(0) : rng.parentElement();
10148		},
10149
10150		getSelectedBlocks : function(st, en) {
10151			var t = this, dom = t.dom, sb, eb, n, bl = [];
10152
10153			sb = dom.getParent(st || t.getStart(), dom.isBlock);
10154			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
10155
10156			if (sb)
10157				bl.push(sb);
10158
10159			if (sb && eb && sb != eb) {
10160				n = sb;
10161
10162				var walker = new TreeWalker(sb, dom.getRoot());
10163				while ((n = walker.next()) && n != eb) {
10164					if (dom.isBlock(n))
10165						bl.push(n);
10166				}
10167			}
10168
10169			if (eb && sb != eb)
10170				bl.push(eb);
10171
10172			return bl;
10173		},
10174
10175		isForward: function(){
10176			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
10177
10178			// No support for selection direction then always return true
10179			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
10180				return true;
10181			}
10182
10183			anchorRange = dom.createRng();
10184			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
10185			anchorRange.collapse(true);
10186
10187			focusRange = dom.createRng();
10188			focusRange.setStart(sel.focusNode, sel.focusOffset);
10189			focusRange.collapse(true);
10190
10191			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
10192		},
10193
10194		normalize : function() {
10195			var self = this, rng, normalized, collapsed, node, sibling;
10196
10197			function normalizeEndPoint(start) {
10198				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
10199
10200				function hasBrBeforeAfter(node, left) {
10201					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
10202
10203					while (node = walker[left ? 'prev' : 'next']()) {
10204						if (node.nodeName === "BR") {
10205							return true;
10206						}
10207					}
10208				};
10209
10210				// Walks the dom left/right to find a suitable text node to move the endpoint into
10211				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
10212				function findTextNodeRelative(left, startNode) {
10213					var walker, lastInlineElement;
10214
10215					startNode = startNode || container;
10216					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
10217
10218					// Walk left until we hit a text node we can move to or a block/br/img
10219					while (node = walker[left ? 'prev' : 'next']()) {
10220						// Found text node that has a length
10221						if (node.nodeType === 3 && node.nodeValue.length > 0) {
10222							container = node;
10223							offset = left ? node.nodeValue.length : 0;
10224							normalized = true;
10225							return;
10226						}
10227
10228						// Break if we find a block or a BR/IMG/INPUT etc
10229						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10230							return;
10231						}
10232
10233						lastInlineElement = node;
10234					}
10235
10236					// Only fetch the last inline element when in caret mode for now
10237					if (collapsed && lastInlineElement) {
10238						container = lastInlineElement;
10239						normalized = true;
10240						offset = 0;
10241					}
10242				};
10243
10244				container = rng[(start ? 'start' : 'end') + 'Container'];
10245				offset = rng[(start ? 'start' : 'end') + 'Offset'];
10246				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
10247
10248				// If the container is a document move it to the body element
10249				if (container.nodeType === 9) {
10250					container = dom.getRoot();
10251					offset = 0;
10252				}
10253
10254				// If the container is body try move it into the closest text node or position
10255				if (container === body) {
10256					// If start is before/after a image, table etc
10257					if (start) {
10258						node = container.childNodes[offset > 0 ? offset - 1 : 0];
10259						if (node) {
10260							nodeName = node.nodeName.toLowerCase();
10261							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
10262								return;
10263							}
10264						}
10265					}
10266
10267					// Resolve the index
10268					if (container.hasChildNodes()) {
10269						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
10270						offset = 0;
10271
10272						// Don't walk into elements that doesn't have any child nodes like a IMG
10273						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
10274							// Walk the DOM to find a text node to place the caret at or a BR
10275							node = container;
10276							walker = new TreeWalker(container, body);
10277
10278							do {
10279								// Found a text node use that position
10280								if (node.nodeType === 3 && node.nodeValue.length > 0) {
10281									offset = start ? 0 : node.nodeValue.length;
10282									container = node;
10283									normalized = true;
10284									break;
10285								}
10286
10287								// Found a BR/IMG element that we can place the caret before
10288								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10289									offset = dom.nodeIndex(node);
10290									container = node.parentNode;
10291
10292									// Put caret after image when moving the end point
10293									if (node.nodeName ==  "IMG" && !start) {
10294										offset++;
10295									}
10296
10297									normalized = true;
10298									break;
10299								}
10300							} while (node = (start ? walker.next() : walker.prev()));
10301						}
10302					}
10303				}
10304
10305				// Lean the caret to the left if possible
10306				if (collapsed) {
10307					// So this: <b>x</b><i>|x</i>
10308					// Becomes: <b>x|</b><i>x</i>
10309					// Seems that only gecko has issues with this
10310					if (container.nodeType === 3 && offset === 0) {
10311						findTextNodeRelative(true);
10312					}
10313
10314					// Lean left into empty inline elements when the caret is before a BR
10315					// So this: <i><b></b><i>|<br></i>
10316					// Becomes: <i><b>|</b><i><br></i>
10317					// Seems that only gecko has issues with this
10318					if (container.nodeType === 1) {
10319						node = container.childNodes[offset];
10320						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
10321							findTextNodeRelative(true, container.childNodes[offset]);
10322						}
10323					}
10324				}
10325
10326				// Lean the start of the selection right if possible
10327				// So this: x[<b>x]</b>
10328				// Becomes: x<b>[x]</b>
10329				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
10330					findTextNodeRelative(false);
10331				}
10332
10333				// Set endpoint if it was normalized
10334				if (normalized)
10335					rng['set' + (start ? 'Start' : 'End')](container, offset);
10336			};
10337
10338			// Normalize only on non IE browsers for now
10339			if (tinymce.isIE)
10340				return;
10341			
10342			rng = self.getRng();
10343			collapsed = rng.collapsed;
10344
10345			// Normalize the end points
10346			normalizeEndPoint(true);
10347
10348			if (!collapsed)
10349				normalizeEndPoint();
10350
10351			// Set the selection if it was normalized
10352			if (normalized) {
10353				// If it was collapsed then make sure it still is
10354				if (collapsed) {
10355					rng.collapse(true);
10356				}
10357
10358				//console.log(self.dom.dumpRng(rng));
10359				self.setRng(rng, self.isForward());
10360			}
10361		},
10362
10363		selectorChanged: function(selector, callback) {
10364			var self = this, currentSelectors;
10365
10366			if (!self.selectorChangedData) {
10367				self.selectorChangedData = {};
10368				currentSelectors = {};
10369
10370				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10371					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10372
10373					// Check for new matching selectors
10374					each(self.selectorChangedData, function(callbacks, selector) {
10375						each(parents, function(node) {
10376							if (dom.is(node, selector)) {
10377								if (!currentSelectors[selector]) {
10378									// Execute callbacks
10379									each(callbacks, function(callback) {
10380										callback(true, {node: node, selector: selector, parents: parents});
10381									});
10382
10383									currentSelectors[selector] = callbacks;
10384								}
10385
10386								matchedSelectors[selector] = callbacks;
10387								return false;
10388							}
10389						});
10390					});
10391
10392					// Check if current selectors still match
10393					each(currentSelectors, function(callbacks, selector) {
10394						if (!matchedSelectors[selector]) {
10395							delete currentSelectors[selector];
10396
10397							each(callbacks, function(callback) {
10398								callback(false, {node: node, selector: selector, parents: parents});
10399							});
10400						}
10401					});
10402				});
10403			}
10404
10405			// Add selector listeners
10406			if (!self.selectorChangedData[selector]) {
10407				self.selectorChangedData[selector] = [];
10408			}
10409
10410			self.selectorChangedData[selector].push(callback);
10411
10412			return self;
10413		},
10414
10415		scrollIntoView: function(elm) {
10416			var y, viewPort, self = this, dom = self.dom;
10417
10418			viewPort = dom.getViewPort(self.editor.getWin());
10419			y = dom.getPos(elm).y;
10420			if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
10421				self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25);
10422			}
10423		},
10424
10425		destroy : function(manual) {
10426			var self = this;
10427
10428			self.win = null;
10429
10430			// Manual destroy then remove unload handler
10431			if (!manual)
10432				tinymce.removeUnload(self.destroy);
10433		},
10434
10435		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10436		_fixIESelection : function() {
10437			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10438
10439			// Return range from point or null if it failed
10440			function rngFromPoint(x, y) {
10441				var rng = body.createTextRange();
10442
10443				try {
10444					rng.moveToPoint(x, y);
10445				} catch (ex) {
10446					// IE sometimes throws and exception, so lets just ignore it
10447					rng = null;
10448				}
10449
10450				return rng;
10451			};
10452
10453			// Fires while the selection is changing
10454			function selectionChange(e) {
10455				var pointRng;
10456
10457				// Check if the button is down or not
10458				if (e.button) {
10459					// Create range from mouse position
10460					pointRng = rngFromPoint(e.x, e.y);
10461
10462					if (pointRng) {
10463						// Check if pointRange is before/after selection then change the endPoint
10464						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10465							pointRng.setEndPoint('StartToStart', startRng);
10466						else
10467							pointRng.setEndPoint('EndToEnd', startRng);
10468
10469						pointRng.select();
10470					}
10471				} else
10472					endSelection();
10473			}
10474
10475			// Removes listeners
10476			function endSelection() {
10477				var rng = doc.selection.createRange();
10478
10479				// If the range is collapsed then use the last start range
10480				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10481					startRng.select();
10482
10483				dom.unbind(doc, 'mouseup', endSelection);
10484				dom.unbind(doc, 'mousemove', selectionChange);
10485				startRng = started = 0;
10486			};
10487
10488			// Make HTML element unselectable since we are going to handle selection by hand
10489			doc.documentElement.unselectable = true;
10490			
10491			// Detect when user selects outside BODY
10492			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10493				if (e.target.nodeName === 'HTML') {
10494					if (started)
10495						endSelection();
10496
10497					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10498					htmlElm = doc.documentElement;
10499					if (htmlElm.scrollHeight > htmlElm.clientHeight)
10500						return;
10501
10502					started = 1;
10503					// Setup start position
10504					startRng = rngFromPoint(e.x, e.y);
10505					if (startRng) {
10506						// Listen for selection change events
10507						dom.bind(doc, 'mouseup', endSelection);
10508						dom.bind(doc, 'mousemove', selectionChange);
10509
10510						dom.win.focus();
10511						startRng.select();
10512					}
10513				}
10514			});
10515		}
10516	});
10517})(tinymce);
10518(function(tinymce) {
10519	tinymce.dom.Serializer = function(settings, dom, schema) {
10520		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10521
10522		// Support the old apply_source_formatting option
10523		if (!settings.apply_source_formatting)
10524			settings.indent = false;
10525
10526		// Default DOM and Schema if they are undefined
10527		dom = dom || tinymce.DOM;
10528		schema = schema || new tinymce.html.Schema(settings);
10529		settings.entity_encoding = settings.entity_encoding || 'named';
10530		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10531
10532		onPreProcess = new tinymce.util.Dispatcher(self);
10533
10534		onPostProcess = new tinymce.util.Dispatcher(self);
10535
10536		htmlParser = new tinymce.html.DomParser(settings, schema);
10537
10538		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10539		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10540			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10541
10542			while (i--) {
10543				node = nodes[i];
10544
10545				value = node.attributes.map[internalName];
10546				if (value !== undef) {
10547					// Set external name to internal value and remove internal
10548					node.attr(name, value.length > 0 ? value : null);
10549					node.attr(internalName, null);
10550				} else {
10551					// No internal attribute found then convert the value we have in the DOM
10552					value = node.attributes.map[name];
10553
10554					if (name === "style")
10555						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10556					else if (urlConverter)
10557						value = urlConverter.call(urlConverterScope, value, name, node.name);
10558
10559					node.attr(name, value.length > 0 ? value : null);
10560				}
10561			}
10562		});
10563
10564		// Remove internal classes mceItem<..> or mceSelected
10565		htmlParser.addAttributeFilter('class', function(nodes, name) {
10566			var i = nodes.length, node, value;
10567
10568			while (i--) {
10569				node = nodes[i];
10570				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10571				node.attr('class', value.length > 0 ? value : null);
10572			}
10573		});
10574
10575		// Remove bookmark elements
10576		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10577			var i = nodes.length, node;
10578
10579			while (i--) {
10580				node = nodes[i];
10581
10582				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10583					node.remove();
10584			}
10585		});
10586
10587		// Remove expando attributes
10588		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10589			var i = nodes.length;
10590
10591			while (i--) {
10592				nodes[i].attr(name, null);
10593			}
10594		});
10595
10596		htmlParser.addNodeFilter('noscript', function(nodes) {
10597			var i = nodes.length, node;
10598
10599			while (i--) {
10600				node = nodes[i].firstChild;
10601
10602				if (node) {
10603					node.value = tinymce.html.Entities.decode(node.value);
10604				}
10605			}
10606		});
10607
10608		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10609		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10610			var i = nodes.length, node, value;
10611
10612			function trim(value) {
10613				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10614						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10615						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10616						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10617			};
10618
10619			while (i--) {
10620				node = nodes[i];
10621				value = node.firstChild ? node.firstChild.value : '';
10622
10623				if (name === "script") {
10624					// Remove mce- prefix from script elements
10625					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10626
10627					if (value.length > 0)
10628						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10629				} else {
10630					if (value.length > 0)
10631						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10632				}
10633			}
10634		});
10635
10636		// Convert comments to cdata and handle protected comments
10637		htmlParser.addNodeFilter('#comment', function(nodes, name) {
10638			var i = nodes.length, node;
10639
10640			while (i--) {
10641				node = nodes[i];
10642
10643				if (node.value.indexOf('[CDATA[') === 0) {
10644					node.name = '#cdata';
10645					node.type = 4;
10646					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10647				} else if (node.value.indexOf('mce:protected ') === 0) {
10648					node.name = "#text";
10649					node.type = 3;
10650					node.raw = true;
10651					node.value = unescape(node.value).substr(14);
10652				}
10653			}
10654		});
10655
10656		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10657			var i = nodes.length, node;
10658
10659			while (i--) {
10660				node = nodes[i];
10661				if (node.type === 7)
10662					node.remove();
10663				else if (node.type === 1) {
10664					if (name === "input" && !("type" in node.attributes.map))
10665						node.attr('type', 'text');
10666				}
10667			}
10668		});
10669
10670		// Fix list elements, TODO: Replace this later
10671		if (settings.fix_list_elements) {
10672			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10673				var i = nodes.length, node, parentNode;
10674
10675				while (i--) {
10676					node = nodes[i];
10677					parentNode = node.parent;
10678
10679					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10680						if (node.prev && node.prev.name === 'li') {
10681							node.prev.append(node);
10682						}
10683					}
10684				}
10685			});
10686		}
10687
10688		// Remove internal data attributes
10689		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10690			var i = nodes.length;
10691
10692			while (i--) {
10693				nodes[i].attr(name, null);
10694			}
10695		});
10696
10697		// Return public methods
10698		return {
10699			schema : schema,
10700
10701			addNodeFilter : htmlParser.addNodeFilter,
10702
10703			addAttributeFilter : htmlParser.addAttributeFilter,
10704
10705			onPreProcess : onPreProcess,
10706
10707			onPostProcess : onPostProcess,
10708
10709			serialize : function(node, args) {
10710				var impl, doc, oldDoc, htmlSerializer, content;
10711
10712				// Explorer won't clone contents of script and style and the
10713				// selected index of select elements are cleared on a clone operation.
10714				if (isIE && dom.select('script,style,select,map').length > 0) {
10715					content = node.innerHTML;
10716					node = node.cloneNode(false);
10717					dom.setHTML(node, content);
10718				} else
10719					node = node.cloneNode(true);
10720
10721				// Nodes needs to be attached to something in WebKit/Opera
10722				// Older builds of Opera crashes if you attach the node to an document created dynamically
10723				// and since we can't feature detect a crash we need to sniff the acutal build number
10724				// This fix will make DOM ranges and make Sizzle happy!
10725				impl = node.ownerDocument.implementation;
10726				if (impl.createHTMLDocument) {
10727					// Create an empty HTML document
10728					doc = impl.createHTMLDocument("");
10729
10730					// Add the element or it's children if it's a body element to the new document
10731					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10732						doc.body.appendChild(doc.importNode(node, true));
10733					});
10734
10735					// Grab first child or body element for serialization
10736					if (node.nodeName != 'BODY')
10737						node = doc.body.firstChild;
10738					else
10739						node = doc.body;
10740
10741					// set the new document in DOMUtils so createElement etc works
10742					oldDoc = dom.doc;
10743					dom.doc = doc;
10744				}
10745
10746				args = args || {};
10747				args.format = args.format || 'html';
10748
10749				// Pre process
10750				if (!args.no_events) {
10751					args.node = node;
10752					onPreProcess.dispatch(self, args);
10753				}
10754
10755				// Setup serializer
10756				htmlSerializer = new tinymce.html.Serializer(settings, schema);
10757
10758				// Parse and serialize HTML
10759				args.content = htmlSerializer.serialize(
10760					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10761				);
10762
10763				// Replace all BOM characters for now until we can find a better solution
10764				if (!args.cleanup)
10765					args.content = args.content.replace(/\uFEFF/g, '');
10766
10767				// Post process
10768				if (!args.no_events)
10769					onPostProcess.dispatch(self, args);
10770
10771				// Restore the old document if it was changed
10772				if (oldDoc)
10773					dom.doc = oldDoc;
10774
10775				args.node = null;
10776
10777				return args.content;
10778			},
10779
10780			addRules : function(rules) {
10781				schema.addValidElements(rules);
10782			},
10783
10784			setRules : function(rules) {
10785				schema.setValidElements(rules);
10786			}
10787		};
10788	};
10789})(tinymce);
10790(function(tinymce) {
10791	tinymce.dom.ScriptLoader = function(settings) {
10792		var QUEUED = 0,
10793			LOADING = 1,
10794			LOADED = 2,
10795			states = {},
10796			queue = [],
10797			scriptLoadedCallbacks = {},
10798			queueLoadedCallbacks = [],
10799			loading = 0,
10800			undef;
10801
10802		function loadScript(url, callback) {
10803			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10804
10805			// Execute callback when script is loaded
10806			function done() {
10807				dom.remove(id);
10808
10809				if (elm)
10810					elm.onreadystatechange = elm.onload = elm = null;
10811
10812				callback();
10813			};
10814			
10815			function error() {
10816				// Report the error so it's easier for people to spot loading errors
10817				if (typeof(console) !== "undefined" && console.log)
10818					console.log("Failed to load: " + url);
10819
10820				// We can't mark it as done if there is a load error since
10821				// A) We don't want to produce 404 errors on the server and
10822				// B) the onerror event won't fire on all browsers.
10823				// done();
10824			};
10825
10826			id = dom.uniqueId();
10827
10828			if (tinymce.isIE6) {
10829				uri = new tinymce.util.URI(url);
10830				loc = location;
10831
10832				// If script is from same domain and we
10833				// use IE 6 then use XHR since it's more reliable
10834				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10835					tinymce.util.XHR.send({
10836						url : tinymce._addVer(uri.getURI()),
10837						success : function(content) {
10838							// Create new temp script element
10839							var script = dom.create('script', {
10840								type : 'text/javascript'
10841							});
10842
10843							// Evaluate script in global scope
10844							script.text = content;
10845							document.getElementsByTagName('head')[0].appendChild(script);
10846							dom.remove(script);
10847
10848							done();
10849						},
10850						
10851						error : error
10852					});
10853
10854					return;
10855				}
10856			}
10857
10858			// Create new script element
10859			elm = document.createElement('script');
10860			elm.id = id;
10861			elm.type = 'text/javascript';
10862			elm.src = tinymce._addVer(url);
10863
10864			// Add onload listener for non IE browsers since IE9
10865			// fires onload event before the script is parsed and executed
10866			if (!tinymce.isIE || tinymce.isIE11)
10867				elm.onload = done;
10868
10869			// Add onerror event will get fired on some browsers but not all of them
10870			elm.onerror = error;
10871
10872			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10873			if (!tinymce.isOpera) {
10874				elm.onreadystatechange = function() {
10875					var state = elm.readyState;
10876
10877					// Loaded state is passed on IE 6 however there
10878					// are known issues with this method but we can't use
10879					// XHR in a cross domain loading
10880					if (state == 'complete' || state == 'loaded')
10881						done();
10882				};
10883			}
10884
10885			// Most browsers support this feature so we report errors
10886			// for those at least to help users track their missing plugins etc
10887			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10888			/*elm.onerror = function() {
10889				alert('Failed to load: ' + url);
10890			};*/
10891
10892			// Add script to document
10893			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10894		};
10895
10896		this.isDone = function(url) {
10897			return states[url] == LOADED;
10898		};
10899
10900		this.markDone = function(url) {
10901			states[url] = LOADED;
10902		};
10903
10904		this.add = this.load = function(url, callback, scope) {
10905			var item, state = states[url];
10906
10907			// Add url to load queue
10908			if (state == undef) {
10909				queue.push(url);
10910				states[url] = QUEUED;
10911			}
10912
10913			if (callback) {
10914				// Store away callback for later execution
10915				if (!scriptLoadedCallbacks[url])
10916					scriptLoadedCallbacks[url] = [];
10917
10918				scriptLoadedCallbacks[url].push({
10919					func : callback,
10920					scope : scope || this
10921				});
10922			}
10923		};
10924
10925		this.loadQueue = function(callback, scope) {
10926			this.loadScripts(queue, callback, scope);
10927		};
10928
10929		this.loadScripts = function(scripts, callback, scope) {
10930			var loadScripts;
10931
10932			function execScriptLoadedCallbacks(url) {
10933				// Execute URL callback functions
10934				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10935					callback.func.call(callback.scope);
10936				});
10937
10938				scriptLoadedCallbacks[url] = undef;
10939			};
10940
10941			queueLoadedCallbacks.push({
10942				func : callback,
10943				scope : scope || this
10944			});
10945
10946			loadScripts = function() {
10947				var loadingScripts = tinymce.grep(scripts);
10948
10949				// Current scripts has been handled
10950				scripts.length = 0;
10951
10952				// Load scripts that needs to be loaded
10953				tinymce.each(loadingScripts, function(url) {
10954					// Script is already loaded then execute script callbacks directly
10955					if (states[url] == LOADED) {
10956						execScriptLoadedCallbacks(url);
10957						return;
10958					}
10959
10960					// Is script not loading then start loading it
10961					if (states[url] != LOADING) {
10962						states[url] = LOADING;
10963						loading++;
10964
10965						loadScript(url, function() {
10966							states[url] = LOADED;
10967							loading--;
10968
10969							execScriptLoadedCallbacks(url);
10970
10971							// Load more scripts if they where added by the recently loaded script
10972							loadScripts();
10973						});
10974					}
10975				});
10976
10977				// No scripts are currently loading then execute all pending queue loaded callbacks
10978				if (!loading) {
10979					tinymce.each(queueLoadedCallbacks, function(callback) {
10980						callback.func.call(callback.scope);
10981					});
10982
10983					queueLoadedCallbacks.length = 0;
10984				}
10985			};
10986
10987			loadScripts();
10988		};
10989	};
10990
10991	// Global script loader
10992	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10993})(tinymce);
10994(function(tinymce) {
10995	tinymce.dom.RangeUtils = function(dom) {
10996		var INVISIBLE_CHAR = '\uFEFF';
10997
10998		this.walk = function(rng, callback) {
10999			var startContainer = rng.startContainer,
11000				startOffset = rng.startOffset,
11001				endContainer = rng.endContainer,
11002				endOffset = rng.endOffset,
11003				ancestor, startPoint,
11004				endPoint, node, parent, siblings, nodes;
11005
11006			// Handle table cell selection the table plugin enables
11007			// you to fake select table cells and perform formatting actions on them
11008			nodes = dom.select('td.mceSelected,th.mceSelected');
11009			if (nodes.length > 0) {
11010				tinymce.each(nodes, function(node) {
11011					callback([node]);
11012				});
11013
11014				return;
11015			}
11016
11017			function exclude(nodes) {
11018				var node;
11019
11020				// First node is excluded
11021				node = nodes[0];
11022				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
11023					nodes.splice(0, 1);
11024				}
11025
11026				// Last node is excluded
11027				node = nodes[nodes.length - 1];
11028				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
11029					nodes.splice(nodes.length - 1, 1);
11030				}
11031
11032				return nodes;
11033			};
11034
11035			function collectSiblings(node, name, end_node) {
11036				var siblings = [];
11037
11038				for (; node && node != end_node; node = node[name])
11039					siblings.push(node);
11040
11041				return siblings;
11042			};
11043
11044			function findEndPoint(node, root) {
11045				do {
11046					if (node.parentNode == root)
11047						return node;
11048
11049					node = node.parentNode;
11050				} while(node);
11051			};
11052
11053			function walkBoundary(start_node, end_node, next) {
11054				var siblingName = next ? 'nextSibling' : 'previousSibling';
11055
11056				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
11057					parent = node.parentNode;
11058					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
11059
11060					if (siblings.length) {
11061						if (!next)
11062							siblings.reverse();
11063
11064						callback(exclude(siblings));
11065					}
11066				}
11067			};
11068
11069			// If index based start position then resolve it
11070			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
11071				startContainer = startContainer.childNodes[startOffset];
11072
11073			// If index based end position then resolve it
11074			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
11075				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
11076
11077			// Same container
11078			if (startContainer == endContainer)
11079				return callback(exclude([startContainer]));
11080
11081			// Find common ancestor and end points
11082			ancestor = dom.findCommonAncestor(startContainer, endContainer);
11083				
11084			// Process left side
11085			for (node = startContainer; node; node = node.parentNode) {
11086				if (node === endContainer)
11087					return walkBoundary(startContainer, ancestor, true);
11088
11089				if (node === ancestor)
11090					break;
11091			}
11092
11093			// Process right side
11094			for (node = endContainer; node; node = node.parentNode) {
11095				if (node === startContainer)
11096					return walkBoundary(endContainer, ancestor);
11097
11098				if (node === ancestor)
11099					break;
11100			}
11101
11102			// Find start/end point
11103			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
11104			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
11105
11106			// Walk left leaf
11107			walkBoundary(startContainer, startPoint, true);
11108
11109			// Walk the middle from start to end point
11110			siblings = collectSiblings(
11111				startPoint == startContainer ? startPoint : startPoint.nextSibling,
11112				'nextSibling',
11113				endPoint == endContainer ? endPoint.nextSibling : endPoint
11114			);
11115
11116			if (siblings.length)
11117				callback(exclude(siblings));
11118
11119			// Walk right leaf
11120			walkBoundary(endContainer, endPoint);
11121		};
11122
11123		this.split = function(rng) {
11124			var startContainer = rng.startContainer,
11125				startOffset = rng.startOffset,
11126				endContainer = rng.endContainer,
11127				endOffset = rng.endOffset;
11128
11129			function splitText(node, offset) {
11130				return node.splitText(offset);
11131			};
11132
11133			// Handle single text node
11134			if (startContainer == endContainer && startContainer.nodeType == 3) {
11135				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11136					endContainer = splitText(startContainer, startOffset);
11137					startContainer = endContainer.previousSibling;
11138
11139					if (endOffset > startOffset) {
11140						endOffset = endOffset - startOffset;
11141						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
11142						endOffset = endContainer.nodeValue.length;
11143						startOffset = 0;
11144					} else {
11145						endOffset = 0;
11146					}
11147				}
11148			} else {
11149				// Split startContainer text node if needed
11150				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11151					startContainer = splitText(startContainer, startOffset);
11152					startOffset = 0;
11153				}
11154
11155				// Split endContainer text node if needed
11156				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
11157					endContainer = splitText(endContainer, endOffset).previousSibling;
11158					endOffset = endContainer.nodeValue.length;
11159				}
11160			}
11161
11162			return {
11163				startContainer : startContainer,
11164				startOffset : startOffset,
11165				endContainer : endContainer,
11166				endOffset : endOffset
11167			};
11168		};
11169
11170	};
11171
11172	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
11173		if (rng1 && rng2) {
11174			// Compare native IE ranges
11175			if (rng1.item || rng1.duplicate) {
11176				// Both are control ranges and the selected element matches
11177				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
11178					return true;
11179
11180				// Both are text ranges and the range matches
11181				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
11182					return true;
11183			} else {
11184				// Compare w3c ranges
11185				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
11186			}
11187		}
11188
11189		return false;
11190	};
11191})(tinymce);
11192(function(tinymce) {
11193	var Event = tinymce.dom.Event, each = tinymce.each;
11194
11195	tinymce.create('tinymce.ui.KeyboardNavigation', {
11196		KeyboardNavigation: function(settings, dom) {
11197			var t = this, root = settings.root, items = settings.items,
11198					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
11199					excludeFromTabOrder = settings.excludeFromTabOrder,
11200					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
11201
11202			dom = dom || tinymce.DOM;
11203
11204			itemFocussed = function(evt) {
11205				focussedId = evt.target.id;
11206			};
11207			
11208			itemBlurred = function(evt) {
11209				dom.setAttrib(evt.target.id, 'tabindex', '-1');
11210			};
11211			
11212			rootFocussed = function(evt) {
11213				var item = dom.get(focussedId);
11214				dom.setAttrib(item, 'tabindex', '0');
11215				item.focus();
11216			};
11217			
11218			t.focus = function() {
11219				dom.get(focussedId).focus();
11220			};
11221
11222			t.destroy = function() {
11223				each(items, function(item) {
11224					var elm = dom.get(item.id);
11225
11226					dom.unbind(elm, 'focus', itemFocussed);
11227					dom.unbind(elm, 'blur', itemBlurred);
11228				});
11229
11230				var rootElm = dom.get(root);
11231				dom.unbind(rootElm, 'focus', rootFocussed);
11232				dom.unbind(rootElm, 'keydown', rootKeydown);
11233
11234				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
11235				t.destroy = function() {};
11236			};
11237			
11238			t.moveFocus = function(dir, evt) {
11239				var idx = -1, controls = t.controls, newFocus;
11240
11241				if (!focussedId)
11242					return;
11243
11244				each(items, function(item, index) {
11245					if (item.id === focussedId) {
11246						idx = index;
11247						return false;
11248					}
11249				});
11250
11251				idx += dir;
11252				if (idx < 0) {
11253					idx = items.length - 1;
11254				} else if (idx >= items.length) {
11255					idx = 0;
11256				}
11257				
11258				newFocus = items[idx];
11259				dom.setAttrib(focussedId, 'tabindex', '-1');
11260				dom.setAttrib(newFocus.id, 'tabindex', '0');
11261				dom.get(newFocus.id).focus();
11262
11263				if (settings.actOnFocus) {
11264					settings.onAction(newFocus.id);
11265				}
11266
11267				if (evt)
11268					Event.cancel(evt);
11269			};
11270			
11271			rootKeydown = function(evt) {
11272				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;
11273				
11274				switch (evt.keyCode) {
11275					case DOM_VK_LEFT:
11276						if (enableLeftRight) t.moveFocus(-1);
11277						Event.cancel(evt);
11278						break;
11279	
11280					case DOM_VK_RIGHT:
11281						if (enableLeftRight) t.moveFocus(1);
11282						Event.cancel(evt);
11283						break;
11284	
11285					case DOM_VK_UP:
11286						if (enableUpDown) t.moveFocus(-1);
11287						Event.cancel(evt);
11288						break;
11289
11290					case DOM_VK_DOWN:
11291						if (enableUpDown) t.moveFocus(1);
11292						Event.cancel(evt);
11293						break;
11294
11295					case DOM_VK_ESCAPE:
11296						if (settings.onCancel) {
11297							settings.onCancel();
11298							Event.cancel(evt);
11299						}
11300						break;
11301
11302					case DOM_VK_ENTER:
11303					case DOM_VK_RETURN:
11304					case DOM_VK_SPACE:
11305						if (settings.onAction) {
11306							settings.onAction(focussedId);
11307							Event.cancel(evt);
11308						}
11309						break;
11310				}
11311			};
11312
11313			// Set up state and listeners for each item.
11314			each(items, function(item, idx) {
11315				var tabindex, elm;
11316
11317				if (!item.id) {
11318					item.id = dom.uniqueId('_mce_item_');
11319				}
11320
11321				elm = dom.get(item.id);
11322
11323				if (excludeFromTabOrder) {
11324					dom.bind(elm, 'blur', itemBlurred);
11325					tabindex = '-1';
11326				} else {
11327					tabindex = (idx === 0 ? '0' : '-1');
11328				}
11329
11330				elm.setAttribute('tabindex', tabindex);
11331				dom.bind(elm, 'focus', itemFocussed);
11332			});
11333			
11334			// Setup initial state for root element.
11335			if (items[0]){
11336				focussedId = items[0].id;
11337			}
11338
11339			dom.setAttrib(root, 'tabindex', '-1');
11340
11341			// Setup listeners for root element.
11342			var rootElm = dom.get(root);
11343			dom.bind(rootElm, 'focus', rootFocussed);
11344			dom.bind(rootElm, 'keydown', rootKeydown);
11345		}
11346	});
11347})(tinymce);
11348(function(tinymce) {
11349	// Shorten class names
11350	var DOM = tinymce.DOM, is = tinymce.is;
11351
11352	tinymce.create('tinymce.ui.Control', {
11353		Control : function(id, s, editor) {
11354			this.id = id;
11355			this.settings = s = s || {};
11356			this.rendered = false;
11357			this.onRender = new tinymce.util.Dispatcher(this);
11358			this.classPrefix = '';
11359			this.scope = s.scope || this;
11360			this.disabled = 0;
11361			this.active = 0;
11362			this.editor = editor;
11363		},
11364		
11365		setAriaProperty : function(property, value) {
11366			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
11367			if (element) {
11368				DOM.setAttrib(element, 'aria-' + property, !!value);
11369			}
11370		},
11371		
11372		focus : function() {
11373			DOM.get(this.id).focus();
11374		},
11375
11376		setDisabled : function(s) {
11377			if (s != this.disabled) {
11378				this.setAriaProperty('disabled', s);
11379
11380				this.setState('Disabled', s);
11381				this.setState('Enabled', !s);
11382				this.disabled = s;
11383			}
11384		},
11385
11386		isDisabled : function() {
11387			return this.disabled;
11388		},
11389
11390		setActive : function(s) {
11391			if (s != this.active) {
11392				this.setState('Active', s);
11393				this.active = s;
11394				this.setAriaProperty('pressed', s);
11395			}
11396		},
11397
11398		isActive : function() {
11399			return this.active;
11400		},
11401
11402		setState : function(c, s) {
11403			var n = DOM.get(this.id);
11404
11405			c = this.classPrefix + c;
11406
11407			if (s)
11408				DOM.addClass(n, c);
11409			else
11410				DOM.removeClass(n, c);
11411		},
11412
11413		isRendered : function() {
11414			return this.rendered;
11415		},
11416
11417		renderHTML : function() {
11418		},
11419
11420		renderTo : function(n) {
11421			DOM.setHTML(n, this.renderHTML());
11422		},
11423
11424		postRender : function() {
11425			var t = this, b;
11426
11427			// Set pending states
11428			if (is(t.disabled)) {
11429				b = t.disabled;
11430				t.disabled = -1;
11431				t.setDisabled(b);
11432			}
11433
11434			if (is(t.active)) {
11435				b = t.active;
11436				t.active = -1;
11437				t.setActive(b);
11438			}
11439		},
11440
11441		remove : function() {
11442			DOM.remove(this.id);
11443			this.destroy();
11444		},
11445
11446		destroy : function() {
11447			tinymce.dom.Event.clear(this.id);
11448		}
11449	});
11450})(tinymce);
11451tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11452	Container : function(id, s, editor) {
11453		this.parent(id, s, editor);
11454
11455		this.controls = [];
11456
11457		this.lookup = {};
11458	},
11459
11460	add : function(c) {
11461		this.lookup[c.id] = c;
11462		this.controls.push(c);
11463
11464		return c;
11465	},
11466
11467	get : function(n) {
11468		return this.lookup[n];
11469	}
11470});
11471
11472tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11473	Separator : function(id, s) {
11474		this.parent(id, s);
11475		this.classPrefix = 'mceSeparator';
11476		this.setDisabled(true);
11477	},
11478
11479	renderHTML : function() {
11480		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11481	}
11482});
11483(function(tinymce) {
11484	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11485
11486	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11487		MenuItem : function(id, s) {
11488			this.parent(id, s);
11489			this.classPrefix = 'mceMenuItem';
11490		},
11491
11492		setSelected : function(s) {
11493			this.setState('Selected', s);
11494			this.setAriaProperty('checked', !!s);
11495			this.selected = s;
11496		},
11497
11498		isSelected : function() {
11499			return this.selected;
11500		},
11501
11502		postRender : function() {
11503			var t = this;
11504			
11505			t.parent();
11506
11507			// Set pending state
11508			if (is(t.selected))
11509				t.setSelected(t.selected);
11510		}
11511	});
11512})(tinymce);
11513(function(tinymce) {
11514	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11515
11516	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11517		Menu : function(id, s) {
11518			var t = this;
11519
11520			t.parent(id, s);
11521			t.items = {};
11522			t.collapsed = false;
11523			t.menuCount = 0;
11524			t.onAddItem = new tinymce.util.Dispatcher(this);
11525		},
11526
11527		expand : function(d) {
11528			var t = this;
11529
11530			if (d) {
11531				walk(t, function(o) {
11532					if (o.expand)
11533						o.expand();
11534				}, 'items', t);
11535			}
11536
11537			t.collapsed = false;
11538		},
11539
11540		collapse : function(d) {
11541			var t = this;
11542
11543			if (d) {
11544				walk(t, function(o) {
11545					if (o.collapse)
11546						o.collapse();
11547				}, 'items', t);
11548			}
11549
11550			t.collapsed = true;
11551		},
11552
11553		isCollapsed : function() {
11554			return this.collapsed;
11555		},
11556
11557		add : function(o) {
11558			if (!o.settings)
11559				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11560
11561			this.onAddItem.dispatch(this, o);
11562
11563			return this.items[o.id] = o;
11564		},
11565
11566		addSeparator : function() {
11567			return this.add({separator : true});
11568		},
11569
11570		addMenu : function(o) {
11571			if (!o.collapse)
11572				o = this.createMenu(o);
11573
11574			this.menuCount++;
11575
11576			return this.add(o);
11577		},
11578
11579		hasMenus : function() {
11580			return this.menuCount !== 0;
11581		},
11582
11583		remove : function(o) {
11584			delete this.items[o.id];
11585		},
11586
11587		removeAll : function() {
11588			var t = this;
11589
11590			walk(t, function(o) {
11591				if (o.removeAll)
11592					o.removeAll();
11593				else
11594					o.remove();
11595
11596				o.destroy();
11597			}, 'items', t);
11598
11599			t.items = {};
11600		},
11601
11602		createMenu : function(o) {
11603			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11604
11605			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11606
11607			return m;
11608		}
11609	});
11610})(tinymce);
11611(function(tinymce) {
11612	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11613
11614	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11615		DropMenu : function(id, s) {
11616			s = s || {};
11617			s.container = s.container || DOM.doc.body;
11618			s.offset_x = s.offset_x || 0;
11619			s.offset_y = s.offset_y || 0;
11620			s.vp_offset_x = s.vp_offset_x || 0;
11621			s.vp_offset_y = s.vp_offset_y || 0;
11622
11623			if (is(s.icons) && !s.icons)
11624				s['class'] += ' mceNoIcons';
11625
11626			this.parent(id, s);
11627			this.onShowMenu = new tinymce.util.Dispatcher(this);
11628			this.onHideMenu = new tinymce.util.Dispatcher(this);
11629			this.classPrefix = 'mceMenu';
11630		},
11631
11632		createMenu : function(s) {
11633			var t = this, cs = t.settings, m;
11634
11635			s.container = s.container || cs.container;
11636			s.parent = t;
11637			s.constrain = s.constrain || cs.constrain;
11638			s['class'] = s['class'] || cs['class'];
11639			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11640			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11641			s.keyboard_focus = cs.keyboard_focus;
11642			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11643
11644			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11645
11646			return m;
11647		},
11648		
11649		focus : function() {
11650			var t = this;
11651			if (t.keyboardNav) {
11652				t.keyboardNav.focus();
11653			}
11654		},
11655
11656		update : function() {
11657			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11658
11659			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11660			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11661
11662			if (!DOM.boxModel)
11663				t.element.setStyles({width : tw + 2, height : th + 2});
11664			else
11665				t.element.setStyles({width : tw, height : th});
11666
11667			if (s.max_width)
11668				DOM.setStyle(co, 'width', tw);
11669
11670			if (s.max_height) {
11671				DOM.setStyle(co, 'height', th);
11672
11673				if (tb.clientHeight < s.max_height)
11674					DOM.setStyle(co, 'overflow', 'hidden');
11675			}
11676		},
11677
11678		showMenu : function(x, y, px) {
11679			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11680
11681			t.collapse(1);
11682
11683			if (t.isMenuVisible)
11684				return;
11685
11686			if (!t.rendered) {
11687				co = DOM.add(t.settings.container, t.renderNode());
11688
11689				each(t.items, function(o) {
11690					o.postRender();
11691				});
11692
11693				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11694			} else
11695				co = DOM.get('menu_' + t.id);
11696
11697			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11698			if (!tinymce.isOpera)
11699				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11700
11701			DOM.show(co);
11702			t.update();
11703
11704			x += s.offset_x || 0;
11705			y += s.offset_y || 0;
11706			vp.w -= 4;
11707			vp.h -= 4;
11708
11709			// Move inside viewport if not submenu
11710			if (s.constrain) {
11711				w = co.clientWidth - ot;
11712				h = co.clientHeight - ot;
11713				mx = vp.x + vp.w;
11714				my = vp.y + vp.h;
11715
11716				if ((x + s.vp_offset_x + w) > mx)
11717					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11718
11719				if ((y + s.vp_offset_y + h) > my)
11720					y = Math.max(0, (my - s.vp_offset_y) - h);
11721			}
11722
11723			DOM.setStyles(co, {left : x , top : y});
11724			t.element.update();
11725
11726			t.isMenuVisible = 1;
11727			t.mouseClickFunc = Event.add(co, 'click', function(e) {
11728				var m;
11729
11730				e = e.target;
11731
11732				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11733					m = t.items[e.id];
11734
11735					if (m.isDisabled())
11736						return;
11737
11738					dm = t;
11739
11740					while (dm) {
11741						if (dm.hideMenu)
11742							dm.hideMenu();
11743
11744						dm = dm.settings.parent;
11745					}
11746
11747					if (m.settings.onclick)
11748						m.settings.onclick(e);
11749
11750					return false; // Cancel to fix onbeforeunload problem
11751				}
11752			});
11753
11754			if (t.hasMenus()) {
11755				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11756					var m, r, mi;
11757
11758					e = e.target;
11759					if (e && (e = DOM.getParent(e, 'tr'))) {
11760						m = t.items[e.id];
11761
11762						if (t.lastMenu)
11763							t.lastMenu.collapse(1);
11764
11765						if (m.isDisabled())
11766							return;
11767
11768						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11769							//p = DOM.getPos(s.container);
11770							r = DOM.getRect(e);
11771							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11772							t.lastMenu = m;
11773							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11774						}
11775					}
11776				});
11777			}
11778			
11779			Event.add(co, 'keydown', t._keyHandler, t);
11780
11781			t.onShowMenu.dispatch(t);
11782
11783			if (s.keyboard_focus) { 
11784				t._setupKeyboardNav(); 
11785			}
11786		},
11787
11788		hideMenu : function(c) {
11789			var t = this, co = DOM.get('menu_' + t.id), e;
11790
11791			if (!t.isMenuVisible)
11792				return;
11793
11794			if (t.keyboardNav) t.keyboardNav.destroy();
11795			Event.remove(co, 'mouseover', t.mouseOverFunc);
11796			Event.remove(co, 'click', t.mouseClickFunc);
11797			Event.remove(co, 'keydown', t._keyHandler);
11798			DOM.hide(co);
11799			t.isMenuVisible = 0;
11800
11801			if (!c)
11802				t.collapse(1);
11803
11804			if (t.element)
11805				t.element.hide();
11806
11807			if (e = DOM.get(t.id))
11808				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11809
11810			t.onHideMenu.dispatch(t);
11811		},
11812
11813		add : function(o) {
11814			var t = this, co;
11815
11816			o = t.parent(o);
11817
11818			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11819				t._add(DOM.select('tbody', co)[0], o);
11820
11821			return o;
11822		},
11823
11824		collapse : function(d) {
11825			this.parent(d);
11826			this.hideMenu(1);
11827		},
11828
11829		remove : function(o) {
11830			DOM.remove(o.id);
11831			this.destroy();
11832
11833			return this.parent(o);
11834		},
11835
11836		destroy : function() {
11837			var t = this, co = DOM.get('menu_' + t.id);
11838
11839			if (t.keyboardNav) t.keyboardNav.destroy();
11840			Event.remove(co, 'mouseover', t.mouseOverFunc);
11841			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11842			Event.remove(co, 'click', t.mouseClickFunc);
11843			Event.remove(co, 'keydown', t._keyHandler);
11844
11845			if (t.element)
11846				t.element.remove();
11847
11848			DOM.remove(co);
11849		},
11850
11851		renderNode : function() {
11852			var t = this, s = t.settings, n, tb, co, w;
11853
11854			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'});
11855			if (t.settings.parent) {
11856				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11857			}
11858			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11859			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11860
11861			if (s.menu_line)
11862				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11863
11864//			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11865			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11866			tb = DOM.add(n, 'tbody');
11867
11868			each(t.items, function(o) {
11869				t._add(tb, o);
11870			});
11871
11872			t.rendered = true;
11873
11874			return w;
11875		},
11876
11877		// Internal functions
11878		_setupKeyboardNav : function(){
11879			var contextMenu, menuItems, t=this; 
11880			contextMenu = DOM.get('menu_' + t.id);
11881			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11882			menuItems.splice(0,0,contextMenu);
11883			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11884				root: 'menu_' + t.id,
11885				items: menuItems,
11886				onCancel: function() {
11887					t.hideMenu();
11888				},
11889				enableUpDown: true
11890			});
11891			contextMenu.focus();
11892		},
11893
11894		_keyHandler : function(evt) {
11895			var t = this, e;
11896			switch (evt.keyCode) {
11897				case 37: // Left
11898					if (t.settings.parent) {
11899						t.hideMenu();
11900						t.settings.parent.focus();
11901						Event.cancel(evt);
11902					}
11903					break;
11904				case 39: // Right
11905					if (t.mouseOverFunc)
11906						t.mouseOverFunc(evt);
11907					break;
11908			}
11909		},
11910
11911		_add : function(tb, o) {
11912			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11913
11914			if (s.separator) {
11915				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11916				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11917
11918				if (n = ro.previousSibling)
11919					DOM.addClass(n, 'mceLast');
11920
11921				return;
11922			}
11923
11924			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11925			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11926			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11927
11928			if (s.parent) {
11929				DOM.setAttrib(a, 'aria-haspopup', 'true');
11930				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11931			}
11932
11933			DOM.addClass(it, s['class']);
11934//			n = DOM.add(n, 'span', {'class' : 'item'});
11935
11936			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11937
11938			if (s.icon_src)
11939				DOM.add(ic, 'img', {src : s.icon_src});
11940
11941			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11942
11943			if (o.settings.style) {
11944				if (typeof o.settings.style == "function")
11945					o.settings.style = o.settings.style();
11946
11947				DOM.setAttrib(n, 'style', o.settings.style);
11948			}
11949
11950			if (tb.childNodes.length == 1)
11951				DOM.addClass(ro, 'mceFirst');
11952
11953			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11954				DOM.addClass(ro, 'mceFirst');
11955
11956			if (o.collapse)
11957				DOM.addClass(ro, cp + 'ItemSub');
11958
11959			if (n = ro.previousSibling)
11960				DOM.removeClass(n, 'mceLast');
11961
11962			DOM.addClass(ro, 'mceLast');
11963		}
11964	});
11965})(tinymce);
11966(function(tinymce) {
11967	var DOM = tinymce.DOM;
11968
11969	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11970		Button : function(id, s, ed) {
11971			this.parent(id, s, ed);
11972			this.classPrefix = 'mceButton';
11973		},
11974
11975		renderHTML : function() {
11976			var cp = this.classPrefix, s = this.settings, h, l;
11977
11978			l = DOM.encode(s.label || '');
11979			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) + '">';
11980			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11981				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11982			else
11983				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11984
11985			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">
11985' + s.title + '</span>';
11986			h += '</a>';
11987			return h;
11988		},
11989
11990		postRender : function() {
11991			var t = this, s = t.settings, imgBookmark;
11992
11993			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11994			// need to keep the selection in case the selection is lost
11995			if (tinymce.isIE && t.editor) {
11996				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11997					var nodeName = t.editor.selection.getNode().nodeName;
11998					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11999				});
12000			}
12001			tinymce.dom.Event.add(t.id, 'click', function(e) {
12002				if (!t.isDisabled()) {
12003					// restore the selection in case the selection is lost in IE
12004					if (tinymce.isIE && t.editor && imgBookmark !== null) {
12005						t.editor.selection.moveToBookmark(imgBookmark);
12006					}
12007					return s.onclick.call(s.scope, e);
12008				}
12009			});
12010			tinymce.dom.Event.add(t.id, 'keydown', function(e) {
12011				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) {
12012					tinymce.dom.Event.cancel(e);
12013					return s.onclick.call(s.scope, e);
12014				}
12015			});
12016		}
12017	});
12018})(tinymce);
12019(function(tinymce) {
12020	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
12021
12022	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
12023		ListBox : function(id, s, ed) {
12024			var t = this;
12025
12026			t.parent(id, s, ed);
12027
12028			t.items = [];
12029
12030			t.onChange = new Dispatcher(t);
12031
12032			t.onPostRender = new Dispatcher(t);
12033
12034			t.onAdd = new Dispatcher(t);
12035
12036			t.onRenderMenu = new tinymce.util.Dispatcher(this);
12037
12038			t.classPrefix = 'mceListBox';
12039			t.marked = {};
12040		},
12041
12042		select : function(va) {
12043			var t = this, fv, f;
12044
12045			t.marked = {};
12046
12047			if (va == undef)
12048				return t.selectByIndex(-1);
12049
12050			// Is string or number make function selector
12051			if (va && typeof(va)=="function")
12052				f = va;
12053			else {
12054				f = function(v) {
12055					return v == va;
12056				};
12057			}
12058
12059			// Do we need to do something?
12060			if (va != t.selectedValue) {
12061				// Find item
12062				each(t.items, function(o, i) {
12063					if (f(o.value)) {
12064						fv = 1;
12065						t.selectByIndex(i);
12066						return false;
12067					}
12068				});
12069
12070				if (!fv)
12071					t.selectByIndex(-1);
12072			}
12073		},
12074
12075		selectByIndex : function(idx) {
12076			var t = this, e, o, label;
12077
12078			t.marked = {};
12079
12080			if (idx != t.selectedIndex) {
12081				e = DOM.get(t.id + '_text');
12082				label = DOM.get(t.id + '_voiceDesc');
12083				o = t.items[idx];
12084
12085				if (o) {
12086					t.selectedValue = o.value;
12087					t.selectedIndex = idx;
12088					DOM.setHTML(e, DOM.encode(o.title));
12089					DOM.setHTML(label, t.settings.title + " - " + o.title);
12090					DOM.removeClass(e, 'mceTitle');
12091					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
12092				} else {
12093					DOM.setHTML(e, DOM.encode(t.settings.title));
12094					DOM.setHTML(label, DOM.encode(t.settings.title));
12095					DOM.addClass(e, 'mceTitle');
12096					t.selectedValue = t.selectedIndex = null;
12097					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
12098				}
12099				e = 0;
12100			}
12101		},
12102
12103		mark : function(value) {
12104			this.marked[value] = true;
12105		},
12106
12107		add : function(n, v, o) {
12108			var t = this;
12109
12110			o = o || {};
12111			o = tinymce.extend(o, {
12112				title : n,
12113				value : v
12114			});
12115
12116			t.items.push(o);
12117			t.onAdd.dispatch(t, o);
12118		},
12119
12120		getLength : function() {
12121			return this.items.length;
12122		},
12123
12124		renderHTML : function() {
12125			var h = '', t = this, s = t.settings, cp = t.classPrefix;
12126
12127			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>';
12128			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
12129			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>';
12130			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>';
12131			h += '</tr></tbody></table></span>';
12132
12133			return h;
12134		},
12135
12136		showMenu : function() {
12137			var t = this, p2, e = DOM.get(this.id), m;
12138
12139			if (t.isDisabled() || t.items.length === 0)
12140				return;
12141
12142			if (t.menu && t.menu.isMenuVisible)
12143				return t.hideMenu();
12144
12145			if (!t.isMenuRendered) {
12146				t.renderMenu();
12147				t.isMenuRendered = true;
12148			}
12149
12150			p2 = DOM.getPos(e);
12151
12152			m = t.menu;
12153			m.settings.offset_x = p2.x;
12154			m.settings.offset_y = p2.y;
12155			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
12156
12157			// Select in menu
12158			each(t.items, function(o) {
12159				if (m.items[o.id]) {
12160					m.items[o.id].setSelected(0);
12161				}
12162			});
12163
12164			each(t.items, function(o) {
12165				if (m.items[o.id] && t.marked[o.value]) {
12166					m.items[o.id].setSelected(1);
12167				}
12168
12169				if (o.value === t.selectedValue) {
12170					m.items[o.id].setSelected(1);
12171				}
12172			});
12173
12174			m.showMenu(0, e.clientHeight);
12175
12176			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12177			DOM.addClass(t.id, t.classPrefix + 'Selected');
12178
12179			//DOM.get(t.id + '_text').focus();
12180		},
12181
12182		hideMenu : function(e) {
12183			var t = this;
12184
12185			if (t.menu && t.menu.isMenuVisible) {
12186				DOM.removeClass(t.id, t.classPrefix + 'Selected');
12187
12188				// Prevent double toogles by canceling the mouse click event to the button
12189				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
12190					return;
12191
12192				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12193					DOM.removeClass(t.id, t.classPrefix + 'Selected');
12194					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12195					t.menu.hideMenu();
12196				}
12197			}
12198		},
12199
12200		renderMenu : function() {
12201			var t = this, m;
12202
12203			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12204				menu_line : 1,
12205				'class' : t.classPrefix + 'Menu mceNoIcons',
12206				max_width : 250,
12207				max_height : 150
12208			});
12209
12210			m.onHideMenu.add(function() {
12211				t.hideMenu();
12212				t.focus();
12213			});
12214
12215			m.add({
12216				title : t.settings.title,
12217				'class' : 'mceMenuItemTitle',
12218				onclick : function() {
12219					if (t.settings.onselect('') !== false)
12220						t.select(''); // Must be runned after
12221				}
12222			});
12223
12224			each(t.items, function(o) {
12225				// No value then treat it as a title
12226				if (o.value === undef) {
12227					m.add({
12228						title : o.title,
12229						role : "option",
12230						'class' : 'mceMenuItemTitle',
12231						onclick : function() {
12232							if (t.settings.onselect('') !== false)
12233								t.select(''); // Must be runned after
12234						}
12235					});
12236				} else {
12237					o.id = DOM.uniqueId();
12238					o.role= "option";
12239					o.onclick = function() {
12240						if (t.settings.onselect(o.value) !== false)
12241							t.select(o.value); // Must be runned after
12242					};
12243
12244					m.add(o);
12245				}
12246			});
12247
12248			t.onRenderMenu.dispatch(t, m);
12249			t.menu = m;
12250		},
12251
12252		postRender : function() {
12253			var t = this, cp = t.classPrefix;
12254
12255			Event.add(t.id, 'click', t.showMenu, t);
12256			Event.add(t.id, 'keydown', function(evt) {
12257				if (evt.keyCode == 32) { // Space
12258					t.showMenu(evt);
12259					Event.cancel(evt);
12260				}
12261			});
12262			Event.add(t.id, 'focus', function() {
12263				if (!t._focused) {
12264					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
12265						if (e.keyCode == 40) {
12266							t.showMenu();
12267							Event.cancel(e);
12268						}
12269					});
12270					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
12271						var v;
12272						if (e.keyCode == 13) {
12273							// Fake select on enter
12274							v = t.selectedValue;
12275							t.selectedValue = null; // Needs to be null to fake change
12276							Event.cancel(e);
12277							t.settings.onselect(v);
12278						}
12279					});
12280				}
12281
12282				t._focused = 1;
12283			});
12284			Event.add(t.id, 'blur', function() {
12285				Event.remove(t.id, 'keydown', t.keyDownHandler);
12286				Event.remove(t.id, 'keypress', t.keyPressHandler);
12287				t._focused = 0;
12288			});
12289
12290			// Old IE doesn't have hover on all elements
12291			if (tinymce.isIE6 || !DOM.boxModel) {
12292				Event.add(t.id, 'mouseover', function() {
12293					if (!DOM.hasClass(t.id, cp + 'Disabled'))
12294						DOM.addClass(t.id, cp + 'Hover');
12295				});
12296
12297				Event.add(t.id, 'mouseout', function() {
12298					if (!DOM.hasClass(t.id, cp + 'Disabled'))
12299						DOM.removeClass(t.id, cp + 'Hover');
12300				});
12301			}
12302
12303			t.onPostRender.dispatch(t, DOM.get(t.id));
12304		},
12305
12306		destroy : function() {
12307			this.parent();
12308
12309			Event.clear(this.id + '_text');
12310			Event.clear(this.id + '_open');
12311		}
12312	});
12313})(tinymce);
12314(function(tinymce) {
12315	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
12316
12317	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
12318		NativeListBox : function(id, s) {
12319			this.parent(id, s);
12320			this.classPrefix = 'mceNativeListBox';
12321		},
12322
12323		setDisabled : function(s) {
12324			DOM.get(this.id).disabled = s;
12325			this.setAriaProperty('disabled', s);
12326		},
12327
12328		isDisabled : function() {
12329			return DOM.get(this.id).disabled;
12330		},
12331
12332		select : function(va) {
12333			var t = this, fv, f;
12334
12335			if (va == undef)
12336				return t.selectByIndex(-1);
12337
12338			// Is string or number make function selector
12339			if (va && typeof(va)=="function")
12340				f = va;
12341			else {
12342				f = function(v) {
12343					return v == va;
12344				};
12345			}
12346
12347			// Do we need to do something?
12348			if (va != t.selectedValue) {
12349				// Find item
12350				each(t.items, function(o, i) {
12351					if (f(o.value)) {
12352						fv = 1;
12353						t.selectByIndex(i);
12354						return false;
12355					}
12356				});
12357
12358				if (!fv)
12359					t.selectByIndex(-1);
12360			}
12361		},
12362
12363		selectByIndex : function(idx) {
12364			DOM.get(this.id).selectedIndex = idx + 1;
12365			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
12366		},
12367
12368		add : function(n, v, a) {
12369			var o, t = this;
12370
12371			a = a || {};
12372			a.value = v;
12373
12374			if (t.isRendered())
12375				DOM.add(DOM.get(this.id), 'option', a, n);
12376
12377			o = {
12378				title : n,
12379				value : v,
12380				attribs : a
12381			};
12382
12383			t.items.push(o);
12384			t.onAdd.dispatch(t, o);
12385		},
12386
12387		getLength : function() {
12388			return this.items.length;
12389		},
12390
12391		renderHTML : function() {
12392			var h, t = this;
12393
12394			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12395
12396			each(t.items, function(it) {
12397				h += DOM.createHTML('option', {value : it.value}, it.title);
12398			});
12399
12400			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12401			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12402			return h;
12403		},
12404
12405		postRender : function() {
12406			var t = this, ch, changeListenerAdded = true;
12407
12408			t.rendered = true;
12409
12410			function onChange(e) {
12411				var v = t.items[e.target.selectedIndex - 1];
12412
12413				if (v && (v = v.value)) {
12414					t.onChange.dispatch(t, v);
12415
12416					if (t.settings.onselect)
12417						t.settings.onselect(v);
12418				}
12419			};
12420
12421			Event.add(t.id, 'change', onChange);
12422
12423			// Accessibility keyhandler
12424			Event.add(t.id, 'keydown', function(e) {
12425				var bf, DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
12426
12427				Event.remove(t.id, 'change', ch);
12428				changeListenerAdded = false;
12429
12430				bf = Event.add(t.id, 'blur', function() {
12431					if (changeListenerAdded) return;
12432					changeListenerAdded = true;
12433					Event.add(t.id, 'change', onChange);
12434					Event.remove(t.id, 'blur', bf);
12435				});
12436
12437				if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) {
12438					onChange(e);
12439					return Event.cancel(e);
12440				} else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) {
12441					// allow native implementation (navigate select element options)
12442					e.stopImmediatePropagation();
12443				}
12444			});
12445
12446			t.onPostRender.dispatch(t, DOM.get(t.id));
12447		}
12448	});
12449})(tinymce);
12450(function(tinymce) {
12451	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12452
12453	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12454		MenuButton : function(id, s, ed) {
12455			this.parent(id, s, ed);
12456
12457			this.onRenderMenu = new tinymce.util.Dispatcher(this);
12458
12459			s.menu_container = s.menu_container || DOM.doc.body;
12460		},
12461
12462		showMenu : function() {
12463			var t = this, p1, p2, e = DOM.get(t.id), m;
12464
12465			if (t.isDisabled())
12466				return;
12467
12468			if (!t.isMenuRendered) {
12469				t.renderMenu();
12470				t.isMenuRendered = true;
12471			}
12472
12473			if (t.isMenuVisible)
12474				return t.hideMenu();
12475
12476			p1 = DOM.getPos(t.settings.menu_container);
12477			p2 = DOM.getPos(e);
12478
12479			m = t.menu;
12480			m.settings.offset_x = p2.x;
12481			m.settings.offset_y = p2.y;
12482			m.settings.vp_offset_x = p2.x;
12483			m.settings.vp_offset_y = p2.y;
12484			m.settings.keyboard_focus = t._focused;
12485			m.showMenu(0, e.firstChild.clientHeight);
12486
12487			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12488			t.setState('Selected', 1);
12489
12490			t.isMenuVisible = 1;
12491		},
12492
12493		renderMenu : function() {
12494			var t = this, m;
12495
12496			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12497				menu_line : 1,
12498				'class' : this.classPrefix + 'Menu',
12499				icons : t.settings.icons
12500			});
12501
12502			m.onHideMenu.add(function() {
12503				t.hideMenu();
12504				t.focus();
12505			});
12506
12507			t.onRenderMenu.dispatch(t, m);
12508			t.menu = m;
12509		},
12510
12511		hideMenu : function(e) {
12512			var t = this;
12513
12514			// Prevent double toogles by canceling the mouse click event to the button
12515			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12516				return;
12517
12518			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12519				t.setState('Selected', 0);
12520				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12521				if (t.menu)
12522					t.menu.hideMenu();
12523			}
12524
12525			t.isMenuVisible = 0;
12526		},
12527
12528		postRender : function() {
12529			var t = this, s = t.settings;
12530
12531			Event.add(t.id, 'click', function() {
12532				if (!t.isDisabled()) {
12533					if (s.onclick)
12534						s.onclick(t.value);
12535
12536					t.showMenu();
12537				}
12538			});
12539		}
12540	});
12541})(tinymce);
12542(function(tinymce) {
12543	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12544
12545	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12546		SplitButton : function(id, s, ed) {
12547			this.parent(id, s, ed);
12548			this.classPrefix = 'mceSplitButton';
12549		},
12550
12551		renderHTML : function() {
12552			var h, t = this, s = t.settings, h1;
12553
12554			h = '<tbody><tr>';
12555
12556			if (s.image)
12557				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12558			else
12559				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12560
12561			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12562			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>';
12563	
12564			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12565			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>';
12566
12567			h += '</tr></tbody>';
12568			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12569			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12570		},
12571
12572		postRender : function() {
12573			var t = this, s = t.settings, activate;
12574
12575			if (s.onclick) {
12576				activate = function(evt) {
12577					if (!t.isDisabled()) {
12578						s.onclick(t.value);
12579						Event.cancel(evt);
12580					}
12581				};
12582				Event.add(t.id + '_action', 'click', activate);
12583				Event.add(t.id, ['click', 'keydown'], function(evt) {
12584					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12585					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12586						activate();
12587						Event.cancel(evt);
12588					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12589						t.showMenu();
12590						Event.cancel(evt);
12591					}
12592				});
12593			}
12594
12595			Event.add(t.id + '_open', 'click', function (evt) {
12596				t.showMenu();
12597				Event.cancel(evt);
12598			});
12599			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12600			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12601
12602			// Old IE doesn't have hover on all elements
12603			if (tinymce.isIE6 || !DOM.boxModel) {
12604				Event.add(t.id, 'mouseover', function() {
12605					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12606						DOM.addClass(t.id, 'mceSplitButtonHover');
12607				});
12608
12609				Event.add(t.id, 'mouseout', function() {
12610					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12611						DOM.removeClass(t.id, 'mceSplitButtonHover');
12612				});
12613			}
12614		},
12615
12616		destroy : function() {
12617			this.parent();
12618
12619			Event.clear(this.id + '_action');
12620			Event.clear(this.id + '_open');
12621			Event.clear(this.id);
12622		}
12623	});
12624})(tinymce);
12625(function(tinymce) {
12626	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12627
12628	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12629		ColorSplitButton : function(id, s, ed) {
12630			var t = this;
12631
12632			t.parent(id, s, ed);
12633
12634			t.settings = s = tinymce.extend({
12635				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',
12636				grid_width : 8,
12637				default_color : '#888888'
12638			}, t.settings);
12639
12640			t.onShowMenu = new tinymce.util.Dispatcher(t);
12641
12642			t.onHideMenu = new tinymce.util.Dispatcher(t);
12643
12644			t.value = s.default_color;
12645		},
12646
12647		showMenu : function() {
12648			var t = this, r, p, e, p2;
12649
12650			if (t.isDisabled())
12651				return;
12652
12653			if (!t.isMenuRendered) {
12654				t.renderMenu();
12655				t.isMenuRendered = true;
12656			}
12657
12658			if (t.isMenuVisible)
12659				return t.hideMenu();
12660
12661			e = DOM.get(t.id);
12662			DOM.show(t.id + '_menu');
12663			DOM.addClass(e, 'mceSplitButtonSelected');
12664			p2 = DOM.getPos(e);
12665			DOM.setStyles(t.id + '_menu', {
12666				left : p2.x,
12667				top : p2.y + e.firstChild.clientHeight,
12668				zIndex : 200000
12669			});
12670			e = 0;
12671
12672			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12673			t.onShowMenu.dispatch(t);
12674
12675			if (t._focused) {
12676				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12677					if (e.keyCode == 27)
12678						t.hideMenu();
12679				});
12680
12681				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12682			}
12683
12684			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12685				root: t.id + '_menu',
12686				items: DOM.select('a', t.id + '_menu'),
12687				onCancel: function() {
12688					t.hideMenu();
12689					t.focus();
12690				}
12691			});
12692
12693			t.keyboardNav.focus();
12694			t.isMenuVisible = 1;
12695		},
12696
12697		hideMenu : function(e) {
12698			var t = this;
12699
12700			if (t.isMenuVisible) {
12701				// Prevent double toogles by canceling the mouse click event to the button
12702				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12703					return;
12704
12705				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12706					DOM.removeClass(t.id, 'mceSplitButtonSelected');
12707					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12708					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12709					DOM.hide(t.id + '_menu');
12710				}
12711
12712				t.isMenuVisible = 0;
12713				t.onHideMenu.dispatch();
12714				t.keyboardNav.destroy();
12715			}
12716		},
12717
12718		renderMenu : function() {
12719			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12720
12721			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;'});
12722			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12723			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12724
12725			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12726			tb = DOM.add(n, 'tbody');
12727
12728			// Generate color grid
12729			i = 0;
12730			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12731				c = c.replace(/^#/, '');
12732
12733				if (!i--) {
12734					tr = DOM.add(tb, 'tr');
12735					i = s.grid_width - 1;
12736				}
12737
12738				n = DOM.add(tr, 'td');
12739				var settings = {
12740					href : 'javascript:;',
12741					style : {
12742						backgroundColor : '#' + c
12743					},
12744					'title': t.editor.getLang('colors.' + c, c),
12745					'data-mce-color' : '#' + c
12746				};
12747
12748				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12749				if (!tinymce.isIE ) {
12750					settings.role = 'option';
12751				}
12752
12753				n = DOM.add(n, 'a', settings);
12754
12755				if (t.editor.forcedHighContrastMode) {
12756					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12757					if (n.getContext && (context = n.getContext("2d"))) {
12758						context.fillStyle = '#' + c;
12759						context.fillRect(0, 0, 16, 16);
12760					} else {
12761						// No point leaving a canvas element around if it's not supported for drawing on anyway.
12762						DOM.remove(n);
12763					}
12764				}
12765			});
12766
12767			if (s.more_colors_func) {
12768				n = DOM.add(tb, 'tr');
12769				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12770				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12771
12772				Event.add(n, 'click', function(e) {
12773					s.more_colors_func.call(s.more_colors_scope || this);
12774					return Event.cancel(e); // Cancel to fix onbeforeunload problem
12775				});
12776			}
12777
12778			DOM.addClass(m, 'mceColorSplitMenu');
12779
12780			// Prevent IE from scrolling and hindering click to occur #4019
12781			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12782
12783			Event.add(t.id + '_menu', 'click', function(e) {
12784				var c;
12785
12786				e = DOM.getParent(e.target, 'a', tb);
12787
12788				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12789					t.setColor(c);
12790
12791				return false; // Prevent IE auto save warning
12792			});
12793
12794			return w;
12795		},
12796
12797		setColor : function(c) {
12798			this.displayColor(c);
12799			this.hideMenu();
12800			this.settings.onselect(c);
12801		},
12802		
12803		displayColor : function(c) {
12804			var t = this;
12805
12806			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12807
12808			t.value = c;
12809		},
12810
12811		postRender : function() {
12812			var t = this, id = t.id;
12813
12814			t.parent();
12815			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12816			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12817		},
12818
12819		destroy : function() {
12820			var self = this;
12821
12822			self.parent();
12823
12824			Event.clear(self.id + '_menu');
12825			Event.clear(self.id + '_more');
12826			DOM.remove(self.id + '_menu');
12827
12828			if (self.keyboardNav) {
12829				self.keyboardNav.destroy();
12830			}
12831		}
12832	});
12833})(tinymce);
12834(function(tinymce) {
12835// Shorten class names
12836var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12837tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12838	renderHTML : function() {
12839		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12840
12841		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12842		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12843		h.push("<span role='application'>");
12844		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12845		each(controls, function(toolbar) {
12846			h.push(toolbar.renderHTML());
12847		});
12848		h.push("</span>");
12849		h.push('</div>');
12850
12851		return h.join('');
12852	},
12853	
12854	focus : function() {
12855		var t = this;
12856		dom.get(t.id).focus();
12857	},
12858	
12859	postRender : function() {
12860		var t = this, items = [];
12861
12862		each(t.controls, function(toolbar) {
12863			each (toolbar.controls, function(control) {
12864				if (control.id) {
12865					items.push(control);
12866				}
12867			});
12868		});
12869
12870		t.keyNav = new tinymce.ui.KeyboardNavigation({
12871			root: t.id,
12872			items: items,
12873			onCancel: function() {
12874				//Move focus if webkit so that navigation back will read the item.
12875				if (tinymce.isWebKit) {
12876					dom.get(t.editor.id+"_ifr").focus();
12877				}
12878				t.editor.focus();
12879			},
12880			excludeFromTabOrder: !t.settings.tab_focus_toolbar
12881		});
12882	},
12883	
12884	destroy : function() {
12885		var self = this;
12886
12887		self.parent();
12888		self.keyNav.destroy();
12889		Event.clear(self.id);
12890	}
12891});
12892})(tinymce);
12893(function(tinymce) {
12894// Shorten class names
12895var dom = tinymce.DOM, each = tinymce.each;
12896tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12897	renderHTML : function() {
12898		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12899
12900		cl = t.controls;
12901		for (i=0; i<cl.length; i++) {
12902			// Get current control, prev control, next control and if the control is a list box or not
12903			co = cl[i];
12904			pr = cl[i - 1];
12905			nx = cl[i + 1];
12906
12907			// Add toolbar start
12908			if (i === 0) {
12909				c = 'mceToolbarStart';
12910
12911				if (co.Button)
12912					c += ' mceToolbarStartButton';
12913				else if (co.SplitButton)
12914					c += ' mceToolbarStartSplitButton';
12915				else if (co.ListBox)
12916					c += ' mceToolbarStartListBox';
12917
12918				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12919			}
12920
12921			// Add toolbar end before list box and after the previous button
12922			// This is to fix the o2k7 editor skins
12923			if (pr && co.ListBox) {
12924				if (pr.Button || pr.SplitButton)
12925					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12926			}
12927
12928			// Render control HTML
12929
12930			// IE 8 quick fix, needed to propertly generate a hit area for anchors
12931			if (dom.stdMode)
12932				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12933			else
12934				h += '<td>' + co.renderHTML() + '</td>';
12935
12936			// Add toolbar start after list box and before the next button
12937			// This is to fix the o2k7 editor skins
12938			if (nx && co.ListBox) {
12939				if (nx.Button || nx.SplitButton)
12940					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12941			}
12942		}
12943
12944		c = 'mceToolbarEnd';
12945
12946		if (co.Button)
12947			c += ' mceToolbarEndButton';
12948		else if (co.SplitButton)
12949			c += ' mceToolbarEndSplitButton';
12950		else if (co.ListBox)
12951			c += ' mceToolbarEndListBox';
12952
12953		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12954
12955		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>');
12956	}
12957});
12958})(tinymce);
12959(function(tinymce) {
12960	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12961
12962	tinymce.create('tinymce.AddOnManager', {
12963		AddOnManager : function() {
12964			var self = this;
12965
12966			self.items = [];
12967			self.urls = {};
12968			self.lookup = {};
12969			self.onAdd = new Dispatcher(self);
12970		},
12971
12972		get : function(n) {
12973			if (this.lookup[n]) {
12974				return this.lookup[n].instance;
12975			} else {
12976				return undefined;
12977			}
12978		},
12979
12980		dependencies : function(n) {
12981			var result;
12982			if (this.lookup[n]) {
12983				result = this.lookup[n].dependencies;
12984			}
12985			return result || [];
12986		},
12987
12988		requireLangPack : function(n) {
12989			var s = tinymce.settings;
12990
12991			if (s && s.language && s.language_load !== false)
12992				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12993		},
12994
12995		add : function(id, o, dependencies) {
12996			this.items.push(o);
12997			this.lookup[id] = {instance:o, dependencies:dependencies};
12998			this.onAdd.dispatch(this, id, o);
12999
13000			return o;
13001		},
13002		createUrl: function(baseUrl, dep) {
13003			if (typeof dep === "object") {
13004				return dep
13005			} else {
13006				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
13007			}
13008		},
13009
13010		addComponents: function(pluginName, scripts) {
13011			var pluginUrl = this.urls[pluginName];
13012			tinymce.each(scripts, function(script){
13013				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
13014			});
13015		},
13016
13017		load : function(n, u, cb, s) {
13018			var t = this, url = u;
13019
13020			function loadDependencies() {
13021				var dependencies = t.dependencies(n);
13022				tinymce.each(dependencies, function(dep) {
13023					var newUrl = t.createUrl(u, dep);
13024					t.load(newUrl.resource, newUrl, undefined, undefined);
13025				});
13026				if (cb) {
13027					if (s) {
13028						cb.call(s);
13029					} else {
13030						cb.call(tinymce.ScriptLoader);
13031					}
13032				}
13033			}
13034
13035			if (t.urls[n])
13036				return;
13037			if (typeof u === "object")
13038				url = u.prefix + u.resource + u.suffix;
13039
13040			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
13041				url = tinymce.baseURL + '/' + url;
13042
13043			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
13044
13045			if (t.lookup[n]) {
13046				loadDependencies();
13047			} else {
13048				tinymce.ScriptLoader.add(url, loadDependencies, s);
13049			}
13050		}
13051	});
13052
13053	// Create plugin and theme managers
13054	tinymce.PluginManager = new tinymce.AddOnManager();
13055	tinymce.ThemeManager = new tinymce.AddOnManager();
13056}(tinymce));
13057
13058(function(tinymce) {
13059	// Shorten names
13060	var each = tinymce.each, extend = tinymce.extend,
13061		DOM = tinymce.DOM, Event = tinymce.dom.Event,
13062		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13063		explode = tinymce.explode,
13064		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
13065
13066	// Setup some URLs where the editor API is located and where the document is
13067	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
13068	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
13069		tinymce.documentBaseURL += '/';
13070
13071	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
13072
13073	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
13074
13075	// Add before unload listener
13076	// This was required since IE was leaking memory if you added and removed beforeunload listeners
13077	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
13078	tinymce.onBeforeUnload = new Dispatcher(tinymce);
13079
13080	// Must be on window or IE will leak if the editor is placed in frame or iframe
13081	Event.add(window, 'beforeunload', function(e) {
13082		tinymce.onBeforeUnload.dispatch(tinymce, e);
13083	});
13084
13085	tinymce.onAddEditor = new Dispatcher(tinymce);
13086
13087	tinymce.onRemoveEditor = new Dispatcher(tinymce);
13088
13089	tinymce.EditorManager = extend(tinymce, {
13090		editors : [],
13091
13092		i18n : {},
13093
13094		activeEditor : null,
13095
13096		init : function(s) {
13097			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
13098
13099			function createId(elm) {
13100				var id = elm.id;
13101	
13102				// Use element id, or unique name or generate a unique id
13103				if (!id) {
13104					id = elm.name;
13105	
13106					if (id && !DOM.get(id)) {
13107						id = elm.name;
13108					} else {
13109						// Generate unique name
13110						id = DOM.uniqueId();
13111					}
13112
13113					elm.setAttribute('id', id);
13114				}
13115
13116				return id;
13117			};
13118
13119			function execCallback(se, n, s) {
13120				var f = se[n];
13121
13122				if (!f)
13123					return;
13124
13125				if (tinymce.is(f, 'string')) {
13126					s = f.replace(/\.\w+$/, '');
13127					s = s ? tinymce.resolve(s) : 0;
13128					f = tinymce.resolve(f);
13129				}
13130
13131				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
13132			};
13133
13134			function hasClass(n, c) {
13135				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
13136			};
13137
13138			t.settings = s;
13139
13140			// Legacy call
13141			Event.bind(window, 'ready', function() {
13142				var l, co;
13143
13144				execCallback(s, 'onpageload');
13145
13146				switch (s.mode) {
13147					case "exact":
13148						l = s.elements || '';
13149
13150						if(l.length > 0) {
13151							each(explode(l), function(v) {
13152								if (DOM.get(v)) {
13153									ed = new tinymce.Editor(v, s);
13154									el.push(ed);
13155									ed.render(1);
13156								} else {
13157									each(document.forms, function(f) {
13158										each(f.elements, function(e) {
13159											if (e.name === v) {
13160												v = 'mce_editor_' + instanceCounter++;
13161												DOM.setAttrib(e, 'id', v);
13162
13163												ed = new tinymce.Editor(v, s);
13164												el.push(ed);
13165												ed.render(1);
13166											}
13167										});
13168									});
13169								}
13170							});
13171						}
13172						break;
13173
13174					case "textareas":
13175					case "specific_textareas":
13176						each(DOM.select('textarea'), function(elm) {
13177							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
13178								return;
13179
13180							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
13181								ed = new tinymce.Editor(createId(elm), s);
13182								el.push(ed);
13183								ed.render(1);
13184							}
13185						});
13186						break;
13187					
13188					default:
13189						if (s.types) {
13190							// Process type specific selector
13191							each(s.types, function(type) {
13192								each(DOM.select(type.selector), function(elm) {
13193									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
13194									el.push(editor);
13195									editor.render(1);
13196								});
13197							});
13198						} else if (s.selector) {
13199							// Process global selector
13200							each(DOM.select(s.selector), function(elm) {
13201								var editor = new tinymce.Editor(createId(elm), s);
13202								el.push(editor);
13203								editor.render(1);
13204							});
13205						}
13206				}
13207
13208				// Call onInit when all editors are initialized
13209				if (s.oninit) {
13210					l = co = 0;
13211
13212					each(el, function(ed) {
13213						co++;
13214
13215						if (!ed.initialized) {
13216							// Wait for it
13217							ed.onInit.add(function() {
13218								l++;
13219
13220								// All done
13221								if (l == co)
13222									execCallback(s, 'oninit');
13223							});
13224						} else
13225							l++;
13226
13227						// All done
13228						if (l == co)
13229							execCallback(s, 'oninit');					
13230					});
13231				}
13232			});
13233		},
13234
13235		get : function(id) {
13236			if (id === undef)
13237				return this.editors;
13238
13239			if (!this.editors.hasOwnProperty(id))
13240				return undef;
13241
13242			return this.editors[id];
13243		},
13244
13245		getInstanceById : function(id) {
13246			return this.get(id);
13247		},
13248
13249		add : function(editor) {
13250			var self = this, editors = self.editors;
13251
13252			// Add named and index editor instance
13253			editors[editor.id] = editor;
13254			editors.push(editor);
13255
13256			self._setActive(editor);
13257			self.onAddEditor.dispatch(self, editor);
13258
13259
13260			return editor;
13261		},
13262
13263		remove : function(editor) {
13264			var t = this, i, editors = t.editors;
13265
13266			// Not in the collection
13267			if (!editors[editor.id])
13268				return null;
13269
13270			delete editors[editor.id];
13271
13272			for (i = 0; i < editors.length; i++) {
13273				if (editors[i] == editor) {
13274					editors.splice(i, 1);
13275					break;
13276				}
13277			}
13278
13279			// Select another editor since the active one was removed
13280			if (t.activeEditor == editor)
13281				t._setActive(editors[0]);
13282
13283			editor.destroy();
13284			t.onRemoveEditor.dispatch(t, editor);
13285
13286			return editor;
13287		},
13288
13289		execCommand : function(c, u, v) {
13290			var t = this, ed = t.get(v), w;
13291
13292			function clr() {
13293				ed.destroy();
13294				w.detachEvent('onunload', clr);
13295				w = w.tinyMCE = w.tinymce = null; // IE leak
13296			};
13297
13298			// Manager commands
13299			switch (c) {
13300				case "mceFocus":
13301					ed.focus();
13302					return true;
13303
13304				case "mceAddEditor":
13305				case "mceAddControl":
13306					if (!t.get(v))
13307						new tinymce.Editor(v, t.settings).render();
13308
13309					return true;
13310
13311				case "mceAddFrameControl":
13312					w = v.window;
13313
13314					// Add tinyMCE global instance and tinymce namespace to specified window
13315					w.tinyMCE = tinyMCE;
13316					w.tinymce = tinymce;
13317
13318					tinymce.DOM.doc = w.document;
13319					tinymce.DOM.win = w;
13320
13321					ed = new tinymce.Editor(v.element_id, v);
13322					ed.render();
13323
13324					// Fix IE memory leaks
13325					if (tinymce.isIE && ! tinymce.isIE11) {
13326						w.attachEvent('onunload', clr);
13327					}
13328
13329					v.page_window = null;
13330
13331					return true;
13332
13333				case "mceRemoveEditor":
13334				case "mceRemoveControl":
13335					if (ed)
13336						ed.remove();
13337
13338					return true;
13339
13340				case 'mceToggleEditor':
13341					if (!ed) {
13342						t.execCommand('mceAddControl', 0, v);
13343						return true;
13344					}
13345
13346					if (ed.isHidden())
13347						ed.show();
13348					else
13349						ed.hide();
13350
13351					return true;
13352			}
13353
13354			// Run command on active editor
13355			if (t.activeEditor)
13356				return t.activeEditor.execCommand(c, u, v);
13357
13358			return false;
13359		},
13360
13361		execInstanceCommand : function(id, c, u, v) {
13362			var ed = this.get(id);
13363
13364			if (ed)
13365				return ed.execCommand(c, u, v);
13366
13367			return false;
13368		},
13369
13370		triggerSave : function() {
13371			each(this.editors, function(e) {
13372				e.save();
13373			});
13374		},
13375
13376		addI18n : function(p, o) {
13377			var lo, i18n = this.i18n;
13378
13379			if (!tinymce.is(p, 'string')) {
13380				each(p, function(o, lc) {
13381					each(o, function(o, g) {
13382						each(o, function(o, k) {
13383							if (g === 'common')
13384								i18n[lc + '.' + k] = o;
13385							else
13386								i18n[lc + '.' + g + '.' + k] = o;
13387						});
13388					});
13389				});
13390			} else {
13391				each(o, function(o, k) {
13392					i18n[p + '.' + k] = o;
13393				});
13394			}
13395		},
13396
13397		// Private methods
13398
13399		_setActive : function(editor) {
13400			this.selectedInstance = this.activeEditor = editor;
13401		}
13402	});
13403})(tinymce);
13404
13405(function(tinymce) {
13406	// Shorten these names
13407	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13408		each = tinymce.each, isGecko = tinymce.isGecko,
13409		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13410		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13411		explode = tinymce.explode;
13412
13413	tinymce.create('tinymce.Editor', {
13414		Editor : function(id, settings) {
13415			var self = this, TRUE = true;
13416
13417			self.settings = settings = extend({
13418				id : id,
13419				language : 'en',
13420				theme : 'advanced',
13421				skin : 'default',
13422				delta_width : 0,
13423				delta_height : 0,
13424				popup_css : '',
13425				plugins : '',
13426				document_base_url : tinymce.documentBaseURL,
13427				add_form_submit_trigger : TRUE,
13428				submit_patch : TRUE,
13429				add_unload_trigger : TRUE,
13430				convert_urls : TRUE,
13431				relative_urls : TRUE,
13432				remove_script_host : TRUE,
13433				table_inline_editing : false,
13434				object_resizing : TRUE,
13435				accessibility_focus : TRUE,
13436				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13437				visual : TRUE,
13438				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13439				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
13440				apply_source_formatting : TRUE,
13441				directionality : 'ltr',
13442				forced_root_block : 'p',
13443				hidden_input : TRUE,
13444				padd_empty_editor : TRUE,
13445				render_ui : TRUE,
13446				indentation : '30px',
13447				fix_table_elements : TRUE,
13448				inline_styles : TRUE,
13449				convert_fonts_to_spans : TRUE,
13450				indent : 'simple',
13451				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,figure,option,optgroup,datalist',
13452				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,figure,option,optgroup,datalist',
13453				validate : TRUE,
13454				entity_encoding : 'named',
13455				url_converter : self.convertURL,
13456				url_converter_scope : self,
13457				ie7_compat : TRUE
13458			}, settings);
13459
13460			self.id = self.editorId = id;
13461
13462			self.isNotDirty = false;
13463
13464			self.plugins = {};
13465
13466			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13467				base_uri : tinyMCE.baseURI
13468			});
13469
13470			self.baseURI = tinymce.baseURI;
13471
13472			self.contentCSS = [];
13473
13474			self.contentStyles = [];
13475
13476			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13477			self.setupEvents();
13478
13479			// Internal command handler objects
13480			self.execCommands = {};
13481			self.queryStateCommands = {};
13482			self.queryValueCommands = {};
13483
13484			// Call setup
13485			self.execCallback('setup', self);
13486		},
13487
13488		render : function(nst) {
13489			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13490
13491			// Page is not loaded yet, wait for it
13492			if (!Event.domLoaded) {
13493				Event.add(window, 'ready', function() {
13494					t.render();
13495				});
13496				return;
13497			}
13498
13499			tinyMCE.settings = s;
13500
13501			// Element not found, then skip initialization
13502			if (!t.getElement())
13503				return;
13504
13505			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13506			// here since the browser says it has contentEditable support but there is no visible caret.
13507			if (tinymce.isIDevice && !tinymce.isIOS5)
13508				return;
13509
13510			// Add hidden input for non input elements inside form elements
13511			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13512				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13513
13514			// Hide target element early to prevent content flashing
13515			if (!s.content_editable) {
13516				t.orgVisibility = t.getElement().style.visibility;
13517				t.getElement().style.visibility = 'hidden';
13518			}
13519
13520			if (tinymce.WindowManager)
13521				t.windowManager = new tinymce.WindowManager(t);
13522
13523			if (s.encoding == 'xml') {
13524				t.onGetContent.add(function(ed, o) {
13525					if (o.save)
13526						o.content = DOM.encode(o.content);
13527				});
13528			}
13529
13530			if (s.add_form_submit_trigger) {
13531				t.onSubmit.addToTop(function() {
13532					if (t.initialized) {
13533						t.save();
13534						t.isNotDirty = 1;
13535					}
13536				});
13537			}
13538
13539			if (s.add_unload_trigger) {
13540				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13541					if (t.initialized && !t.destroyed && !t.isHidden())
13542						t.save({format : 'raw', no_events : true});
13543				});
13544			}
13545
13546			tinymce.addUnload(t.destroy, t);
13547
13548			if (s.submit_patch) {
13549				t.onBeforeRenderUI.add(function() {
13550					var n = t.getElement().form;
13551
13552					if (!n)
13553						return;
13554
13555					// Already patched
13556					if (n._mceOldSubmit)
13557						return;
13558
13559					// Check page uses id="submit" or name="submit" for it's submit button
13560					if (!n.submit.nodeType && !n.submit.length) {
13561						t.formElement = n;
13562						n._mceOldSubmit = n.submit;
13563						n.submit = function() {
13564							// Save all instances
13565							tinymce.triggerSave();
13566							t.isNotDirty = 1;
13567
13568							return t.formElement._mceOldSubmit(t.formElement);
13569						};
13570					}
13571
13572					n = null;
13573				});
13574			}
13575
13576			// Load scripts
13577			function loadScripts() {
13578				if (s.language && s.language_load !== false)
13579					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13580
13581				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13582					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13583
13584				each(explode(s.plugins), function(p) {
13585					if (p &&!PluginManager.urls[p]) {
13586						if (p.charAt(0) == '-') {
13587							p = p.substr(1, p.length);
13588							var dependencies = PluginManager.dependencies(p);
13589							each(dependencies, function(dep) {
13590								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13591								dep = PluginManager.createUrl(defaultSettings, dep);
13592								PluginManager.load(dep.resource, dep);
13593							});
13594						} else {
13595							// Skip safari plugin, since it is removed as of 3.3b1
13596							if (p == 'safari') {
13597								return;
13598							}
13599							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13600						}
13601					}
13602				});
13603
13604				// Init when que is loaded
13605				sl.loadQueue(function() {
13606					if (!t.removed)
13607						t.init();
13608				});
13609			};
13610
13611			loadScripts();
13612		},
13613
13614		init : function() {
13615			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13616
13617			tinymce.add(t);
13618
13619			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13620
13621			if (s.theme) {
13622				if (typeof s.theme != "function") {
13623					s.theme = s.theme.replace(/-/, '');
13624					o = ThemeManager.get(s.theme);
13625					t.theme = new o();
13626
13627					if (t.theme.init)
13628						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13629				} else {
13630					t.theme = s.theme;
13631				}
13632			}
13633
13634			function initPlugin(p) {
13635				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13636				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13637					each(PluginManager.dependencies(p), function(dep){
13638						initPlugin(dep);
13639					});
13640					po = new c(t, u);
13641
13642					t.plugins[p] = po;
13643
13644					if (po.init) {
13645						po.init(t, u);
13646						initializedPlugins.push(p);
13647					}
13648				}
13649			}
13650			
13651			// Create all plugins
13652			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13653
13654			// Setup popup CSS path(s)
13655			if (s.popup_css !== false) {
13656				if (s.popup_css)
13657					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13658				else
13659					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13660			}
13661
13662			if (s.popup_css_add)
13663				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13664
13665			t.controlManager = new tinymce.ControlManager(t);
13666
13667			// Enables users to override the control factory
13668			t.onBeforeRenderUI.dispatch(t, t.controlManager);
13669
13670			// Measure box
13671			if (s.render_ui && t.theme) {
13672				t.orgDisplay = e.style.display;
13673
13674				if (typeof s.theme != "function") {
13675					w = s.width || e.style.width || e.offsetWidth;
13676					h = s.height || e.style.height || e.offsetHeight;
13677					mh = s.min_height || 100;
13678					re = /^[0-9\.]+(|px)$/i;
13679
13680					if (re.test('' + w))
13681						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13682
13683					if (re.test('' + h))
13684						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13685
13686					// Render UI
13687					o = t.theme.renderUI({
13688						targetNode : e,
13689						width : w,
13690						height : h,
13691						deltaWidth : s.delta_width,
13692						deltaHeight : s.delta_height
13693					});
13694
13695					// Resize editor
13696					DOM.setStyles(o.sizeContainer || o.editorContainer, {
13697						width : w,
13698						height : h
13699					});
13700
13701					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13702					if (h < mh)
13703						h = mh;
13704				} else {
13705					o = s.theme(t, e);
13706
13707					// Convert element type to id:s
13708					if (o.editorContainer.nodeType) {
13709						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13710					}
13711
13712					// Convert element type to id:s
13713					if (o.iframeContainer.nodeType) {
13714						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13715					}
13716
13717					// Use specified iframe height or the targets offsetHeight
13718					h = o.iframeHeight || e.offsetHeight;
13719
13720					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
13721					if (isIE) {
13722						t.onInit.add(function(ed) {
13723							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() {
13724								ed.bookmark = ed.selection.getBookmark(1);
13725							});
13726						});
13727
13728						t.onNodeChange.add(function(ed) {
13729							if (document.activeElement.id == ed.id + "_ifr") {
13730								ed.bookmark = ed.selection.getBookmark(1);
13731							}
13732						});
13733					}
13734				}
13735
13736				t.editorContainer = o.editorContainer;
13737			}
13738
13739			// Load specified content CSS last
13740			if (s.content_css) {
13741				each(explode(s.content_css), function(u) {
13742					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13743				});
13744			}
13745
13746			// Load specified content CSS last
13747			if (s.content_style) {
13748				t.contentStyles.push(s.content_style);
13749			}
13750
13751			// Content editable mode ends here
13752			if (s.content_editable) {
13753				e = n = o = null; // Fix IE leak
13754				return t.initContentBody();
13755			}
13756
13757			// User specified a document.domain value
13758			if (document.domain && location.hostname != document.domain)
13759				tinymce.relaxedDomain = document.domain;
13760
13761			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13762
13763			// We only need to override paths if we have to
13764			// IE has a bug where it remove site absolute urls to relative ones if this is specified
13765			if (s.document_base_url != tinymce.documentBaseURL)
13766				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13767
13768			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13769			if (tinymce.isIE8) {
13770				if (s.ie7_compat)
13771					t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13772				else
13773					t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13774			}
13775
13776			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13777
13778			// Load the CSS by injecting them into the HTML this will reduce "flicker"
13779			for (i = 0; i < t.contentCSS.length; i++) {
13780				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13781			}
13782
13783			t.contentCSS = [];
13784
13785			bi = s.body_id || 'tinymce';
13786			if (bi.indexOf('=') != -1) {
13787				bi = t.getParam('body_id', '', 'hash');
13788				bi = bi[t.id] || bi;
13789			}
13790
13791			bc = s.body_class || '';
13792			if (bc.indexOf('=') != -1) {
13793				bc = t.getParam('body_class', '', 'hash');
13794				bc = bc[t.id] || '';
13795			}
13796
13797			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13798
13799			// Domain relaxing enabled, then set document domain
13800			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13801				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
13802				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13803			}
13804
13805			// Create iframe
13806			// TODO: ACC add the appropriate description on this.
13807			n = DOM.add(o.iframeContainer, 'iframe', { 
13808				id : t.id + "_ifr",
13809				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13810				frameBorder : '0',
13811				allowTransparency : "true",
13812				title : s.aria_label,
13813				style : {
13814					width : '100%',
13815					height : h,
13816					display : 'block' // Important for Gecko to render the iframe correctly
13817				}
13818			});
13819
13820			t.contentAreaContainer = o.iframeContainer;
13821
13822			if (o.editorContainer) {
13823				DOM.get(o.editorContainer).style.display = t.orgDisplay;
13824			}
13825
13826			// Restore visibility on target element
13827			e.style.visibility = t.orgVisibility;
13828
13829			DOM.get(t.id).style.display = 'none';
13830			DOM.setAttrib(t.id, 'aria-hidden', true);
13831
13832			if (!tinymce.relaxedDomain || !u)
13833				t.initContentBody();
13834
13835			e = n = o = null; // Cleanup
13836		},
13837
13838		initContentBody : function() {
13839			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13840
13841			// Setup iframe body
13842			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13843				doc.open();
13844				doc.write(self.iframeHTML);
13845				doc.close();
13846
13847				if (tinymce.relaxedDomain)
13848					doc.domain = tinymce.relaxedDomain;
13849			}
13850
13851			if (settings.content_editable) {
13852				DOM.addClass(targetElm, 'mceContentBody');
13853				self.contentDocument = doc = settings.content_document || document;
13854				self.contentWindow = settings.content_window || window;
13855				self.bodyElement = targetElm;
13856
13857				// Prevent leak in IE
13858				settings.content_document = settings.content_window = null;
13859			}
13860
13861			// It will not steal focus while setting contentEditable
13862			body = self.getBody();
13863			body.disabled = true;
13864
13865			if (!settings.readonly)
13866				body.contentEditable = self.getParam('content_editable_state', true);
13867
13868			body.disabled = false;
13869
13870			self.schema = new tinymce.html.Schema(settings);
13871
13872			self.dom = new tinymce.dom.DOMUtils(doc, {
13873				keep_values : true,
13874				url_converter : self.convertURL,
13875				url_converter_scope : self,
13876				hex_colors : settings.force_hex_style_colors,
13877				class_filter : settings.class_filter,
13878				update_styles : true,
13879				root_element : settings.content_editable ? self.id : null,
13880				schema : self.schema
13881			});
13882
13883			self.parser = new tinymce.html.DomParser(settings, self.schema);
13884
13885			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
13886			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13887				var i = nodes.length, node, dom = self.dom, value, internalName;
13888
13889				while (i--) {
13890					node = nodes[i];
13891					value = node.attr(name);
13892					internalName = 'data-mce-' + name;
13893
13894					// Add internal attribute if we need to we don't on a refresh of the document
13895					if (!node.attributes.map[internalName]) {	
13896						if (name === "style")
13897							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13898						else
13899							node.attr(internalName, self.convertURL(value, name, node.name));
13900					}
13901				}
13902			});
13903
13904			// Keep scripts from executing
13905			self.parser.addNodeFilter('script', function(nodes, name) {
13906				var i = nodes.length, node;
13907
13908				while (i--) {
13909					node = nodes[i];
13910					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13911				}
13912			});
13913
13914			self.parser.addNodeFilter('#cdata', function(nodes, name) {
13915				var i = nodes.length, node;
13916
13917				while (i--) {
13918					node = nodes[i];
13919					node.type = 8;
13920					node.name = '#comment';
13921					node.value = '[CDATA[' + node.value + ']]';
13922				}
13923			});
13924
13925			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13926				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13927
13928				while (i--) {
13929					node = nodes[i];
13930
13931					if (node.isEmpty(nonEmptyElements))
13932						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13933				}
13934			});
13935
13936			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13937
13938			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13939
13940			self.formatter = new tinymce.Formatter(self);
13941
13942			self.undoManager = new tinymce.UndoManager(self);
13943
13944			self.forceBlocks = new tinymce.ForceBlocks(self);
13945			self.enterKey = new tinymce.EnterKey(self);
13946			self.editorCommands = new tinymce.EditorCommands(self);
13947
13948			self.onExecCommand.add(function(editor, command) {
13949				// Don't refresh the select lists until caret move
13950				if (!/^(FontName|FontSize)$/.test(command))
13951					self.nodeChanged();
13952			});
13953
13954			// Pass through
13955			self.serializer.onPreProcess.add(function(se, o) {
13956				return self.onPreProcess.dispatch(self, o, se);
13957			});
13958
13959			self.serializer.onPostProcess.add(function(se, o) {
13960				return self.onPostProcess.dispatch(self, o, se);
13961			});
13962
13963			self.onPreInit.dispatch(self);
13964
13965			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13966				doc.body.spellcheck = false;
13967
13968			if (!settings.readonly) {
13969				self.bindNativeEvents();
13970			}
13971
13972			self.controlManager.onPostRender.dispatch(self, self.controlManager);
13973			self.onPostRender.dispatch(self);
13974
13975			self.quirks = tinymce.util.Quirks(self);
13976
13977			if (settings.directionality)
13978				body.dir = settings.directionality;
13979
13980			if (settings.nowrap)
13981				body.style.whiteSpace = "nowrap";
13982
13983			if (settings.protect) {
13984				self.onBeforeSetContent.add(function(ed, o) {
13985					each(settings.protect, function(pattern) {
13986						o.content = o.content.replace(pattern, function(str) {
13987							return '<!--mce:protected ' + escape(str) + '-->';
13988						});
13989					});
13990				});
13991			}
13992
13993			// Add visual aids when new contents is added
13994			self.onSetContent.add(function() {
13995				self.addVisual(self.getBody());
13996			});
13997
13998			// Remove empty contents
13999			if (settings.padd_empty_editor) {
14000				self.onPostProcess.add(function(ed, o) {
14001					o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
14002				});
14003			}
14004
14005			self.load({initial : true, format : 'html'});
14006			self.startContent = self.getContent({format : 'raw'});
14007
14008			self.initialized = true;
14009
14010			self.onInit.dispatch(self);
14011			self.execCallback('setupcontent_callback', self.id, body, doc);
14012			self.execCallback('init_instance_callback', self);
14013			self.focus(true);
14014			self.nodeChanged({initial : true});
14015
14016			// Add editor specific CSS styles
14017			if (self.contentStyles.length > 0) {
14018				contentCssText = '';
14019
14020				each(self.contentStyles, function(style) {
14021					contentCssText += style + "\r\n";
14022				});
14023
14024				self.dom.addStyle(contentCssText);
14025			}
14026
14027			// Load specified content CSS last
14028			each(self.contentCSS, function(url) {
14029				self.dom.loadCSS(url);
14030			});
14031
14032			// Handle auto focus
14033			if (settings.auto_focus) {
14034				setTimeout(function () {
14035					var ed = tinymce.get(settings.auto_focus);
14036
14037					ed.selection.select(ed.getBody(), 1);
14038					ed.selection.collapse(1);
14039					ed.getBody().focus();
14040					ed.getWin().focus();
14041				}, 100);
14042			}
14043
14044			// Clean up references for IE
14045			targetElm = doc = body = null;
14046		},
14047
14048		focus : function(skip_focus) {
14049			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
14050
14051			if (!skip_focus) {
14052				if (self.bookmark) {
14053					selection.moveToBookmark(self.bookmark);
14054					self.bookmark = null;
14055				}
14056
14057				// Get selected control element
14058				ieRng = selection.getRng();
14059				if (ieRng.item) {
14060					controlElm = ieRng.item(0);
14061				}
14062
14063				self._refreshContentEditable();
14064
14065				// Focus the window iframe
14066				if (!contentEditable) {
14067					self.getWin().focus();
14068				}
14069
14070				// Focus the body as well since it's contentEditable
14071				if (tinymce.isGecko || contentEditable) {
14072					body = self.getBody();
14073
14074					// Check for setActive since it doesn't scroll to the element
14075					if (body.setActive && ! tinymce.isIE11) {
14076						body.setActive();
14077					} else {
14078						body.focus();
14079					}
14080
14081					if (contentEditable) {
14082						selection.normalize();
14083					}
14084				}
14085
14086				// Restore selected control element
14087				// This is needed when for example an image is selected within a
14088				// layer a call to focus will then remove the control selection
14089				if (controlElm && controlElm.ownerDocument == doc) {
14090					ieRng = doc.body.createControlRange();
14091					ieRng.addElement(controlElm);
14092					ieRng.select();
14093				}
14094			}
14095
14096			if (tinymce.activeEditor != self) {
14097				if ((oed = tinymce.activeEditor) != null)
14098					oed.onDeactivate.dispatch(oed, self);
14099
14100				self.onActivate.dispatch(self, oed);
14101			}
14102
14103			tinymce._setActive(self);
14104		},
14105
14106		execCallback : function(n) {
14107			var t = this, f = t.settings[n], s;
14108
14109			if (!f)
14110				return;
14111
14112			// Look through lookup
14113			if (t.callbackLookup && (s = t.callbackLookup[n])) {
14114				f = s.func;
14115				s = s.scope;
14116			}
14117
14118			if (is(f, 'string')) {
14119				s = f.replace(/\.\w+$/, '');
14120				s = s ? tinymce.resolve(s) : 0;
14121				f = tinymce.resolve(f);
14122				t.callbackLookup = t.callbackLookup || {};
14123				t.callbackLookup[n] = {func : f, scope : s};
14124			}
14125
14126			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
14127		},
14128
14129		translate : function(s) {
14130			var c = this.settings.language || 'en', i18n = tinymce.i18n;
14131
14132			if (!s)
14133				return '';
14134
14135			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
14136				return i18n[c + '.' + b] || '{#' + b + '}';
14137			});
14138		},
14139
14140		getLang : function(n, dv) {
14141			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
14142		},
14143
14144		getParam : function(n, dv, ty) {
14145			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
14146
14147			if (ty === 'hash') {
14148				o = {};
14149
14150				if (is(v, 'string')) {
14151					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
14152						v = v.split('=');
14153
14154						if (v.length > 1)
14155							o[tr(v[0])] = tr(v[1]);
14156						else
14157							o[tr(v[0])] = tr(v);
14158					});
14159				} else
14160					o = v;
14161
14162				return o;
14163			}
14164
14165			return v;
14166		},
14167
14168		nodeChanged : function(o) {
14169			var self = this, selection = self.selection, node;
14170
14171			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
14172			if (self.initialized) {
14173				o = o || {};
14174
14175				// Get start node
14176				node = selection.getStart() || self.getBody();
14177				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
14178
14179				// Get parents and add them to object
14180				o.parents = [];
14181				self.dom.getParent(node, function(node) {
14182					if (node.nodeName == 'BODY')
14183						return true;
14184
14185					o.parents.push(node);
14186				});
14187
14188				self.onNodeChange.dispatch(
14189					self,
14190					o ? o.controlManager || self.controlManager : self.controlManager,
14191					node,
14192					selection.isCollapsed(),
14193					o
14194				);
14195			}
14196		},
14197
14198		addButton : function(name, settings) {
14199			var self = this;
14200
14201			self.buttons = self.buttons || {};
14202			self.buttons[name] = settings;
14203		},
14204
14205		addCommand : function(name, callback, scope) {
14206			this.execCommands[name] = {func : callback, scope : scope || this};
14207		},
14208
14209		addQueryStateHandler : function(name, callback, scope) {
14210			this.queryStateCommands[name] = {func : callback, scope : scope || this};
14211		},
14212
14213		addQueryValueHandler : function(name, callback, scope) {
14214			this.queryValueCommands[name] = {func : callback, scope : scope || this};
14215		},
14216
14217		addShortcut : function(pa, desc, cmd_func, sc) {
14218			var t = this, c;
14219
14220			if (t.settings.custom_shortcuts === false)
14221				return false;
14222
14223			t.shortcuts = t.shortcuts || {};
14224
14225			if (is(cmd_func, 'string')) {
14226				c = cmd_func;
14227
14228				cmd_func = function() {
14229					t.execCommand(c, false, null);
14230				};
14231			}
14232
14233			if (is(cmd_func, 'object')) {
14234				c = cmd_func;
14235
14236				cmd_func = function() {
14237					t.execCommand(c[0], c[1], c[2]);
14238				};
14239			}
14240
14241			each(explode(pa), function(pa) {
14242				var o = {
14243					func : cmd_func,
14244					scope : sc || this,
14245					desc : t.translate(desc),
14246					alt : false,
14247					ctrl : false,
14248					shift : false
14249				};
14250
14251				each(explode(pa, '+'), function(v) {
14252					switch (v) {
14253						case 'alt':
14254						case 'ctrl':
14255						case 'shift':
14256							o[v] = true;
14257							break;
14258
14259						default:
14260							o.charCode = v.charCodeAt(0);
14261							o.keyCode = v.toUpperCase().charCodeAt(0);
14262					}
14263				});
14264
14265				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
14266			});
14267
14268			return true;
14269		},
14270
14271		execCommand : function(cmd, ui, val, a) {
14272			var t = this, s = 0, o, st;
14273
14274			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
14275				t.focus();
14276
14277			a = extend({}, a);
14278			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
14279			if (a.terminate)
14280				return false;
14281
14282			// Command callback
14283			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
14284				t.onExecCommand.dispatch(t, cmd, ui, val, a);
14285				return true;
14286			}
14287
14288			// Registred commands
14289			if (o = t.execCommands[cmd]) {
14290				st = o.func.call(o.scope, ui, val);
14291
14292				// Fall through on true
14293				if (st !== true) {
14294					t.onExecCommand.dispatch(t, cmd, ui, val, a);
14295					return st;
14296				}
14297			}
14298
14299			// Plugin commands
14300			each(t.plugins, function(p) {
14301				if (p.execCommand && p.execCommand(cmd, ui, val)) {
14302					t.onExecCommand.dispatch(t, cmd, ui, val, a);
14303					s = 1;
14304					return false;
14305				}
14306			});
14307
14308			if (s)
14309				return true;
14310
14311			// Theme commands
14312			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
14313				t.onExecCommand.dispatch(t, cmd, ui, val, a);
14314				return true;
14315			}
14316
14317			// Editor commands
14318			if (t.editorCommands.execCommand(cmd, ui, val)) {
14319				t.onExecCommand.dispatch(t, cmd, ui, val, a);
14320				return true;
14321			}
14322
14323			// Browser commands
14324			t.getDoc().execCommand(cmd, ui, val);
14325			t.onExecCommand.dispatch(t, cmd, ui, val, a);
14326		},
14327
14328		queryCommandState : function(cmd) {
14329			var t = this, o, s;
14330
14331			// Is hidden then return undefined
14332			if (t._isHidden())
14333				return;
14334
14335			// Registred commands
14336			if (o = t.queryStateCommands[cmd]) {
14337				s = o.func.call(o.scope);
14338
14339				// Fall though on true
14340				if (s !== true)
14341					return s;
14342			}
14343
14344			// Registred commands
14345			o = t.editorCommands.queryCommandState(cmd);
14346			if (o !== -1)
14347				return o;
14348
14349			// Browser commands
14350			try {
14351				return this.getDoc().queryCommandState(cmd);
14352			} catch (ex) {
14353				// Fails sometimes see bug: 1896577
14354			}
14355		},
14356
14357		queryCommandValue : function(c) {
14358			var t = this, o, s;
14359
14360			// Is hidden then return undefined
14361			if (t._isHidden())
14362				return;
14363
14364			// Registred commands
14365			if (o = t.queryValueCommands[c]) {
14366				s = o.func.call(o.scope);
14367
14368				// Fall though on true
14369				if (s !== true)
14370					return s;
14371			}
14372
14373			// Registred commands
14374			o = t.editorCommands.queryCommandValue(c);
14375			if (is(o))
14376				return o;
14377
14378			// Browser commands
14379			try {
14380				return this.getDoc().queryCommandValue(c);
14381			} catch (ex) {
14382				// Fails sometimes see bug: 1896577
14383			}
14384		},
14385
14386		show : function() {
14387			var self = this;
14388
14389			DOM.show(self.getContainer());
14390			DOM.hide(self.id);
14391			self.load();
14392		},
14393
14394		hide : function() {
14395			var self = this, doc = self.getDoc();
14396
14397			// Fixed bug where IE has a blinking cursor left from the editor
14398			if (isIE && doc)
14399				doc.execCommand('SelectAll');
14400
14401			// We must save before we hide so Safari doesn't crash
14402			self.save();
14403
14404			// defer the call to hide to prevent an IE9 crash #4921
14405			DOM.hide(self.getContainer());
14406			DOM.setStyle(self.id, 'display', self.orgDisplay);
14407		},
14408
14409		isHidden : function() {
14410			return !DOM.isHidden(this.id);
14411		},
14412
14413		setProgressState : function(b, ti, o) {
14414			this.onSetProgressState.dispatch(this, b, ti, o);
14415
14416			return b;
14417		},
14418
14419		load : function(o) {
14420			var t = this, e = t.getElement(), h;
14421
14422			if (e) {
14423				o = o || {};
14424				o.load = true;
14425
14426				// Double encode existing entities in the value
14427				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14428				o.element = e;
14429
14430				if (!o.no_events)
14431					t.onLoadContent.dispatch(t, o);
14432
14433				o.element = e = null;
14434
14435				return h;
14436			}
14437		},
14438
14439		save : function(o) {
14440			var t = this, e = t.getElement(), h, f;
14441
14442			if (!e || !t.initialized)
14443				return;
14444
14445			o = o || {};
14446			o.save = true;
14447
14448			o.element = e;
14449			h = o.content = t.getContent(o);
14450
14451			if (!o.no_events)
14452				t.onSaveContent.dispatch(t, o);
14453
14454			h = o.content;
14455
14456			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14457				e.innerHTML = h;
14458
14459				// Update hidden form element
14460				if (f = DOM.getParent(t.id, 'form')) {
14461					each(f.elements, function(e) {
14462						if (e.name == t.id) {
14463							e.value = h;
14464							return false;
14465						}
14466					});
14467				}
14468			} else
14469				e.value = h;
14470
14471			o.element = e = null;
14472
14473			return h;
14474		},
14475
14476		setContent : function(content, args) {
14477			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14478
14479			// Setup args object
14480			args = args || {};
14481			args.format = args.format || 'html';
14482			args.set = true;
14483			args.content = content;
14484
14485			// Do preprocessing
14486			if (!args.no_events)
14487				self.onBeforeSetContent.dispatch(self, args);
14488
14489			content = args.content;
14490
14491			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14492			// It will also be impossible to place the caret in the editor unless there is a BR element present
14493			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14494				forcedRootBlockName = self.settings.forced_root_block;
14495				if (forcedRootBlockName)
14496					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14497				else
14498					content = '<br data-mce-bogus="1">';
14499
14500				body.innerHTML = content;
14501				self.selection.select(body, true);
14502				self.selection.collapse(true);
14503				return;
14504			}
14505
14506			// Parse and serialize the html
14507			if (args.format !== 'raw') {
14508				content = new tinymce.html.Serializer({}, self.schema).serialize(
14509					self.parser.parse(content)
14510				);
14511			}
14512
14513			// Set the new cleaned contents to the editor
14514			args.content = tinymce.trim(content);
14515			self.dom.setHTML(body, args.content);
14516
14517			// Do post processing
14518			if (!args.no_events)
14519				self.onSetContent.dispatch(self, args);
14520
14521			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14522			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14523				self.selection.normalize();
14524			}
14525
14526			return args.content;
14527		},
14528
14529		getContent : function(args) {
14530			var self = this, content, body = self.getBody();
14531
14532			// Setup args object
14533			args = args || {};
14534			args.format = args.format || 'html';
14535			args.get = true;
14536			args.getInner = true;
14537
14538			// Do preprocessing
14539			if (!args.no_events)
14540				self.onBeforeGetContent.dispatch(self, args);
14541
14542			// Get raw contents or by default the cleaned contents
14543			if (args.format == 'raw')
14544				content = body.innerHTML;
14545			else if (args.format == 'text')
14546				content = body.innerText || body.textContent;
14547			else
14548				content = self.serializer.serialize(body, args);
14549
14550			// Trim whitespace in beginning/end of HTML
14551			if (args.format != 'text') {
14552				args.content = tinymce.trim(content);
14553			} else {
14554				args.content = content;
14555			}
14556
14557			// Do post processing
14558			if (!args.no_events)
14559				self.onGetContent.dispatch(self, args);
14560
14561			return args.content;
14562		},
14563
14564		isDirty : function() {
14565			var self = this;
14566
14567			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14568		},
14569
14570		getContainer : function() {
14571			var self = this;
14572
14573			if (!self.container)
14574				self.container = DOM.get(self.editorContainer || self.id + '_parent');
14575
14576			return self.container;
14577		},
14578
14579		getContentAreaContainer : function() {
14580			return this.contentAreaContainer;
14581		},
14582
14583		getElement : function() {
14584			return DOM.get(this.settings.content_element || this.id);
14585		},
14586
14587		getWin : function() {
14588			var self = this, elm;
14589
14590			if (!self.contentWindow) {
14591				elm = DOM.get(self.id + "_ifr");
14592
14593				if (elm)
14594					self.contentWindow = elm.contentWindow;
14595			}
14596
14597			return self.contentWindow;
14598		},
14599
14600		getDoc : function() {
14601			var self = this, win;
14602
14603			if (!self.contentDocument) {
14604				win = self.getWin();
14605
14606				if (win)
14607					self.contentDocument = win.document;
14608			}
14609
14610			return self.contentDocument;
14611		},
14612
14613		getBody : function() {
14614			return this.bodyElement || this.getDoc().body;
14615		},
14616
14617		convertURL : function(url, name, elm) {
14618			var self = this, settings = self.settings;
14619
14620			// Use callback instead
14621			if (settings.urlconverter_callback)
14622				return self.execCallback('urlconverter_callback', url, elm, true, name);
14623
14624			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14625			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14626				return url;
14627
14628			// Convert to relative
14629			if (settings.relative_urls)
14630				return self.documentBaseURI.toRelative(url);
14631
14632			// Convert to absolute
14633			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14634
14635			return url;
14636		},
14637
14638		addVisual : function(elm) {
14639			var self = this, settings = self.settings, dom = self.dom, cls;
14640
14641			elm = elm || self.getBody();
14642
14643			if (!is(self.hasVisual))
14644				self.hasVisual = settings.visual;
14645
14646			each(dom.select('table,a', elm), function(elm) {
14647				var value;
14648
14649				switch (elm.nodeName) {
14650					case 'TABLE':
14651						cls = settings.visual_table_class || 'mceItemTable';
14652						value = dom.getAttrib(elm, 'border');
14653
14654						if (!value || value == '0') {
14655							if (self.hasVisual)
14656								dom.addClass(elm, cls);
14657							else
14658								dom.removeClass(elm, cls);
14659						}
14660
14661						return;
14662
14663					case 'A':
14664						if (!dom.getAttrib(elm, 'href', false)) {
14665							value = dom.getAttrib(elm, 'name') || elm.id;
14666							cls = 'mceItemAnchor';
14667
14668							if (value) {
14669								if (self.hasVisual)
14670									dom.addClass(elm, cls);
14671								else
14672									dom.removeClass(elm, cls);
14673							}
14674						}
14675
14676						return;
14677				}
14678			});
14679
14680			self.onVisualAid.dispatch(self, elm, self.hasVisual);
14681		},
14682
14683		remove : function() {
14684			var self = this, elm = self.getContainer(), doc = self.getDoc();
14685
14686			if (!self.removed) {
14687				self.removed = 1; // Cancels post remove event execution
14688
14689				// Fixed bug where IE has a blinking cursor left from the editor
14690				if (isIE && doc)
14691					doc.execCommand('SelectAll');
14692
14693				// We must save before we hide so Safari doesn't crash
14694				self.save();
14695
14696				DOM.setStyle(self.id, 'display', self.orgDisplay);
14697
14698				// Don't clear the window or document if content editable
14699				// is enabled since other instances might still be present
14700				if (!self.settings.content_editable) {
14701					Event.unbind(self.getWin());
14702					Event.unbind(self.getDoc());
14703				}
14704
14705				Event.unbind(self.getBody());
14706				Event.clear(elm);
14707
14708				self.execCallback('remove_instance_callback', self);
14709				self.onRemove.dispatch(self);
14710
14711				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14712				self.onExecCommand.listeners = [];
14713
14714				tinymce.remove(self);
14715				DOM.remove(elm);
14716			}
14717		},
14718
14719		destroy : function(s) {
14720			var t = this;
14721
14722			// One time is enough
14723			if (t.destroyed)
14724				return;
14725
14726			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14727			if (isGecko) {
14728				Event.unbind(t.getDoc());
14729				Event.unbind(t.getWin());
14730				Event.unbind(t.getBody());
14731			}
14732
14733			if (!s) {
14734				tinymce.removeUnload(t.destroy);
14735				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14736
14737				// Manual destroy
14738				if (t.theme && t.theme.destroy)
14739					t.theme.destroy();
14740
14741				// Destroy controls, selection and dom
14742				t.controlManager.destroy();
14743				t.selection.destroy();
14744				t.dom.destroy();
14745			}
14746
14747			if (t.formElement) {
14748				t.formElement.submit = t.formElement._mceOldSubmit;
14749				t.formElement._mceOldSubmit = null;
14750			}
14751
14752			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14753
14754			if (t.selection)
14755				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14756
14757			t.destroyed = 1;
14758		},
14759
14760		// Internal functions
14761
14762		_refreshContentEditable : function() {
14763			var self = this, body, parent;
14764
14765			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14766			if (self._isHidden()) {
14767				body = self.getBody();
14768				parent = body.parentNode;
14769
14770				parent.removeChild(body);
14771				parent.appendChild(body);
14772
14773				body.focus();
14774			}
14775		},
14776
14777		_isHidden : function() {
14778			var s;
14779
14780			if (!isGecko)
14781				return 0;
14782
14783			// Weird, wheres that cursor selection?
14784			s = this.selection.getSel();
14785			return (!s || !s.rangeCount || s.rangeCount === 0);
14786		}
14787	});
14788})(tinymce);
14789(function(tinymce) {
14790	var each = tinymce.each;
14791
14792	tinymce.Editor.prototype.setupEvents = function() {
14793		var self = this, settings = self.settings;
14794
14795		// Add events to the editor
14796		each([
14797			'onPreInit',
14798
14799			'onBeforeRenderUI',
14800
14801			'onPostRender',
14802
14803			'onLoad',
14804
14805			'onInit',
14806
14807			'onRemove',
14808
14809			'onActivate',
14810
14811			'onDeactivate',
14812
14813			'onClick',
14814
14815			'onEvent',
14816
14817			'onMouseUp',
14818
14819			'onMouseDown',
14820
14821			'onDblClick',
14822
14823			'onKeyDown',
14824
14825			'onKeyUp',
14826
14827			'onKeyPress',
14828
14829			'onContextMenu',
14830
14831			'onSubmit',
14832
14833			'onReset',
14834
14835			'onPaste',
14836
14837			'onPreProcess',
14838
14839			'onPostProcess',
14840
14841			'onBeforeSetContent',
14842
14843			'onBeforeGetContent',
14844
14845			'onSetContent',
14846
14847			'onGetContent',
14848
14849			'onLoadContent',
14850
14851			'onSaveContent',
14852
14853			'onNodeChange',
14854
14855			'onChange',
14856
14857			'onBeforeExecCommand',
14858
14859			'onExecCommand',
14860
14861			'onUndo',
14862
14863			'onRedo',
14864
14865			'onVisualAid',
14866
14867			'onSetProgressState',
14868
14869			'onSetAttrib'
14870		], function(name) {
14871			self[name] = new tinymce.util.Dispatcher(self);
14872		});
14873
14874		// Handle legacy cleanup_callback option
14875		if (settings.cleanup_callback) {
14876			self.onBeforeSetContent.add(function(ed, o) {
14877				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14878			});
14879
14880			self.onPreProcess.add(function(ed, o) {
14881				if (o.set)
14882					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14883
14884				if (o.get)
14885					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14886			});
14887
14888			self.onPostProcess.add(function(ed, o) {
14889				if (o.set)
14890					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14891
14892				if (o.get)						
14893					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14894			});
14895		}
14896
14897		// Handle legacy save_callback option
14898		if (settings.save_callback) {
14899			self.onGetContent.add(function(ed, o) {
14900				if (o.save)
14901					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14902			});
14903		}
14904
14905		// Handle legacy handle_event_callback option
14906		if (settings.handle_event_callback) {
14907			self.onEvent.add(function(ed, e, o) {
14908				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14909					e.preventDefault();
14910					e.stopPropagation();
14911				}
14912			});
14913		}
14914
14915		// Handle legacy handle_node_change_callback option
14916		if (settings.handle_node_change_callback) {
14917			self.onNodeChange.add(function(ed, cm, n) {
14918				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14919			});
14920		}
14921
14922		// Handle legacy save_callback option
14923		if (settings.save_callback) {
14924			self.onSaveContent.add(function(ed, o) {
14925				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14926
14927				if (h)
14928					o.content = h;
14929			});
14930		}
14931
14932		// Handle legacy onchange_callback option
14933		if (settings.onchange_callback) {
14934			self.onChange.add(function(ed, l) {
14935				ed.execCallback('onchange_callback', ed, l);
14936			});
14937		}
14938	};
14939
14940	tinymce.Editor.prototype.bindNativeEvents = function() {
14941		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14942		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14943
14944		nativeToDispatcherMap = {
14945			mouseup : 'onMouseUp',
14946			mousedown : 'onMouseDown',
14947			click : 'onClick',
14948			keyup : 'onKeyUp',
14949			keydown : 'onKeyDown',
14950			keypress : 'onKeyPress',
14951			submit : 'onSubmit',
14952			reset : 'onReset',
14953			contextmenu : 'onContextMenu',
14954			dblclick : 'onDblClick',
14955			paste : 'onPaste' // Doesn't work in all browsers yet
14956		};
14957
14958		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14959		function eventHandler(evt, args) {
14960			var type = evt.type;
14961
14962			// Don't fire events when it's removed
14963			if (self.removed)
14964				return;
14965
14966			// Sends the native event out to a global dispatcher then to the specific event dispatcher
14967			if (self.onEvent.dispatch(self, evt, args) !== false) {
14968				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14969			}
14970		};
14971
14972		// Opera doesn't support focus event for contentEditable elements so we need to fake it
14973		function doOperaFocus(e) {
14974			self.focus(true);
14975		};
14976
14977		function nodeChanged(ed, e) {
14978			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14979			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14980				self.selection.normalize();
14981			}
14982
14983			self.nodeChanged();
14984		}
14985
14986		// Add DOM events
14987		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14988			var root = settings.content_editable ? self.getBody() : self.getDoc();
14989
14990			switch (nativeName) {
14991				case 'contextmenu':
14992					dom.bind(root, nativeName, eventHandler);
14993					break;
14994
14995				case 'paste':
14996					dom.bind(self.getBody(), nativeName, eventHandler);
14997					break;
14998
14999				case 'submit':
15000				case 'reset':
15001					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
15002					break;
15003
15004				default:
15005					dom.bind(root, nativeName, eventHandler);
15006			}
15007		});
15008
15009		// Set the editor as active when focused
15010		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
15011			self.focus(true);
15012		});
15013
15014		if (settings.content_editable && tinymce.isOpera) {
15015			dom.bind(self.getBody(), 'click', doOperaFocus);
15016			dom.bind(self.getBody(), 'keydown', doOperaFocus);
15017		}
15018
15019		// Add node change handler
15020		self.onMouseUp.add(nodeChanged);
15021
15022		self.onKeyUp.add(function(ed, e) {
15023			var keyCode = e.keyCode;
15024
15025			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
15026				nodeChanged(ed, e);
15027		});
15028
15029		// Add reset handler
15030		self.onReset.add(function() {
15031			self.setContent(self.startContent, {format : 'raw'});
15032		});
15033
15034		// Add shortcuts
15035		function handleShortcut(e, execute) {
15036			if (e.altKey || e.ctrlKey || e.metaKey) {
15037				each(self.shortcuts, function(shortcut) {
15038					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
15039
15040					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
15041						return;
15042
15043					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
15044						e.preventDefault();
15045
15046						if (execute) {
15047							shortcut.func.call(shortcut.scope);
15048						}
15049
15050						return true;
15051					}
15052				});
15053			}
15054		};
15055
15056		self.onKeyUp.add(function(ed, e) {
15057			handleShortcut(e);
15058		});
15059
15060		self.onKeyPress.add(function(ed, e) {
15061			handleShortcut(e);
15062		});
15063
15064		self.onKeyDown.add(function(ed, e) {
15065			handleShortcut(e, true);
15066		});
15067
15068		if (tinymce.isOpera) {
15069			self.onClick.add(function(ed, e) {
15070				e.preventDefault();
15071			});
15072		}
15073	};
15074})(tinymce);
15075(function(tinymce) {
15076	// Added for compression purposes
15077	var each = tinymce.each, undef, TRUE = true, FALSE = false;
15078
15079	tinymce.EditorCommands = function(editor) {
15080		var dom = editor.dom,
15081			selection = editor.selection,
15082			commands = {state: {}, exec : {}, value : {}},
15083			settings = editor.settings,
15084			formatter = editor.formatter,
15085			bookmark;
15086
15087		function execCommand(command, ui, value) {
15088			var func;
15089
15090			command = command.toLowerCase();
15091			if (func = commands.exec[command]) {
15092				func(command, ui, value);
15093				return TRUE;
15094			}
15095
15096			return FALSE;
15097		};
15098
15099		function queryCommandState(command) {
15100			var func;
15101
15102			command = command.toLowerCase();
15103			if (func = commands.state[command])
15104				return func(command);
15105
15106			return -1;
15107		};
15108
15109		function queryCommandValue(command) {
15110			var func;
15111
15112			command = command.toLowerCase();
15113			if (func = commands.value[command])
15114				return func(command);
15115
15116			return FALSE;
15117		};
15118
15119		function addCommands(command_list, type) {
15120			type = type || 'exec';
15121
15122			each(command_list, function(callback, command) {
15123				each(command.toLowerCase().split(','), function(command) {
15124					commands[type][command] = callback;
15125				});
15126			});
15127		};
15128
15129		// Expose public methods
15130		tinymce.extend(this, {
15131			execCommand : execCommand,
15132			queryCommandState : queryCommandState,
15133			queryCommandValue : queryCommandValue,
15134			addCommands : addCommands
15135		});
15136
15137		// Private methods
15138
15139		function execNativeCommand(command, ui, value) {
15140			if (ui === undef)
15141				ui = FALSE;
15142
15143			if (value === undef)
15144				value = null;
15145
15146			return editor.getDoc().execCommand(command, ui, value);
15147		};
15148
15149		function isFormatMatch(name) {
15150			return formatter.match(name);
15151		};
15152
15153		function toggleFormat(name, value) {
15154			formatter.toggle(name, value ? {value : value} : undef);
15155		};
15156
15157		function storeSelection(type) {
15158			bookmark = selection.getBookmark(type);
15159		};
15160
15161		function restoreSelection() {
15162			selection.moveToBookmark(bookmark);
15163		};
15164
15165		// Add execCommand overrides
15166		addCommands({
15167			// Ignore these, added for compatibility
15168			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
15169
15170			// Add undo manager logic
15171			'mceEndUndoLevel,mceAddUndoLevel' : function() {
15172				editor.undoManager.add();
15173			},
15174
15175			'Cut,Copy,Paste' : function(command) {
15176				var doc = editor.getDoc(), failed;
15177
15178				// Try executing the native command
15179				try {
15180					execNativeCommand(command);
15181				} catch (ex) {
15182					// Command failed
15183					failed = TRUE;
15184				}
15185
15186				// Present alert message about clipboard access not being available
15187				if (failed || !doc.queryCommandSupported(command)) {
15188					if (tinymce.isGecko) {
15189						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
15190							if (state)
15191								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
15192						});
15193					} else
15194						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
15195				}
15196			},
15197
15198			// Override unlink command
15199			unlink : function(command) {
15200				if (selection.isCollapsed())
15201					selection.select(selection.getNode());
15202
15203				execNativeCommand(command);
15204				selection.collapse(FALSE);
15205			},
15206
15207			// Override justify commands to use the text formatter engine
15208			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15209				var align = command.substring(7);
15210
15211				// Remove all other alignments first
15212				each('left,center,right,full'.split(','), function(name) {
15213					if (align != name)
15214						formatter.remove('align' + name);
15215				});
15216
15217				toggleFormat('align' + align);
15218				execCommand('mceRepaint');
15219			},
15220
15221			// Override list commands to fix WebKit bug
15222			'InsertUnorderedList,InsertOrderedList' : function(command) {
15223				var listElm, listParent;
15224
15225				execNativeCommand(command);
15226
15227				// WebKit produces lists within block elements so we need to split them
15228				// we will replace the native list creation logic to custom logic later on
15229				// TODO: Remove this when the list creation logic is removed
15230				listElm = dom.getParent(selection.getNode(), 'ol,ul');
15231				if (listElm) {
15232					listParent = listElm.parentNode;
15233
15234					// If list is within a text block then split that block
15235					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
15236						storeSelection();
15237						dom.split(listParent, listElm);
15238						restoreSelection();
15239					}
15240				}
15241			},
15242
15243			// Override commands to use the text formatter engine
15244			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15245				toggleFormat(command);
15246			},
15247
15248			// Override commands to use the text formatter engine
15249			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
15250				toggleFormat(command, value);
15251			},
15252
15253			FontSize : function(command, ui, value) {
15254				var fontClasses, fontSizes;
15255
15256				// Convert font size 1-7 to styles
15257				if (value >= 1 && value <= 7) {
15258					fontSizes = tinymce.explode(settings.font_size_style_values);
15259					fontClasses = tinymce.explode(settings.font_size_classes);
15260
15261					if (fontClasses)
15262						value = fontClasses[value - 1] || value;
15263					else
15264						value = fontSizes[value - 1] || value;
15265				}
15266
15267				toggleFormat(command, value);
15268			},
15269
15270			RemoveFormat : function(command) {
15271				formatter.remove(command);
15272			},
15273
15274			mceBlockQuote : function(command) {
15275				toggleFormat('blockquote');
15276			},
15277
15278			FormatBlock : function(command, ui, value) {
15279				return toggleFormat(value || 'p');
15280			},
15281
15282			mceCleanup : function() {
15283				var bookmark = selection.getBookmark();
15284
15285				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
15286
15287				selection.moveToBookmark(bookmark);
15288			},
15289
15290			mceRemoveNode : function(command, ui, value) {
15291				var node = value || selection.getNode();
15292
15293				// Make sure that the body node isn't removed
15294				if (node != editor.getBody()) {
15295					storeSelection();
15296					editor.dom.remove(node, TRUE);
15297					restoreSelection();
15298				}
15299			},
15300
15301			mceSelectNodeDepth : function(command, ui, value) {
15302				var counter = 0;
15303
15304				dom.getParent(selection.getNode(), function(node) {
15305					if (node.nodeType == 1 && counter++ == value) {
15306						selection.select(node);
15307						return FALSE;
15308					}
15309				}, editor.getBody());
15310			},
15311
15312			mceSelectNode : function(command, ui, value) {
15313				selection.select(value);
15314			},
15315
15316			mceInsertContent : function(command, ui, value) {
15317				var parser, serializer, parentNode, rootNode, fragment, args,
15318					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
15319
15320				//selection.normalize();
15321
15322				// Setup parser and serializer
15323				parser = editor.parser;
15324				serializer = new tinymce.html.Serializer({}, editor.schema);
15325				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
15326
15327				// Run beforeSetContent handlers on the HTML to be inserted
15328				args = {content: value, format: 'html'};
15329				selection.onBeforeSetContent.dispatch(selection, args);
15330				value = args.content;
15331
15332				// Add caret at end of contents if it's missing
15333				if (value.indexOf('{$caret}') == -1)
15334					value += '{$caret}';
15335
15336				// Replace the caret marker with a span bookmark element
15337				value = value.replace(/\{\$caret\}/, bookmarkHtml);
15338
15339				// Insert node maker where we will insert the new HTML and get it's parent
15340				if (!selection.isCollapsed())
15341					editor.getDoc().execCommand('Delete', false, null);
15342
15343				parentNode = selection.getNode();
15344
15345				// Parse the fragment within the context of the parent node
15346				args = {context : parentNode.nodeName.toLowerCase()};
15347				fragment = parser.parse(value, args);
15348
15349				// Move the caret to a more suitable location
15350				node = fragment.lastChild;
15351				if (node.attr('id') == 'mce_marker') {
15352					marker = node;
15353
15354					for (node = node.prev; node; node = node.walk(true)) {
15355						if (node.type == 3 || !dom.isBlock(node.name)) {
15356							node.parent.insert(marker, node, node.name === 'br');
15357							break;
15358						}
15359					}
15360				}
15361
15362				// If parser says valid we can insert the contents into that parent
15363				if (!args.invalid) {
15364					value = serializer.serialize(fragment);
15365
15366					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
15367					node = parentNode.firstChild;
15368					node2 = parentNode.lastChild;
15369					if (!node || (node === node2 && node.nodeName === 'BR'))
15370						dom.setHTML(parentNode, value);
15371					else
15372						selection.setContent(value);
15373				} else {
15374					// If the fragment was invalid within that context then we need
15375					// to parse and process the parent it's inserted into
15376
15377					// Insert bookmark node and get the parent
15378					selection.setContent(bookmarkHtml);
15379					parentNode = selection.getNode();
15380					rootNode = editor.getBody();
15381
15382					// Opera will return the document node when selection is in root
15383					if (parentNode.nodeType == 9)
15384						parentNode = node = rootNode;
15385					else
15386						node = parentNode;
15387
15388					// Find the ancestor just before the root element
15389					while (node !== rootNode) {
15390						parentNode = node;
15391						node = node.parentNode;
15392					}
15393
15394					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15395					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15396					value = serializer.serialize(
15397						parser.parse(
15398							// Need to replace by using a function since $ in the contents would otherwise be a problem
15399							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15400								return serializer.serialize(fragment);
15401							})
15402						)
15403					);
15404
15405					// Set the inner/outer HTML depending on if we are in the root or not
15406					if (parentNode == rootNode)
15407						dom.setHTML(rootNode, value);
15408					else
15409						dom.setOuterHTML(parentNode, value);
15410				}
15411
15412				marker = dom.get('mce_marker');
15413
15414				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15415				nodeRect = dom.getRect(marker);
15416				viewPortRect = dom.getViewPort(editor.getWin());
15417
15418				// Check if node is out side the viewport if it is then scroll to it
15419				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15420					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15421					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15422					viewportBodyElement.scrollLeft = nodeRect.x;
15423					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15424				}
15425
15426				// Move selection before marker and remove it
15427				rng = dom.createRng();
15428
15429				// If previous sibling is a text node set the selection to the end of that node
15430				node = marker.previousSibling;
15431				if (node && node.nodeType == 3) {
15432					rng.setStart(node, node.nodeValue.length);
15433				} else {
15434					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15435					rng.setStartBefore(marker);
15436					rng.setEndBefore(marker);
15437				}
15438
15439				// Remove the marker node and set the new range
15440				dom.remove(marker);
15441				selection.setRng(rng);
15442
15443				// Dispatch after event and add any visual elements needed
15444				selection.onSetContent.dispatch(selection, args);
15445				editor.addVisual();
15446			},
15447
15448			mceInsertRawHTML : function(command, ui, value) {
15449				selection.setContent('tiny_mce_marker');
15450				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15451			},
15452
15453			mceToggleFormat : function(command, ui, value) {
15454				toggleFormat(value);
15455			},
15456
15457			mceSetContent : function(command, ui, value) {
15458				editor.setContent(value);
15459			},
15460
15461			'Indent,Outdent' : function(command) {
15462				var intentValue, indentUnit, value;
15463
15464				// Setup indent level
15465				intentValue = settings.indentation;
15466				indentUnit = /[a-z%]+$/i.exec(intentValue);
15467				intentValue = parseInt(intentValue);
15468
15469				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15470					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15471					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15472						formatter.apply('div');
15473					}
15474
15475					each(selection.getSelectedBlocks(), function(element) {
15476						if (command == 'outdent') {
15477							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15478							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15479						} else
15480							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15481					});
15482				} else
15483					execNativeCommand(command);
15484			},
15485
15486			mceRepaint : function() {
15487				var bookmark;
15488
15489				if (tinymce.isGecko) {
15490					try {
15491						storeSelection(TRUE);
15492
15493						if (selection.getSel())
15494							selection.getSel().selectAllChildren(editor.getBody());
15495
15496						selection.collapse(TRUE);
15497						restoreSelection();
15498					} catch (ex) {
15499						// Ignore
15500					}
15501				}
15502			},
15503
15504			mceToggleFormat : function(command, ui, value) {
15505				formatter.toggle(value);
15506			},
15507
15508			InsertHorizontalRule : function() {
15509				editor.execCommand('mceInsertContent', false, '<hr />');
15510			},
15511
15512			mceToggleVisualAid : function() {
15513				editor.hasVisual = !editor.hasVisual;
15514				editor.addVisual();
15515			},
15516
15517			mceReplaceContent : function(command, ui, value) {
15518				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15519			},
15520
15521			mceInsertLink : function(command, ui, value) {
15522				var anchor;
15523
15524				if (typeof(value) == 'string')
15525					value = {href : value};
15526
15527				anchor = dom.getParent(selection.getNode(), 'a');
15528
15529				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15530				value.href = value.href.replace(' ', '%20');
15531
15532				// Remove existing links if there could be child links or that the href isn't specified
15533				if (!anchor || !value.href) {
15534					formatter.remove('link');
15535				}		
15536
15537				// Apply new link to selection
15538				if (value.href) {
15539					formatter.apply('link', value, anchor);
15540				}
15541			},
15542
15543			selectAll : function() {
15544				var root = dom.getRoot(), rng = dom.createRng();
15545
15546				// Old IE does a better job with selectall than new versions
15547				if (selection.getRng().setStart) {
15548					rng.setStart(root, 0);
15549					rng.setEnd(root, root.childNodes.length);
15550
15551					selection.setRng(rng);
15552				} else {
15553					execNativeCommand('SelectAll');
15554				}
15555			}
15556		});
15557
15558		// Add queryCommandState overrides
15559		addCommands({
15560			// Override justify commands
15561			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15562				var name = 'align' + command.substring(7);
15563				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15564				var matches = tinymce.map(nodes, function(node) {
15565					return !!formatter.matchNode(node, name);
15566				});
15567				return tinymce.inArray(matches, TRUE) !== -1;
15568			},
15569
15570			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15571				return isFormatMatch(command);
15572			},
15573
15574			mceBlockQuote : function() {
15575				return isFormatMatch('blockquote');
15576			},
15577
15578			Outdent : function() {
15579				var node;
15580
15581				if (settings.inline_styles) {
15582					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15583						return TRUE;
15584
15585					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15586						return TRUE;
15587				}
15588
15589				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15590			},
15591
15592			'InsertUnorderedList,InsertOrderedList' : function(command) {
15593				var list = dom.getParent(selection.getNode(), 'ul,ol');
15594				return list && 
15595				     (command === 'insertunorderedlist' && list.tagName === 'UL'
15596				   || command === 'insertorderedlist' && list.tagName === 'OL');
15597			}
15598		}, 'state');
15599
15600		// Add queryCommandValue overrides
15601		addCommands({
15602			'FontSize,FontName' : function(command) {
15603				var value = 0, parent;
15604
15605				if (parent = dom.getParent(selection.getNode(), 'span')) {
15606					if (command == 'fontsize')
15607						value = parent.style.fontSize;
15608					else
15609						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15610				}
15611
15612				return value;
15613			}
15614		}, 'value');
15615
15616		// Add undo manager logic
15617		addCommands({
15618			Undo : function() {
15619				editor.undoManager.undo();
15620			},
15621
15622			Redo : function() {
15623				editor.undoManager.redo();
15624			}
15625		});
15626	};
15627})(tinymce);
15628(function(tinymce) {
15629	var Dispatcher = tinymce.util.Dispatcher;
15630
15631	tinymce.UndoManager = function(editor) {
15632		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15633
15634		function getContent() {
15635			// Remove whitespace before/after and remove pure bogus nodes
15636			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15637		};
15638
15639		function addNonTypingUndoLevel() {
15640			self.typing = false;
15641			self.add();
15642		};
15643
15644		// Create event instances
15645		onBeforeAdd = new Dispatcher(self);
15646		onAdd       = new Dispatcher(self);
15647		onUndo      = new Dispatcher(self);
15648		onRedo      = new Dispatcher(self);
15649
15650		// Pass though onAdd event from UndoManager to Editor as onChange
15651		onAdd.add(function(undoman, level) {
15652			if (undoman.hasUndo())
15653				return editor.onChange.dispatch(editor, level, undoman);
15654		});
15655
15656		// Pass though onUndo event from UndoManager to Editor
15657		onUndo.add(function(undoman, level) {
15658			return editor.onUndo.dispatch(editor, level, undoman);
15659		});
15660
15661		// Pass though onRedo event from UndoManager to Editor
15662		onRedo.add(function(undoman, level) {
15663			return editor.onRedo.dispatch(editor, level, undoman);
15664		});
15665
15666		// Add initial undo level when the editor is initialized
15667		editor.onInit.add(function() {
15668			self.add();
15669		});
15670
15671		// Get position before an execCommand is processed
15672		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15673			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15674				self.beforeChange();
15675			}
15676		});
15677
15678		// Add undo level after an execCommand call was made
15679		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15680			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15681				self.add();
15682			}
15683		});
15684
15685		// Add undo level on save contents, drag end and blur/focusout
15686		editor.onSaveContent.add(addNonTypingUndoLevel);
15687		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15688		editor.dom.bind(editor.getBody(), 'focusout', function(e) {
15689			if (!editor.removed && self.typing) {
15690				addNonTypingUndoLevel();
15691			}
15692		});
15693
15694		editor.onKeyUp.add(function(editor, e) {
15695			var keyCode = e.keyCode;
15696
15697			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15698				addNonTypingUndoLevel();
15699			}
15700		});
15701
15702		editor.onKeyDown.add(function(editor, e) {
15703			var keyCode = e.keyCode;
15704
15705			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15706			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15707				if (self.typing) {
15708					addNonTypingUndoLevel();
15709				}
15710
15711				return;
15712			}
15713
15714			// If key isn't shift,ctrl,alt,capslock,metakey
15715			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15716				self.beforeChange();
15717				self.typing = true;
15718				self.add();
15719			}
15720		});
15721
15722		editor.onMouseDown.add(function(editor, e) {
15723			if (self.typing) {
15724				addNonTypingUndoLevel();
15725			}
15726		});
15727
15728		// Add keyboard shortcuts for undo/redo keys
15729		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15730		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15731
15732		self = {
15733			// Explose for debugging reasons
15734			data : data,
15735
15736			typing : false,
15737			
15738			onBeforeAdd: onBeforeAdd,
15739
15740			onAdd : onAdd,
15741
15742			onUndo : onUndo,
15743
15744			onRedo : onRedo,
15745
15746			beforeChange : function() {
15747				beforeBookmark = editor.selection.getBookmark(2, true);
15748			},
15749
15750			add : function(level) {
15751				var i, settings = editor.settings, lastLevel;
15752
15753				level = level || {};
15754				level.content = getContent();
15755				
15756				self.onBeforeAdd.dispatch(self, level);
15757
15758				// Add undo level if needed
15759				lastLevel = data[index];
15760				if (lastLevel && lastLevel.content == level.content)
15761					return null;
15762
15763				// Set before bookmark on previous level
15764				if (data[index])
15765					data[index].beforeBookmark = beforeBookmark;
15766
15767				// Time to compress
15768				if (settings.custom_undo_redo_levels) {
15769					if (data.length > settings.custom_undo_redo_levels) {
15770						for (i = 0; i < data.length - 1; i++)
15771							data[i] = data[i + 1];
15772
15773						data.length--;
15774						index = data.length;
15775					}
15776				}
15777
15778				// Get a non intrusive normalized bookmark
15779				level.bookmark = editor.selection.getBookmark(2, true);
15780
15781				// Crop array if needed
15782				if (index < data.length - 1)
15783					data.length = index + 1;
15784
15785				data.push(level);
15786				index = data.length - 1;
15787
15788				self.onAdd.dispatch(self, level);
15789				editor.isNotDirty = 0;
15790
15791				return level;
15792			},
15793
15794			undo : function() {
15795				var level, i;
15796
15797				if (self.typing) {
15798					self.add();
15799					self.typing = false;
15800				}
15801
15802				if (index > 0) {
15803					level = data[--index];
15804
15805					editor.setContent(level.content, {format : 'raw'});
15806					editor.selection.moveToBookmark(level.beforeBookmark);
15807
15808					self.onUndo.dispatch(self, level);
15809				}
15810
15811				return level;
15812			},
15813
15814			redo : function() {
15815				var level;
15816
15817				if (index < data.length - 1) {
15818					level = data[++index];
15819
15820					editor.setContent(level.content, {format : 'raw'});
15821					editor.selection.moveToBookmark(level.bookmark);
15822
15823					self.onRedo.dispatch(self, level);
15824				}
15825
15826				return level;
15827			},
15828
15829			clear : function() {
15830				data = [];
15831				index = 0;
15832				self.typing = false;
15833			},
15834
15835			hasUndo : function() {
15836				return index > 0 || this.typing;
15837			},
15838
15839			hasRedo : function() {
15840				return index < data.length - 1 && !this.typing;
15841			}
15842		};
15843
15844		return self;
15845	};
15846})(tinymce);
15847tinymce.ForceBlocks = function(editor) {
15848	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15849
15850	function addRootBlocks() {
15851		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15852
15853		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15854			return;
15855
15856		// Check if node is wrapped in block
15857		while (node && node != rootNode) {
15858			if (blockElements[node.nodeName])
15859				return;
15860
15861			node = node.parentNode;
15862		}
15863
15864		// Get current selection
15865		rng = selection.getRng();
15866		if (rng.setStart) {
15867			startContainer = rng.startContainer;
15868			startOffset = rng.startOffset;
15869			endContainer = rng.endContainer;
15870			endOffset = rng.endOffset;
15871		} else {
15872			// Force control range into text range
15873			if (rng.item) {
15874				node = rng.item(0);
15875				rng = editor.getDoc().body.createTextRange();
15876				rng.moveToElementText(node);
15877			}
15878
15879			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15880			tmpRng = rng.duplicate();
15881			tmpRng.collapse(true);
15882			startOffset = tmpRng.move('character', offset) * -1;
15883
15884			if (!tmpRng.collapsed) {
15885				tmpRng = rng.duplicate();
15886				tmpRng.collapse(false);
15887				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15888			}
15889		}
15890
15891		// Wrap non block elements and text nodes
15892		node = rootNode.firstChild;
15893		while (node) {
15894			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15895				// Remove empty text nodes
15896				if (node.nodeType === 3 && node.nodeValue.length == 0) {
15897					tempNode = node;
15898					node = node.nextSibling;
15899					dom.remove(tempNode);
15900					continue;
15901				}
15902
15903				if (!rootBlockNode) {
15904					rootBlockNode = dom.create(settings.forced_root_block);
15905					node.parentNode.insertBefore(rootBlockNode, node);
15906					wrapped = true;
15907				}
15908
15909				tempNode = node;
15910				node = node.nextSibling;
15911				rootBlockNode.appendChild(tempNode);
15912			} else {
15913				rootBlockNode = null;
15914				node = node.nextSibling;
15915			}
15916		}
15917
15918		if (wrapped) {
15919			if (rng.setStart) {
15920				rng.setStart(startContainer, startOffset);
15921				rng.setEnd(endContainer, endOffset);
15922				selection.setRng(rng);
15923			} else {
15924				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15925				if (isInEditorDocument) {
15926					try {
15927						rng = editor.getDoc().body.createTextRange();
15928						rng.moveToElementText(rootNode);
15929						rng.collapse(true);
15930						rng.moveStart('character', startOffset);
15931
15932						if (endOffset > 0)
15933							rng.moveEnd('character', endOffset);
15934
15935						rng.select();
15936					} catch (ex) {
15937						// Ignore
15938					}
15939				}
15940			}
15941
15942			editor.nodeChanged();
15943		}
15944	};
15945
15946	// Force root blocks
15947	if (settings.forced_root_block) {
15948		editor.onKeyUp.add(addRootBlocks);
15949		editor.onNodeChange.add(addRootBlocks);
15950	}
15951};
15952(function(tinymce) {
15953	// Shorten names
15954	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15955
15956	tinymce.create('tinymce.ControlManager', {
15957		ControlManager : function(ed, s) {
15958			var t = this, i;
15959
15960			s = s || {};
15961			t.editor = ed;
15962			t.controls = {};
15963			t.onAdd = new tinymce.util.Dispatcher(t);
15964			t.onPostRender = new tinymce.util.Dispatcher(t);
15965			t.prefix = s.prefix || ed.id + '_';
15966			t._cls = {};
15967
15968			t.onPostRender.add(function() {
15969				each(t.controls, function(c) {
15970					c.postRender();
15971				});
15972			});
15973		},
15974
15975		get : function(id) {
15976			return this.controls[this.prefix + id] || this.controls[id];
15977		},
15978
15979		setActive : function(id, s) {
15980			var c = null;
15981
15982			if (c = this.get(id))
15983				c.setActive(s);
15984
15985			return c;
15986		},
15987
15988		setDisabled : function(id, s) {
15989			var c = null;
15990
15991			if (c = this.get(id))
15992				c.setDisabled(s);
15993
15994			return c;
15995		},
15996
15997		add : function(c) {
15998			var t = this;
15999
16000			if (c) {
16001				t.controls[c.id] = c;
16002				t.onAdd.dispatch(c, t);
16003			}
16004
16005			return c;
16006		},
16007
16008		createControl : function(name) {
16009			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
16010
16011			// Build control factory cache
16012			if (!self.controlFactories) {
16013				self.controlFactories = [];
16014				each(editor.plugins, function(plugin) {
16015					if (plugin.createControl) {
16016						self.controlFactories.push(plugin);
16017					}
16018				});
16019			}
16020
16021			// Create controls by asking cached factories
16022			factories = self.controlFactories;
16023			for (i = 0, l = factories.length; i < l; i++) {
16024				ctrl = factories[i].createControl(name, self);
16025
16026				if (ctrl) {
16027					return self.add(ctrl);
16028				}
16029			}
16030
16031			// Create sepearator
16032			if (name === "|" || name === "separator") {
16033				return self.createSeparator();
16034			}
16035
16036			// Create control from button collection
16037			if (editor.buttons && (ctrl = editor.buttons[name])) {
16038				return self.createButton(name, ctrl);
16039			}
16040
16041			return self.add(ctrl);
16042		},
16043
16044		createDropMenu : function(id, s, cc) {
16045			var t = this, ed = t.editor, c, bm, v, cls;
16046
16047			s = extend({
16048				'class' : 'mceDropDown',
16049				constrain : ed.settings.constrain_menus
16050			}, s);
16051
16052			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
16053			if (v = ed.getParam('skin_variant'))
16054				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
16055
16056			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
16057
16058			id = t.prefix + id;
16059			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
16060			c = t.controls[id] = new cls(id, s);
16061			c.onAddItem.add(function(c, o) {
16062				var s = o.settings;
16063
16064				s.title = ed.getLang(s.title, s.title);
16065
16066				if (!s.onclick) {
16067					s.onclick = function(v) {
16068						if (s.cmd)
16069							ed.execCommand(s.cmd, s.ui || false, s.value);
16070					};
16071				}
16072			});
16073
16074			ed.onRemove.add(function() {
16075				c.destroy();
16076			});
16077
16078			// Fix for bug #1897785, #1898007
16079			if (tinymce.isIE) {
16080				c.onShowMenu.add(function() {
16081					// IE 8 needs focus in order to store away a range with the current collapsed caret location
16082					ed.focus();
16083
16084					bm = ed.selection.getBookmark(1);
16085				});
16086
16087				c.onHideMenu.add(function() {
16088					if (bm) {
16089						ed.selection.moveToBookmark(bm);
16090						bm = 0;
16091					}
16092				});
16093			}
16094
16095			return t.add(c);
16096		},
16097
16098		createListBox : function(id, s, cc) {
16099			var t = this, ed = t.editor, cmd, c, cls;
16100
16101			if (t.get(id))
16102				return null;
16103
16104			s.title = ed.translate(s.title);
16105			s.scope = s.scope || ed;
16106
16107			if (!s.onselect) {
16108				s.onselect = function(v) {
16109					ed.execCommand(s.cmd, s.ui || false, v || s.value);
16110				};
16111			}
16112
16113			s = extend({
16114				title : s.title,
16115				'class' : 'mce_' + id,
16116				scope : s.scope,
16117				control_manager : t
16118			}, s);
16119
16120			id = t.prefix + id;
16121
16122
16123			function useNativeListForAccessibility(ed) {
16124				return ed.settings.use_accessible_selects && !tinymce.isGecko
16125			}
16126
16127			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
16128				c = new tinymce.ui.NativeListBox(id, s);
16129			else {
16130				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
16131				c = new cls(id, s, ed);
16132			}
16133
16134			t.controls[id] = c;
16135
16136			// Fix focus problem in Safari
16137			if (tinymce.isWebKit) {
16138				c.onPostRender.add(function(c, n) {
16139					// Store bookmark on mousedown
16140					Event.add(n, 'mousedown', function() {
16141						ed.bookmark = ed.selection.getBookmark(1);
16142					});
16143
16144					// Restore on focus, since it might be lost
16145					Event.add(n, 'focus', function() {
16146						ed.selection.moveToBookmark(ed.bookmark);
16147						ed.bookmark = null;
16148					});
16149				});
16150			}
16151
16152			if (c.hideMenu)
16153				ed.onMouseDown.add(c.hideMenu, c);
16154
16155			return t.add(c);
16156		},
16157
16158		createButton : function(id, s, cc) {
16159			var t = this, ed = t.editor, o, c, cls;
16160
16161			if (t.get(id))
16162				return null;
16163
16164			s.title = ed.translate(s.title);
16165			s.label = ed.translate(s.label);
16166			s.scope = s.scope || ed;
16167
16168			if (!s.onclick && !s.menu_button) {
16169				s.onclick = function() {
16170					ed.execCommand(s.cmd, s.ui || false, s.value);
16171				};
16172			}
16173
16174			s = extend({
16175				title : s.title,
16176				'class' : 'mce_' + id,
16177				unavailable_prefix : ed.getLang('unavailable', ''),
16178				scope : s.scope,
16179				control_manager : t
16180			}, s);
16181
16182			id = t.prefix + id;
16183
16184			if (s.menu_button) {
16185				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
16186				c = new cls(id, s, ed);
16187				ed.onMouseDown.add(c.hideMenu, c);
16188			} else {
16189				cls = t._cls.button || tinymce.ui.Button;
16190				c = new cls(id, s, ed);
16191			}
16192
16193			return t.add(c);
16194		},
16195
16196		createMenuButton : function(id, s, cc) {
16197			s = s || {};
16198			s.menu_button = 1;
16199
16200			return this.createButton(id, s, cc);
16201		},
16202
16203		createSplitButton : function(id, s, cc) {
16204			var t = this, ed = t.editor, cmd, c, cls;
16205
16206			if (t.get(id))
16207				return null;
16208
16209			s.title = ed.translate(s.title);
16210			s.scope = s.scope || ed;
16211
16212			if (!s.onclick) {
16213				s.onclick = function(v) {
16214					ed.execCommand(s.cmd, s.ui || false, v || s.value);
16215				};
16216			}
16217
16218			if (!s.onselect) {
16219				s.onselect = function(v) {
16220					ed.execCommand(s.cmd, s.ui || false, v || s.value);
16221				};
16222			}
16223
16224			s = extend({
16225				title : s.title,
16226				'class' : 'mce_' + id,
16227				scope : s.scope,
16228				control_manager : t
16229			}, s);
16230
16231			id = t.prefix + id;
16232			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
16233			c = t.add(new cls(id, s, ed));
16234			ed.onMouseDown.add(c.hideMenu, c);
16235
16236			return c;
16237		},
16238
16239		createColorSplitButton : function(id, s, cc) {
16240			var t = this, ed = t.editor, cmd, c, cls, bm;
16241
16242			if (t.get(id))
16243				return null;
16244
16245			s.title = ed.translate(s.title);
16246			s.scope = s.scope || ed;
16247
16248			if (!s.onclick) {
16249				s.onclick = function(v) {
16250					if (tinymce.isIE)
16251						bm = ed.selection.getBookmark(1);
16252
16253					ed.execCommand(s.cmd, s.ui || false, v || s.value);
16254				};
16255			}
16256
16257			if (!s.onselect) {
16258				s.onselect = function(v) {
16259					ed.execCommand(s.cmd, s.ui || false, v || s.value);
16260				};
16261			}
16262
16263			s = extend({
16264				title : s.title,
16265				'class' : 'mce_' + id,
16266				'menu_class' : ed.getParam('skin') + 'Skin',
16267				scope : s.scope,
16268				more_colors_title : ed.getLang('more_colors')
16269			}, s);
16270
16271			id = t.prefix + id;
16272			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
16273			c = new cls(id, s, ed);
16274			ed.onMouseDown.add(c.hideMenu, c);
16275
16276			// Remove the menu element when the editor is removed
16277			ed.onRemove.add(function() {
16278				c.destroy();
16279			});
16280
16281			// Fix for bug #1897785, #1898007
16282			if (tinymce.isIE) {
16283				c.onShowMenu.add(function() {
16284					// IE 8 needs focus in order to store away a range with the current collapsed caret location
16285					ed.focus();
16286					bm = ed.selection.getBookmark(1);
16287				});
16288
16289				c.onHideMenu.add(function() {
16290					if (bm) {
16291						ed.selection.moveToBookmark(bm);
16292						bm = 0;
16293					}
16294				});
16295			}
16296
16297			return t.add(c);
16298		},
16299
16300		createToolbar : function(id, s, cc) {
16301			var c, t = this, cls;
16302
16303			id = t.prefix + id;
16304			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
16305			c = new cls(id, s, t.editor);
16306
16307			if (t.get(id))
16308				return null;
16309
16310			return t.add(c);
16311		},
16312		
16313		createToolbarGroup : function(id, s, cc) {
16314			var c, t = this, cls;
16315			id = t.prefix + id;
16316			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
16317			c = new cls(id, s, t.editor);
16318			
16319			if (t.get(id))
16320				return null;
16321			
16322			return t.add(c);
16323		},
16324
16325		createSeparator : function(cc) {
16326			var cls = cc || this._cls.separator || tinymce.ui.Separator;
16327
16328			return new cls();
16329		},
16330
16331		setControlType : function(n, c) {
16332			return this._cls[n.toLowerCase()] = c;
16333		},
16334	
16335		destroy : function() {
16336			each(this.controls, function(c) {
16337				c.destroy();
16338			});
16339
16340			this.controls = null;
16341		}
16342	});
16343})(tinymce);
16344(function(tinymce) {
16345	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
16346
16347	tinymce.create('tinymce.WindowManager', {
16348		WindowManager : function(ed) {
16349			var t = this;
16350
16351			t.editor = ed;
16352			t.onOpen = new Dispatcher(t);
16353			t.onClose = new Dispatcher(t);
16354			t.params = {};
16355			t.features = {};
16356		},
16357
16358		open : function(s, p) {
16359			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
16360
16361			// Default some options
16362			s = s || {};
16363			p = p || {};
16364			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
16365			sh = isOpera ? vp.h : screen.height;
16366			s.name = s.name || 'mc_' + new Date().getTime();
16367			s.width = parseInt(s.width || 320);
16368			s.height = parseInt(s.height || 240);
16369			s.resizable = true;
16370			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
16371			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
16372			p.inline = false;
16373			p.mce_width = s.width;
16374			p.mce_height = s.height;
16375			p.mce_auto_focus = s.auto_focus;
16376
16377			if (mo) {
16378				if (isIE) {
16379					s.center = true;
16380					s.help = false;
16381					s.dialogWidth = s.width + 'px';
16382					s.dialogHeight = s.height + 'px';
16383					s.scroll = s.scrollbars || false;
16384				}
16385			}
16386
16387			// Build features string
16388			each(s, function(v, k) {
16389				if (tinymce.is(v, 'boolean'))
16390					v = v ? 'yes' : 'no';
16391
16392				if (!/^(name|url)$/.test(k)) {
16393					if (isIE && mo)
16394						f += (f ? ';' : '') + k + ':' + v;
16395					else
16396						f += (f ? ',' : '') + k + '=' + v;
16397				}
16398			});
16399
16400			t.features = s;
16401			t.params = p;
16402			t.onOpen.dispatch(t, s, p);
16403
16404			u = s.url || s.file;
16405			u = tinymce._addVer(u);
16406
16407			try {
16408				if (isIE && mo) {
16409					w = 1;
16410					window.showModalDialog(u, window, f);
16411				} else
16412					w = window.open(u, s.name, f);
16413			} catch (ex) {
16414				// Ignore
16415			}
16416
16417			if (!w)
16418				alert(t.editor.getLang('popup_blocked'));
16419		},
16420
16421		close : function(w) {
16422			w.close();
16423			this.onClose.dispatch(this);
16424		},
16425
16426		createInstance : function(cl, a, b, c, d, e) {
16427			var f = tinymce.resolve(cl);
16428
16429			return new f(a, b, c, d, e);
16430		},
16431
16432		confirm : function(t, cb, s, w) {
16433			w = w || window;
16434
16435			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16436		},
16437
16438		alert : function(tx, cb, s, w) {
16439			var t = this;
16440
16441			w = w || window;
16442			w.alert(t._decode(t.editor.getLang(tx, tx)));
16443
16444			if (cb)
16445				cb.call(s || t);
16446		},
16447
16448		resizeBy : function(dw, dh, win) {
16449			win.resizeBy(dw, dh);
16450		},
16451
16452		// Internal functions
16453
16454		_decode : function(s) {
16455			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16456		}
16457	});
16458}(tinymce));
16459(function(tinymce) {
16460	tinymce.Formatter = function(ed) {
16461		var formats = {},
16462			each = tinymce.each,
16463			dom = ed.dom,
16464			selection = ed.selection,
16465			TreeWalker = tinymce.dom.TreeWalker,
16466			rangeUtils = new tinymce.dom.RangeUtils(dom),
16467			isValid = ed.schema.isValidChild,
16468			isArray = tinymce.isArray,
16469			isBlock = dom.isBlock,
16470			forcedRootBlock = ed.settings.forced_root_block,
16471			nodeIndex = dom.nodeIndex,
16472			INVISIBLE_CHAR = '\uFEFF',
16473			MCE_ATTR_RE = /^(src|href|style)$/,
16474			FALSE = false,
16475			TRUE = true,
16476			formatChangeData,
16477			undef,
16478			getContentEditable = dom.getContentEditable;
16479
16480		function isTextBlock(name) {
16481			if (name.nodeType) {
16482				name = name.nodeName;
16483			}
16484
16485			return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
16486		}
16487
16488		function getParents(node, selector) {
16489			return dom.getParents(node, selector, dom.getRoot());
16490		};
16491
16492		function isCaretNode(node) {
16493			return node.nodeType === 1 && node.id === '_mce_caret';
16494		};
16495
16496		function defaultFormats() {
16497			register({
16498				alignleft : [
16499					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16500					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16501				],
16502
16503				aligncenter : [
16504					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16505					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16506					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16507				],
16508
16509				alignright : [
16510					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16511					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16512				],
16513
16514				alignfull : [
16515					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16516				],
16517
16518				bold : [
16519					{inline : 'strong', remove : 'all'},
16520					{inline : 'span', styles : {fontWeight : 'bold'}},
16521					{inline : 'b', remove : 'all'}
16522				],
16523
16524				italic : [
16525					{inline : 'em', remove : 'all'},
16526					{inline : 'span', styles : {fontStyle : 'italic'}},
16527					{inline : 'i', remove : 'all'}
16528				],
16529
16530				underline : [
16531					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16532					{inline : 'u', remove : 'all'}
16533				],
16534
16535				strikethrough : [
16536					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16537					{inline : 'strike', remove : 'all'}
16538				],
16539
16540				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16541				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16542				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16543				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16544				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16545				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16546				subscript : {inline : 'sub'},
16547				superscript : {inline : 'sup'},
16548
16549				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16550					onmatch : function(node) {
16551						return true;
16552					},
16553
16554					onformat : function(elm, fmt, vars) {
16555						each(vars, function(value, key) {
16556							dom.setAttrib(elm, key, value);
16557						});
16558					}
16559				},
16560
16561				removeformat : [
16562					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16563					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16564					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16565				]
16566			});
16567
16568			// Register default block formats
16569			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16570				register(name, {block : name, remove : 'all'});
16571			});
16572
16573			// Register user defined formats
16574			register(ed.settings.formats);
16575		};
16576
16577		function addKeyboardShortcuts() {
16578			// Add some inline shortcuts
16579			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16580			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16581			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16582
16583			// BlockFormat shortcuts keys
16584			for (var i = 1; i <= 6; i++) {
16585				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16586			}
16587
16588			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16589			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16590			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16591		};
16592
16593		// Public functions
16594
16595		function get(name) {
16596			return name ? formats[name] : formats;
16597		};
16598
16599		function register(name, format) {
16600			if (name) {
16601				if (typeof(name) !== 'string') {
16602					each(name, function(format, name) {
16603						register(name, format);
16604					});
16605				} else {
16606					// Force format into array and add it to internal collection
16607					format = format.length ? format : [format];
16608
16609					each(format, function(format) {
16610						// Set deep to false by default on selector formats this to avoid removing
16611						// alignment on images inside paragraphs when alignment is changed on paragraphs
16612						if (format.deep === undef)
16613							format.deep = !format.selector;
16614
16615						// Default to true
16616						if (format.split === undef)
16617							format.split = !format.selector || format.inline;
16618
16619						// Default to true
16620						if (format.remove === undef && format.selector && !format.inline)
16621							format.remove = 'none';
16622
16623						// Mark format as a mixed format inline + block level
16624						if (format.selector && format.inline) {
16625							format.mixed = true;
16626							format.block_expand = true;
16627						}
16628
16629						// Split classes if needed
16630						if (typeof(format.classes) === 'string')
16631							format.classes = format.classes.split(/\s+/);
16632					});
16633
16634					formats[name] = format;
16635				}
16636			}
16637		};
16638
16639		var getTextDecoration = function(node) {
16640			var decoration;
16641
16642			ed.dom.getParent(node, function(n) {
16643				decoration = ed.dom.getStyle(n, 'text-decoration');
16644				return decoration && decoration !== 'none';
16645			});
16646
16647			return decoration;
16648		};
16649
16650		var processUnderlineAndColor = function(node) {
16651			var textDecoration;
16652			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16653				textDecoration = getTextDecoration(node.parentNode);
16654				if (ed.dom.getStyle(node, 'color') && textDecoration) {
16655					ed.dom.setStyle(node, 'text-decoration', textDecoration);
16656				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16657					ed.dom.setStyle(node, 'text-decoration', null);
16658				}
16659			}
16660		};
16661
16662		function apply(name, vars, node) {
16663			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16664
16665			function setElementFormat(elm, fmt) {
16666				fmt = fmt || format;
16667
16668				if (elm) {
16669					if (fmt.onformat) {
16670						fmt.onformat(elm, fmt, vars, node);
16671					}
16672
16673					each(fmt.styles, function(value, name) {
16674						dom.setStyle(elm, name, replaceVars(value, vars));
16675					});
16676
16677					each(fmt.attributes, function(value, name) {
16678						dom.setAttrib(elm, name, replaceVars(value, vars));
16679					});
16680
16681					each(fmt.classes, function(value) {
16682						value = replaceVars(value, vars);
16683
16684						if (!dom.hasClass(elm, value))
16685							dom.addClass(elm, value);
16686					});
16687				}
16688			};
16689			function adjustSelectionToVisibleSelection() {
16690				function findSelectionEnd(start, end) {
16691					var walker = new TreeWalker(end);
16692					for (node = walker.current(); node; node = walker.prev()) {
16693						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16694							return node;
16695						}
16696					}
16697				};
16698
16699				// Adjust selection so that a end container with a end offset of zero is not included in the selection
16700				// as this isn't visible to the user.
16701				var rng = ed.selection.getRng();
16702				var start = rng.startContainer;
16703				var end = rng.endContainer;
16704
16705				if (start != end && rng.endOffset === 0) {
16706					var newEnd = findSelectionEnd(start, end);
16707					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16708
16709					rng.setEnd(newEnd, endOffset);
16710				}
16711
16712				return rng;
16713			}
16714			
16715			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16716				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16717				
16718				// find the index of the first child list.
16719				each(node.childNodes, function(n, index) {
16720					if (n.nodeName === "UL" || n.nodeName === "OL") {
16721						listIndex = index;
16722						list = n;
16723						return false;
16724					}
16725				});
16726				
16727				// get the index of the bookmarks
16728				each(node.childNodes, function(n, index) {
16729					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16730						if (n.id == bookmark.id + "_start") {
16731							startIndex = index;
16732						} else if (n.id == bookmark.id + "_end") {
16733							endIndex = index;
16734						}
16735					}
16736				});
16737				
16738				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16739				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16740					each(tinymce.grep(node.childNodes), process);
16741					return 0;
16742				} else {
16743					currentWrapElm = dom.clone(wrapElm, FALSE);
16744
16745					// create a list of the nodes on the same side of the list as the selection
16746					each(tinymce.grep(node.childNodes), function(n, index) {
16747						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16748							nodes.push(n); 
16749							n.parentNode.removeChild(n);
16750						}
16751					});
16752
16753					// insert the wrapping element either before or after the list.
16754					if (startIndex < listIndex) {
16755						node.insertBefore(currentWrapElm, list);
16756					} else if (startIndex > listIndex) {
16757						node.insertBefore(currentWrapElm, list.nextSibling);
16758					}
16759					
16760					// add the new nodes to the list.
16761					newWrappers.push(currentWrapElm);
16762
16763					each(nodes, function(node) {
16764						currentWrapElm.appendChild(node);
16765					});
16766
16767					return currentWrapElm;
16768				}
16769			};
16770
16771			function applyRngStyle(rng, bookmark, node_specific) {
16772				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16773
16774				// Setup wrapper element
16775				wrapName = format.inline || format.block;
16776				wrapElm = dom.create(wrapName);
16777				setElementFormat(wrapElm);
16778
16779				rangeUtils.walk(rng, function(nodes) {
16780					var currentWrapElm;
16781
16782					function process(node) {
16783						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16784
16785						lastContentEditable = contentEditable;
16786						nodeName = node.nodeName.toLowerCase();
16787						parentName = node.parentNode.nodeName.toLowerCase();
16788
16789						// Node has a contentEditable value
16790						if (node.nodeType === 1 && getContentEditable(node)) {
16791							lastContentEditable = contentEditable;
16792							contentEditable = getContentEditable(node) === "true";
16793							hasContentEditableState = true; // We don't want to wrap the container only it's children
16794						}
16795
16796						// Stop wrapping on br elements
16797						if (isEq(nodeName, 'br')) {
16798							currentWrapElm = 0;
16799
16800							// Remove any br elements when we wrap things
16801							if (format.block)
16802								dom.remove(node);
16803
16804							return;
16805						}
16806
16807						// If node is wrapper type
16808						if (format.wrapper && matchNode(node, name, vars)) {
16809							currentWrapElm = 0;
16810							return;
16811						}
16812
16813						// Can we rename the block
16814						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16815							node = dom.rename(node, wrapName);
16816							setElementFormat(node);
16817							newWrappers.push(node);
16818							currentWrapElm = 0;
16819							return;
16820						}
16821
16822						// Handle selector patterns
16823						if (format.selector) {
16824							// Look for matching formats
16825							each(formatList, function(format) {
16826								// Check collapsed state if it exists
16827								if ('collapsed' in format && format.collapsed !== isCollapsed) {
16828									return;
16829								}
16830
16831								if (dom.is(node, format.selector) && !isCaretNode(node)) {
16832									setElementFormat(node, format);
16833									found = true;
16834								}
16835							});
16836
16837							// Continue processing if a selector match wasn't found and a inline element is defined
16838							if (!format.inline || found) {
16839								currentWrapElm = 0;
16840								return;
16841							}
16842						}
16843
16844						// Is it valid to wrap this item
16845						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16846								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) {
16847							// Start wrapping
16848							if (!currentWrapElm) {
16849								// Wrap the node
16850								currentWrapElm = dom.clone(wrapElm, FALSE);
16851								node.parentNode.insertBefore(currentWrapElm, node);
16852								newWrappers.push(currentWrapElm);
16853							}
16854
16855							currentWrapElm.appendChild(node);
16856						} else if (nodeName == 'li' && bookmark) {
16857							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16858							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16859						} else {
16860							// Start a new wrapper for possible children
16861							currentWrapElm = 0;
16862							
16863							each(tinymce.grep(node.childNodes), process);
16864
16865							if (hasContentEditableState) {
16866								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16867							}
16868
16869							// End the last wrapper
16870							currentWrapElm = 0;
16871						}
16872					};
16873
16874					// Process siblings from range
16875					each(nodes, process);
16876				});
16877
16878				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
16879				if (format.wrap_links === false) {
16880					each(newWrappers, function(node) {
16881						function process(node) {
16882							var i, currentWrapElm, children;
16883
16884							if (node.nodeName === 'A') {
16885								currentWrapElm = dom.clone(wrapElm, FALSE);
16886								newWrappers.push(currentWrapElm);
16887
16888								children = tinymce.grep(node.childNodes);
16889								for (i = 0; i < children.length; i++)
16890									currentWrapElm.appendChild(children[i]);
16891
16892								node.appendChild(currentWrapElm);
16893							}
16894
16895							each(tinymce.grep(node.childNodes), process);
16896						};
16897
16898						process(node);
16899					});
16900				}
16901
16902				// Cleanup
16903				
16904				each(newWrappers, function(node) {
16905					var childCount;
16906
16907					function getChildCount(node) {
16908						var count = 0;
16909
16910						each(node.childNodes, function(node) {
16911							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16912								count++;
16913						});
16914
16915						return count;
16916					};
16917
16918					function mergeStyles(node) {
16919						var child, clone;
16920
16921						each(node.childNodes, function(node) {
16922							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16923								child = node;
16924								return FALSE; // break loop
16925							}
16926						});
16927
16928						// If child was found and of the same type as the current node
16929						if (child && matchName(child, format)) {
16930							clone = dom.clone(child, FALSE);
16931							setElementFormat(clone);
16932
16933							dom.replace(clone, node, TRUE);
16934							dom.remove(child, 1);
16935						}
16936
16937						return clone || node;
16938					};
16939
16940					childCount = getChildCount(node);
16941
16942					// Remove empty nodes but only if there is multiple wrappers and they are not block
16943					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16944					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16945						dom.remove(node, 1);
16946						return;
16947					}
16948
16949					if (format.inline || format.wrapper) {
16950						// Merges the current node with it's children of similar type to reduce the number of elements
16951						if (!format.exact && childCount === 1)
16952							node = mergeStyles(node);
16953
16954						// Remove/merge children
16955						each(formatList, function(format) {
16956							// Merge all children of similar type will move styles from child to parent
16957							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16958							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16959							each(dom.select(format.inline, node), function(child) {
16960								var parent;
16961
16962								// When wrap_links is set to false we don't want
16963								// to remove the format on children within links
16964								if (format.wrap_links === false) {
16965									parent = child.parentNode;
16966
16967									do {
16968										if (parent.nodeName === 'A')
16969											return;
16970									} while (parent = parent.parentNode);
16971								}
16972
16973								removeFormat(format, vars, child, format.exact ? child : null);
16974							});
16975						});
16976
16977						// Remove child if direct parent is of same type
16978						if (matchNode(node.parentNode, name, vars)) {
16979							dom.remove(node, 1);
16980							node = 0;
16981							return TRUE;
16982						}
16983
16984						// Look for parent with similar style format
16985						if (format.merge_with_parents) {
16986							dom.getParent(node.parentNode, function(parent) {
16987								if (matchNode(parent, name, vars)) {
16988									dom.remove(node, 1);
16989									node = 0;
16990									return TRUE;
16991								}
16992							});
16993						}
16994
16995						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16996						if (node && format.merge_siblings !== false) {
16997							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16998							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16999						}
17000					}
17001				});
17002			};
17003
17004			if (format) {
17005				if (node) {
17006					if (node.nodeType) {
17007						rng = dom.createRng();
17008						rng.setStartBefore(node);
17009						rng.setEndAfter(node);
17010						applyRngStyle(expandRng(rng, formatList), null, true);
17011					} else {
17012						applyRngStyle(node, null, true);
17013					}
17014				} else {
17015					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
17016						// Obtain selection node before selection is unselected by applyRngStyle()
17017						var curSelNode = ed.selection.getNode();
17018
17019						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
17020						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
17021						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
17022							apply(formatList[0].defaultBlock);
17023						}
17024
17025						// Apply formatting to selection
17026						ed.selection.setRng(adjustSelectionToVisibleSelection());
17027						bookmark = selection.getBookmark();
17028						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
17029
17030						// Colored nodes should be underlined so that the color of the underline matches the text color.
17031						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
17032							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
17033							processUnderlineAndColor(curSelNode);
17034						}
17035
17036						selection.moveToBookmark(bookmark);
17037						moveStart(selection.getRng(TRUE));
17038						ed.nodeChanged();
17039					} else
17040						performCaretAction('apply', name, vars);
17041				}
17042			}
17043		};
17044
17045		function remove(name, vars, node) {
17046			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
17047
17048			// Merges the styles for each node
17049			function process(node) {
17050				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
17051
17052				// Skip on text nodes as they have neither format to remove nor children
17053				if (node.nodeType === 3) {
17054					return;
17055				}
17056
17057				// Node has a contentEditable value
17058				if (node.nodeType === 1 && getContentEditable(node)) {
17059					lastContentEditable = contentEditable;
17060					contentEditable = getContentEditable(node) === "true";
17061					hasContentEditableState = true; // We don't want to wrap the container only it's children
17062				}
17063
17064				// Grab the children first since the nodelist might be changed
17065				children = tinymce.grep(node.childNodes);
17066
17067				// Process current node
17068				if (contentEditable && !hasContentEditableState) {
17069					for (i = 0, l = formatList.length; i < l; i++) {
17070						if (removeFormat(formatList[i], vars, node, node))
17071							break;
17072					}
17073				}
17074
17075				// Process the children
17076				if (format.deep) {
17077					if (children.length) {					
17078						for (i = 0, l = children.length; i < l; i++)
17079							process(children[i]);
17080
17081						if (hasContentEditableState) {
17082							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
17083						}
17084					}
17085				}
17086			};
17087
17088			function findFormatRoot(container) {
17089				var formatRoot;
17090
17091				// Find format root
17092				each(getParents(container.parentNode).reverse(), function(parent) {
17093					var format;
17094
17095					// Find format root element
17096					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
17097						// Is the node matching the format we are looking for
17098						format = matchNode(parent, name, vars);
17099						if (format && format.split !== false)
17100							formatRoot = parent;
17101					}
17102				});
17103
17104				return formatRoot;
17105			};
17106
17107			function wrapAndSplit(format_root, container, target, split) {
17108				var parent, clone, lastClone, firstClone, i, formatRootParent;
17109
17110				// Format root found then clone formats and split it
17111				if (format_root) {
17112					formatRootParent = format_root.parentNode;
17113
17114					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
17115						clone = dom.clone(parent, FALSE);
17116
17117						for (i = 0; i < formatList.length; i++) {
17118							if (removeFormat(formatList[i], vars, clone, clone)) {
17119								clone = 0;
17120								break;
17121							}
17122						}
17123
17124						// Build wrapper node
17125						if (clone) {
17126							if (lastClone)
17127								clone.appendChild(lastClone);
17128
17129							if (!firstClone)
17130								firstClone = clone;
17131
17132							lastClone = clone;
17133						}
17134					}
17135
17136					// Never split block elements if the format is mixed
17137					if (split && (!format.mixed || !isBlock(format_root)))
17138						container = dom.split(format_root, container);
17139
17140					// Wrap container in cloned formats
17141					if (lastClone) {
17142						target.parentNode.insertBefore(lastClone, target);
17143						firstClone.appendChild(target);
17144					}
17145				}
17146
17147				return container;
17148			};
17149
17150			function splitToFormatRoot(container) {
17151				return wrapAndSplit(findFormatRoot(container), container, container, true);
17152			};
17153
17154			function unwrap(start) {
17155				var node = dom.get(start ? '_start' : '_end'),
17156					out = node[start ? 'firstChild' : 'lastChild'];
17157
17158				// If the end is placed within the start the result will be removed
17159				// So this checks if the out node is a bookmark node if it is it
17160				// checks for another more suitable node
17161				if (isBookmarkNode(out))
17162					out = out[start ? 'firstChild' : 'lastChild'];
17163
17164				dom.remove(node, true);
17165
17166				return out;
17167			};
17168
17169			function removeRngStyle(rng) {
17170				var startContainer, endContainer, node;
17171
17172				rng = expandRng(rng, formatList, TRUE);
17173
17174				if (format.split) {
17175					startContainer = getContainer(rng, TRUE);
17176					endContainer = getContainer(rng);
17177
17178					if (startContainer != endContainer) {
17179						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
17180						// This will happen if you tripple click a table cell and use remove formatting
17181						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
17182							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
17183						}
17184
17185						// Wrap start/end nodes in span element since these might be cloned/moved
17186						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
17187						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
17188
17189						// Split start/end
17190						splitToFormatRoot(startContainer);
17191						splitToFormatRoot(endContainer);
17192
17193						// Unwrap start/end to get real elements again
17194						startContainer = unwrap(TRUE);
17195						endContainer = unwrap();
17196					} else
17197						startContainer = endContainer = splitToFormatRoot(startContainer);
17198
17199					// Update range positions since they might have changed after the split operations
17200					rng.startContainer = startContainer.parentNode;
17201					rng.startOffset = nodeIndex(startContainer);
17202					rng.endContainer = endContainer.parentNode;
17203					rng.endOffset = nodeIndex(endContainer) + 1;
17204				}
17205
17206				// Remove items between start/end
17207				rangeUtils.walk(rng, function(nodes) {
17208					each(nodes, function(node) {
17209						process(node);
17210
17211						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
17212						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
17213							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
17214						}
17215					});
17216				});
17217			};
17218
17219			// Handle node
17220			if (node) {
17221				if (node.nodeType) {
17222					rng = dom.createRng();
17223					rng.setStartBefore(node);
17224					rng.setEndAfter(node);
17225					removeRngStyle(rng);
17226				} else {
17227					removeRngStyle(node);
17228				}
17229
17230				return;
17231			}
17232
17233			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
17234				bookmark = selection.getBookmark();
17235				removeRngStyle(selection.getRng(TRUE));
17236				selection.moveToBookmark(bookmark);
17237
17238				// 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
17239				if (format.inline && match(name, vars, selection.getStart())) {
17240					moveStart(selection.getRng(true));
17241				}
17242
17243				ed.nodeChanged();
17244			} else
17245				performCaretAction('remove', name, vars);
17246		};
17247
17248		function toggle(name, vars, node) {
17249			var fmt = get(name);
17250
17251			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
17252				remove(name, vars, node);
17253			else
17254				apply(name, vars, node);
17255		};
17256
17257		function matchNode(node, name, vars, similar) {
17258			var formatList = get(name), format, i, classes;
17259
17260			function matchItems(node, format, item_name) {
17261				var key, value, items = format[item_name], i;
17262
17263				// Custom match
17264				if (format.onmatch) {
17265					return format.onmatch(node, format, item_name);
17266				}
17267
17268				// Check all items
17269				if (items) {
17270					// Non indexed object
17271					if (items.length === undef) {
17272						for (key in items) {
17273							if (items.hasOwnProperty(key)) {
17274								if (item_name === 'attributes')
17275									value = dom.getAttrib(node, key);
17276								else
17277									value = getStyle(node, key);
17278
17279								if (similar && !value && !format.exact)
17280									return;
17281
17282								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
17283									return;
17284							}
17285						}
17286					} else {
17287						// Only one match needed for indexed arrays
17288						for (i = 0; i < items.length; i++) {
17289							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
17290								return format;
17291						}
17292					}
17293				}
17294
17295				return format;
17296			};
17297
17298			if (formatList && node) {
17299				// Check each format in list
17300				for (i = 0; i < formatList.length; i++) {
17301					format = formatList[i];
17302
17303					// Name name, attributes, styles and classes
17304					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
17305						// Match classes
17306						if (classes = format.classes) {
17307							for (i = 0; i < classes.length; i++) {
17308								if (!dom.hasClass(node, classes[i]))
17309									return;
17310							}
17311						}
17312
17313						return format;
17314					}
17315				}
17316			}
17317		};
17318
17319		function match(name, vars, node) {
17320			var startNode;
17321
17322			function matchParents(node) {
17323				// Find first node with similar format settings
17324				node = dom.getParent(node, function(node) {
17325					return !!matchNode(node, name, vars, true);
17326				});
17327
17328				// Do an exact check on the similar format element
17329				return matchNode(node, name, vars);
17330			};
17331
17332			// Check specified node
17333			if (node)
17334				return matchParents(node);
17335
17336			// Check selected node
17337			node = selection.getNode();
17338			if (matchParents(node))
17339				return TRUE;
17340
17341			// Check start node if it's different
17342			startNode = selection.getStart();
17343			if (startNode != node) {
17344				if (matchParents(startNode))
17345					return TRUE;
17346			}
17347
17348			return FALSE;
17349		};
17350
17351		function matchAll(names, vars) {
17352			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
17353
17354			// Check start of selection for formats
17355			startElement = selection.getStart();
17356			dom.getParent(startElement, function(node) {
17357				var i, name;
17358
17359				for (i = 0; i < names.length; i++) {
17360					name = names[i];
17361
17362					if (!checkedMap[name] && matchNode(node, name, vars)) {
17363						checkedMap[name] = true;
17364						matchedFormatNames.push(name);
17365					}
17366				}
17367			}, dom.getRoot());
17368
17369			return matchedFormatNames;
17370		};
17371
17372		function canApply(name) {
17373			var formatList = get(name), startNode, parents, i, x, selector;
17374
17375			if (formatList) {
17376				startNode = selection.getStart();
17377				parents = getParents(startNode);
17378
17379				for (x = formatList.length - 1; x >= 0; x--) {
17380					selector = formatList[x].selector;
17381
17382					// Format is not selector based, then always return TRUE
17383					if (!selector)
17384						return TRUE;
17385
17386					for (i = parents.length - 1; i >= 0; i--) {
17387						if (dom.is(parents[i], selector))
17388							return TRUE;
17389					}
17390				}
17391			}
17392
17393			return FALSE;
17394		};
17395
17396		function formatChanged(formats, callback, similar) {
17397			var currentFormats;
17398
17399			// Setup format node change logic
17400			if (!formatChangeData) {
17401				formatChangeData = {};
17402				currentFormats = {};
17403
17404				ed.onNodeChange.addToTop(function(ed, cm, node) {
17405					var parents = getParents(node), matchedFormats = {};
17406
17407					// Check for new formats
17408					each(formatChangeData, function(callbacks, format) {
17409						each(parents, function(node) {
17410							if (matchNode(node, format, {}, callbacks.similar)) {
17411								if (!currentFormats[format]) {
17412									// Execute callbacks
17413									each(callbacks, function(callback) {
17414										callback(true, {node: node, format: format, parents: parents});
17415									});
17416
17417									currentFormats[format] = callbacks;
17418								}
17419
17420								matchedFormats[format] = callbacks;
17421								return false;
17422							}
17423						});
17424					});
17425
17426					// Check if current formats still match
17427					each(currentFormats, function(callbacks, format) {
17428						if (!matchedFormats[format]) {
17429							delete currentFormats[format];
17430
17431							each(callbacks, function(callback) {
17432								callback(false, {node: node, format: format, parents: parents});
17433							});
17434						}
17435					});
17436				});
17437			}
17438
17439			// Add format listeners
17440			each(formats.split(','), function(format) {
17441				if (!formatChangeData[format]) {
17442					formatChangeData[format] = [];
17443					formatChangeData[format].similar = similar;
17444				}
17445
17446				formatChangeData[format].push(callback);
17447			});
17448
17449			return this;
17450		};
17451
17452		// Expose to public
17453		tinymce.extend(this, {
17454			get : get,
17455			register : register,
17456			apply : apply,
17457			remove : remove,
17458			toggle : toggle,
17459			match : match,
17460			matchAll : matchAll,
17461			matchNode : matchNode,
17462			canApply : canApply,
17463			formatChanged: formatChanged
17464		});
17465
17466		// Initialize
17467		defaultFormats();
17468		addKeyboardShortcuts();
17469
17470		// Private functions
17471
17472		function matchName(node, format) {
17473			// Check for inline match
17474			if (isEq(node, format.inline))
17475				return TRUE;
17476
17477			// Check for block match
17478			if (isEq(node, format.block))
17479				return TRUE;
17480
17481			// Check for selector match
17482			if (format.selector)
17483				return dom.is(node, format.selector);
17484		};
17485
17486		function isEq(str1, str2) {
17487			str1 = str1 || '';
17488			str2 = str2 || '';
17489
17490			str1 = '' + (str1.nodeName || str1);
17491			str2 = '' + (str2.nodeName || str2);
17492
17493			return str1.toLowerCase() == str2.toLowerCase();
17494		};
17495
17496		function getStyle(node, name) {
17497			var styleVal = dom.getStyle(node, name);
17498
17499			// Force the format to hex
17500			if (name == 'color' || name == 'backgroundColor')
17501				styleVal = dom.toHex(styleVal);
17502
17503			// Opera will return bold as 700
17504			if (name == 'fontWeight' && styleVal == 700)
17505				styleVal = 'bold';
17506
17507			return '' + styleVal;
17508		};
17509
17510		function replaceVars(value, vars) {
17511			if (typeof(value) != "string")
17512				value = value(vars);
17513			else if (vars) {
17514				value = value.replace(/%(\w+)/g, function(str, name) {
17515					return vars[name] || str;
17516				});
17517			}
17518
17519			return value;
17520		};
17521
17522		function isWhiteSpaceNode(node) {
17523			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17524		};
17525
17526		function wrap(node, name, attrs) {
17527			var wrapper = dom.create(name, attrs);
17528
17529			node.parentNode.insertBefore(wrapper, node);
17530			wrapper.appendChild(node);
17531
17532			return wrapper;
17533		};
17534
17535		function expandRng(rng, format, remove) {
17536			var sibling, lastIdx, leaf, endPoint,
17537				startContainer = rng.startContainer,
17538				startOffset = rng.startOffset,
17539				endContainer = rng.endContainer,
17540				endOffset = rng.endOffset;
17541
17542			// This function walks up the tree if there is no siblings before/after the node
17543			function findParentContainer(start) {
17544				var container, parent, child, sibling, siblingName, root;
17545
17546				container = parent = start ? startContainer : endContainer;
17547				siblingName = start ? 'previousSibling' : 'nextSibling';
17548				root = dom.getRoot();
17549
17550				function isBogusBr(node) {
17551					return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
17552				};
17553
17554				// If it's a text node and the offset is inside the text
17555				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17556					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17557						return container;
17558					}
17559				}
17560
17561				for (;;) {
17562					// Stop expanding on block elements
17563					if (!format[0].block_expand && isBlock(parent))
17564						return parent;
17565
17566					// Walk left/right
17567					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17568						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
17569							return parent;
17570						}
17571					}
17572
17573					// Check if we can move up are we at root level or body level
17574					if (parent.parentNode == root) {
17575						container = parent;
17576						break;
17577					}
17578
17579					parent = parent.parentNode;
17580				}
17581
17582				return container;
17583			};
17584
17585			// This function walks down the tree to find the leaf at the selection.
17586			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17587			function findLeaf(node, offset) {
17588				if (offset === undef)
17589					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17590				while (node && node.hasChildNodes()) {
17591					node = node.childNodes[offset];
17592					if (node)
17593						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17594				}
17595				return { node: node, offset: offset };
17596			}
17597
17598			// If index based start position then resolve it
17599			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17600				lastIdx = startContainer.childNodes.length - 1;
17601				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17602
17603				if (startContainer.nodeType == 3)
17604					startOffset = 0;
17605			}
17606
17607			// If index based end position then resolve it
17608			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17609				lastIdx = endContainer.childNodes.length - 1;
17610				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17611
17612				if (endContainer.nodeType == 3)
17613					endOffset = endContainer.nodeValue.length;
17614			}
17615
17616			// Expands the node to the closes contentEditable false element if it exists
17617			function findParentContentEditable(node) {
17618				var parent = node;
17619
17620				while (parent) {
17621					if (parent.nodeType === 1 && getContentEditable(parent)) {
17622						return getContentEditable(parent) === "false" ? parent : node;
17623					}
17624
17625					parent = parent.parentNode;
17626				}
17627
17628				return node;
17629			};
17630
17631			function findWordEndPoint(container, offset, start) {
17632				var walker, node, pos, lastTextNode;
17633
17634				function findSpace(node, offset) {
17635					var pos, pos2, str = node.nodeValue;
17636
17637					if (typeof(offset) == "undefined") {
17638						offset = start ? str.length : 0;
17639					}
17640
17641					if (start) {
17642						pos = str.lastIndexOf(' ', offset);
17643						pos2 = str.lastIndexOf('\u00a0', offset);
17644						pos = pos > pos2 ? pos : pos2;
17645
17646						// Include the space on remove to avoid tag soup
17647						if (pos !== -1 && !remove) {
17648							pos++;
17649						}
17650					} else {
17651						pos = str.indexOf(' ', offset);
17652						pos2 = str.indexOf('\u00a0', offset);
17653						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17654					}
17655
17656					return pos;
17657				};
17658
17659				if (container.nodeType === 3) {
17660					pos = findSpace(container, offset);
17661
17662					if (pos !== -1) {
17663						return {container : container, offset : pos};
17664					}
17665
17666					lastTextNode = container;
17667				}
17668
17669				// Walk the nodes inside the block
17670				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17671				while (node = walker[start ? 'prev' : 'next']()) {
17672					if (node.nodeType === 3) {
17673						lastTextNode = node;
17674						pos = findSpace(node);
17675
17676						if (pos !== -1) {
17677							return {container : node, offset : pos};
17678						}
17679					} else if (isBlock(node)) {
17680						break;
17681					}
17682				}
17683
17684				if (lastTextNode) {
17685					if (start) {
17686						offset = 0;
17687					} else {
17688						offset = lastTextNode.length;
17689					}
17690
17691					return {container: lastTextNode, offset: offset};
17692				}
17693			};
17694
17695			function findSelectorEndPoint(container, sibling_name) {
17696				var parents, i, y, curFormat;
17697
17698				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17699					container = container[sibling_name];
17700
17701				parents = getParents(container);
17702				for (i = 0; i < parents.length; i++) {
17703					for (y = 0; y < format.length; y++) {
17704						curFormat = format[y];
17705
17706						// If collapsed state is set then skip formats that doesn't match that
17707						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17708							continue;
17709
17710						if (dom.is(parents[i], curFormat.selector))
17711							return parents[i];
17712					}
17713				}
17714
17715				return container;
17716			};
17717
17718			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17719				var node;
17720
17721				// Expand to block of similar type
17722				if (!format[0].wrapper)
17723					node = dom.getParent(container, format[0].block);
17724
17725				// Expand to first wrappable block element or any block element
17726				if (!node)
17727					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock);
17728
17729				// Exclude inner lists from wrapping
17730				if (node && format[0].wrapper)
17731					node = getParents(node, 'ul,ol').reverse()[0] || node;
17732
17733				// Didn't find a block element look for first/last wrappable element
17734				if (!node) {
17735					node = container;
17736
17737					while (node[sibling_name] && !isBlock(node[sibling_name])) {
17738						node = node[sibling_name];
17739
17740						// Break on BR but include it will be removed later on
17741						// we can't remove it now since we need to check if it can be wrapped
17742						if (isEq(node, 'br'))
17743							break;
17744					}
17745				}
17746
17747				return node || container;
17748			};
17749
17750			// Expand to closest contentEditable element
17751			startContainer = findParentContentEditable(startContainer);
17752			endContainer = findParentContentEditable(endContainer);
17753
17754			// Exclude bookmark nodes if possible
17755			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17756				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17757				startContainer = startContainer.nextSibling || startContainer;
17758
17759				if (startContainer.nodeType == 3)
17760					startOffset = 0;
17761			}
17762
17763			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17764				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17765				endContainer = endContainer.previousSibling || endContainer;
17766
17767				if (endContainer.nodeType == 3)
17768					endOffset = endContainer.length;
17769			}
17770
17771			if (format[0].inline) {
17772				if (rng.collapsed) {
17773					// Expand left to closest word boundery
17774					endPoint = findWordEndPoint(startContainer, startOffset, true);
17775					if (endPoint) {
17776						startContainer = endPoint.container;
17777						startOffset = endPoint.offset;
17778					}
17779
17780					// Expand right to closest word boundery
17781					endPoint = findWordEndPoint(endContainer, endOffset);
17782					if (endPoint) {
17783						endContainer = endPoint.container;
17784						endOffset = endPoint.offset;
17785					}
17786				}
17787
17788				// Avoid applying formatting to a trailing space.
17789				leaf = findLeaf(endContainer, endOffset);
17790				if (leaf.node) {
17791					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17792						leaf = findLeaf(leaf.node.previousSibling);
17793
17794					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17795							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17796
17797						if (leaf.offset > 1) {
17798							endContainer = leaf.node;
17799							endContainer.splitText(leaf.offset - 1);
17800						}
17801					}
17802				}
17803			}
17804
17805			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
17806			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17807			// This will reduce the number of wrapper elements that needs to be created
17808			// Move start point up the tree
17809			if (format[0].inline || format[0].block_expand) {
17810				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17811					startContainer = findParentContainer(true);
17812				}
17813
17814				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17815					endContainer = findParentContainer();
17816				}
17817			}
17818
17819			// Expand start/end container to matching selector
17820			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17821				// Find new startContainer/endContainer if there is better one
17822				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17823				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17824			}
17825
17826			// Expand start/end container to matching block element or text node
17827			if (format[0].block || format[0].selector) {
17828				// Find new startContainer/endContainer if there is better one
17829				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17830				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17831
17832				// Non block element then try to expand up the leaf
17833				if (format[0].block) {
17834					if (!isBlock(startContainer))
17835						startContainer = findParentContainer(true);
17836
17837					if (!isBlock(endContainer))
17838						endContainer = findParentContainer();
17839				}
17840			}
17841
17842			// Setup index for startContainer
17843			if (startContainer.nodeType == 1) {
17844				startOffset = nodeIndex(startContainer);
17845				startContainer = startContainer.parentNode;
17846			}
17847
17848			// Setup index for endContainer
17849			if (endContainer.nodeType == 1) {
17850				endOffset = nodeIndex(endContainer) + 1;
17851				endContainer = endContainer.parentNode;
17852			}
17853
17854			// Return new range like object
17855			return {
17856				startContainer : startContainer,
17857				startOffset : startOffset,
17858				endContainer : endContainer,
17859				endOffset : endOffset
17860			};
17861		}
17862
17863		function removeFormat(format, vars, node, compare_node) {
17864			var i, attrs, stylesModified;
17865
17866			// Check if node matches format
17867			if (!matchName(node, format))
17868				return FALSE;
17869
17870			// Should we compare with format attribs and styles
17871			if (format.remove != 'all') {
17872				// Remove styles
17873				each(format.styles, function(value, name) {
17874					value = replaceVars(value, vars);
17875
17876					// Indexed array
17877					if (typeof(name) === 'number') {
17878						name = value;
17879						compare_node = 0;
17880					}
17881
17882					if (!compare_node || isEq(getStyle(compare_node, name), value))
17883						dom.setStyle(node, name, '');
17884
17885					stylesModified = 1;
17886				});
17887
17888				// Remove style attribute if it's empty
17889				if (stylesModified && dom.getAttrib(node, 'style') == '') {
17890					node.removeAttribute('style');
17891					node.removeAttribute('data-mce-style');
17892				}
17893
17894				// Remove attributes
17895				each(format.attributes, function(value, name) {
17896					var valueOut;
17897
17898					value = replaceVars(value, vars);
17899
17900					// Indexed array
17901					if (typeof(name) === 'number') {
17902						name = value;
17903						compare_node = 0;
17904					}
17905
17906					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17907						// Keep internal classes
17908						if (name == 'class') {
17909							value = dom.getAttrib(node, name);
17910							if (value) {
17911								// Build new class value where everything is removed except the internal prefixed classes
17912								valueOut = '';
17913								each(value.split(/\s+/), function(cls) {
17914									if (/mce\w+/.test(cls))
17915										valueOut += (valueOut ? ' ' : '') + cls;
17916								});
17917
17918								// We got some internal classes left
17919								if (valueOut) {
17920									dom.setAttrib(node, name, valueOut);
17921									return;
17922								}
17923							}
17924						}
17925
17926						// IE6 has a bug where the attribute doesn't get removed correctly
17927						if (name == "class")
17928							node.removeAttribute('className');
17929
17930						// Remove mce prefixed attributes
17931						if (MCE_ATTR_RE.test(name))
17932							node.removeAttribute('data-mce-' + name);
17933
17934						node.removeAttribute(name);
17935					}
17936				});
17937
17938				// Remove classes
17939				each(format.classes, function(value) {
17940					value = replaceVars(value, vars);
17941
17942					if (!compare_node || dom.hasClass(compare_node, value))
17943						dom.removeClass(node, value);
17944				});
17945
17946				// Check for non internal attributes
17947				attrs = dom.getAttribs(node);
17948				for (i = 0; i < attrs.length; i++) {
17949					if (attrs[i].nodeName.indexOf('_') !== 0)
17950						return FALSE;
17951				}
17952			}
17953
17954			// Remove the inline child if it's empty for example <b> or <span>
17955			if (format.remove != 'none') {
17956				removeNode(node, format);
17957				return TRUE;
17958			}
17959		};
17960
17961		function removeNode(node, format) {
17962			var parentNode = node.parentNode, rootBlockElm;
17963
17964			function find(node, next, inc) {
17965				node = getNonWhiteSpaceSibling(node, next, inc);
17966
17967				return !node || (node.nodeName == 'BR' || isBlock(node));
17968			};
17969
17970			if (format.block) {
17971				if (!forcedRootBlock) {
17972					// Append BR elements if needed before we remove the block
17973					if (isBlock(node) && !isBlock(parentNode)) {
17974						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17975							node.insertBefore(dom.create('br'), node.firstChild);
17976
17977						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17978							node.appendChild(dom.create('br'));
17979					}
17980				} else {
17981					// Wrap the block in a forcedRootBlock if we are at the root of document
17982					if (parentNode == dom.getRoot()) {
17983						if (!format.list_block || !isEq(node, format.list_block)) {
17984							each(tinymce.grep(node.childNodes), function(node) {
17985								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17986									if (!rootBlockElm)
17987										rootBlockElm = wrap(node, forcedRootBlock);
17988									else
17989										rootBlockElm.appendChild(node);
17990								} else
17991									rootBlockElm = 0;
17992							});
17993						}
17994					}
17995				}
17996			}
17997
17998			// Never remove nodes that isn't the specified inline element if a selector is specified too
17999			if (format.selector && format.inline && !isEq(format.inline, node))
18000				return;
18001
18002			dom.remove(node, 1);
18003		};
18004
18005		function getNonWhiteSpaceSibling(node, next, inc) {
18006			if (node) {
18007				next = next ? 'nextSibling' : 'previousSibling';
18008
18009				for (node = inc ? node : node[next]; node; node = node[next]) {
18010					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
18011						return node;
18012				}
18013			}
18014		};
18015
18016		function isBookmarkNode(node) {
18017			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
18018		};
18019
18020		function mergeSiblings(prev, next) {
18021			var marker, sibling, tmpSibling;
18022
18023			function compareElements(node1, node2) {
18024				// Not the same name
18025				if (node1.nodeName != node2.nodeName)
18026					return FALSE;
18027
18028				function getAttribs(node) {
18029					var attribs = {};
18030
18031					each(dom.getAttribs(node), function(attr) {
18032						var name = attr.nodeName.toLowerCase();
18033
18034						// Don't compare internal attributes or style
18035						if (name.indexOf('_') !== 0 && name !== 'style')
18036							attribs[name] = dom.getAttrib(node, name);
18037					});
18038
18039					return attribs;
18040				};
18041
18042				function compareObjects(obj1, obj2) {
18043					var value, name;
18044
18045					for (name in obj1) {
18046						// Obj1 has item obj2 doesn't have
18047						if (obj1.hasOwnProperty(name)) {
18048							value = obj2[name];
18049
18050							// Obj2 doesn't have obj1 item
18051							if (value === undef)
18052								return FALSE;
18053
18054							// Obj2 item has a different value
18055							if (obj1[name] != value)
18056								return FALSE;
18057
18058							// Delete similar value
18059							delete obj2[name];
18060						}
18061					}
18062
18063					// Check if obj 2 has something obj 1 doesn't have
18064					for (name in obj2) {
18065						// Obj2 has item obj1 doesn't have
18066						if (obj2.hasOwnProperty(name))
18067							return FALSE;
18068					}
18069
18070					return TRUE;
18071				};
18072
18073				// Attribs are not the same
18074				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
18075					return FALSE;
18076
18077				// Styles are not the same
18078				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
18079					return FALSE;
18080
18081				return TRUE;
18082			};
18083
18084			function findElementSibling(node, sibling_name) {
18085				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
18086					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
18087						return node;
18088
18089					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
18090						return sibling;
18091				}
18092
18093				return node;
18094			};
18095
18096			// Check if next/prev exists and that they are elements
18097			if (prev && next) {
18098				// If previous sibling is empty then jump over it
18099				prev = findElementSibling(prev, 'previousSibling');
18100				next = findElementSibling(next, 'nextSibling');
18101
18102				// Compare next and previous nodes
18103				if (compareElements(prev, next)) {
18104					// Append nodes between
18105					for (sibling = prev.nextSibling; sibling && sibling != next;) {
18106						tmpSibling = sibling;
18107						sibling = sibling.nextSibling;
18108						prev.appendChild(tmpSibling);
18109					}
18110
18111					// Remove next node
18112					dom.remove(next);
18113
18114					// Move children into prev node
18115					each(tinymce.grep(next.childNodes), function(node) {
18116						prev.appendChild(node);
18117					});
18118
18119					return prev;
18120				}
18121			}
18122
18123			return next;
18124		};
18125
18126		function getContainer(rng, start) {
18127			var container, offset, lastIdx, walker;
18128
18129			container = rng[start ? 'startContainer' : 'endContainer'];
18130			offset = rng[start ? 'startOffset' : 'endOffset'];
18131
18132			if (container.nodeType == 1) {
18133				lastIdx = container.childNodes.length - 1;
18134
18135				if (!start && offset)
18136					offset--;
18137
18138				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
18139			}
18140
18141			// If start text node is excluded then walk to the next node
18142			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
18143				container = new TreeWalker(container, ed.getBody()).next() || container;
18144			}
18145
18146			// If end text node is excluded then walk to the previous node
18147			if (container.nodeType === 3 && !start && offset === 0) {
18148				container = new TreeWalker(container, ed.getBody()).prev() || container;
18149			}
18150
18151			return container;
18152		};
18153
18154		function performCaretAction(type, name, vars) {
18155			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
18156
18157			// Creates a caret container bogus element
18158			function createCaretContainer(fill) {
18159				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
18160
18161				if (fill) {
18162					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
18163				}
18164
18165				return caretContainer;
18166			};
18167
18168			function isCaretContainerEmpty(node, nodes) {
18169				while (node) {
18170					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
18171						return false;
18172					}
18173
18174					// Collect nodes
18175					if (nodes && node.nodeType === 1) {
18176						nodes.push(node);
18177					}
18178
18179					node = node.firstChild;
18180				}
18181
18182				return true;
18183			};
18184			
18185			// Returns any parent caret container element
18186			function getParentCaretContainer(node) {
18187				while (node) {
18188					if (node.id === caretContainerId) {
18189						return node;
18190					}
18191
18192					node = node.parentNode;
18193				}
18194			};
18195
18196			// Finds the first text node in the specified node
18197			function findFirstTextNode(node) {
18198				var walker;
18199
18200				if (node) {
18201					walker = new TreeWalker(node, node);
18202
18203					for (node = walker.current(); node; node = walker.next()) {
18204						if (node.nodeType === 3) {
18205							return node;
18206						}
18207					}
18208				}
18209			};
18210
18211			// Removes the caret container for the specified node or all on the current document
18212			function removeCaretContainer(node, move_caret) {
18213				var child, rng;
18214
18215				if (!node) {
18216					node = getParentCaretContainer(selection.getStart());
18217
18218					if (!node) {
18219						while (node = dom.get(caretContainerId)) {
18220							removeCaretContainer(node, false);
18221						}
18222					}
18223				} else {
18224					rng = selection.getRng(true);
18225
18226					if (isCaretContainerEmpty(node)) {
18227						if (move_caret !== false) {
18228							rng.setStartBefore(node);
18229							rng.setEndBefore(node);
18230						}
18231
18232						dom.remove(node);
18233					} else {
18234						child = findFirstTextNode(node);
18235
18236						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
18237							child = child.deleteData(0, 1);
18238						}
18239
18240						dom.remove(node, 1);
18241					}
18242
18243					selection.setRng(rng);
18244				}
18245			};
18246			
18247			// Applies formatting to the caret postion
18248			function applyCaretFormat() {
18249				var rng, caretContainer, textNode, offset, bookmark, container, text;
18250
18251				rng = selection.getRng(true);
18252				offset = rng.startOffset;
18253				container = rng.startContainer;
18254				text = container.nodeValue;
18255
18256				caretContainer = getParentCaretContainer(selection.getStart());
18257				if (caretContainer) {
18258					textNode = findFirstTextNode(caretContainer);
18259				}
18260
18261				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
18262				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
18263					// Get bookmark of caret position
18264					bookmark = selection.getBookmark();
18265
18266					// Collapse bookmark range (WebKit)
18267					rng.collapse(true);
18268
18269					// Expand the range to the closest word and split it at those points
18270					rng = expandRng(rng, get(name));
18271					rng = rangeUtils.split(rng);
18272
18273					// Apply the format to the range
18274					apply(name, vars, rng);
18275
18276					// Move selection back to caret position
18277					selection.moveToBookmark(bookmark);
18278				} else {
18279					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
18280						caretContainer = createCaretContainer(true);
18281						textNode = caretContainer.firstChild;
18282
18283						rng.insertNode(caretContainer);
18284						offset = 1;
18285
18286						apply(name, vars, caretContainer);
18287					} else {
18288						apply(name, vars, caretContainer);
18289					}
18290
18291					// Move selection to text node
18292					selection.setCursorLocation(textNode, offset);
18293				}
18294			};
18295
18296			function removeCaretFormat() {
18297				var rng = selection.getRng(true), container, offset, bookmark,
18298					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
18299
18300				container = rng.startContainer;
18301				offset = rng.startOffset;
18302				node = container;
18303
18304				if (container.nodeType == 3) {
18305					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
18306						hasContentAfter = true;
18307					}
18308
18309					node = node.parentNode;
18310				}
18311
18312				while (node) {
18313					if (matchNode(node, name, vars)) {
18314						formatNode = node;
18315						break;
18316					}
18317
18318					if (node.nextSibling) {
18319						hasContentAfter = true;
18320					}
18321
18322					parents.push(node);
18323					node = node.parentNode;
18324				}
18325
18326				// Node doesn't have the specified format
18327				if (!formatNode) {
18328					return;
18329				}
18330
18331				// Is there contents after the caret then remove the format on the element
18332				if (hasContentAfter) {
18333					// Get bookmark of caret position
18334					bookmark = selection.getBookmark();
18335
18336					// Collapse bookmark range (WebKit)
18337					rng.collapse(true);
18338
18339					// Expand the range to the closest word and split it at those points
18340					rng = expandRng(rng, get(name), true);
18341					rng = rangeUtils.split(rng);
18342
18343					// Remove the format from the range
18344					remove(name, vars, rng);
18345
18346					// Move selection back to caret position
18347					selection.moveToBookmark(bookmark);
18348				} else {
18349					caretContainer = createCaretContainer();
18350
18351					node = caretContainer;
18352					for (i = parents.length - 1; i >= 0; i--) {
18353						node.appendChild(dom.clone(parents[i], false));
18354						node = node.firstChild;
18355					}
18356
18357					// Insert invisible character into inner most format element
18358					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
18359					node = node.firstChild;
18360
18361					var block = dom.getParent(formatNode, isTextBlock);
18362
18363					if (block && dom.isEmpty(block)) {
18364						// Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
18365						formatNode.parentNode.replaceChild(caretContainer, formatNode);
18366					} else {
18367						// Insert caret container after the formated node
18368						dom.insertAfter(caretContainer, formatNode);
18369					}
18370
18371					// Move selection to text node
18372					selection.setCursorLocation(node, 1);
18373
18374					// If the formatNode is empty, we can remove it safely. 
18375					if (dom.isEmpty(formatNode)) {
18376						dom.remove(formatNode);
18377					}
18378				}
18379			};
18380
18381			// Checks if the parent caret container node isn't empty if that is the case it
18382			// will remove the bogus state on all children that isn't empty
18383			function unmarkBogusCaretParents() {
18384				var i, caretContainer, node;
18385
18386				caretContainer = getParentCaretContainer(selection.getStart());
18387				if (caretContainer && !dom.isEmpty(caretContainer)) {
18388					tinymce.walk(caretContainer, function(node) {
18389						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
18390							dom.setAttrib(node, 'data-mce-bogus', null);
18391						}
18392					}, 'childNodes');
18393				}
18394			};
18395
18396			// Only bind the caret events once
18397			if (!self._hasCaretEvents) {
18398				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
18399				ed.onBeforeGetContent.addToTop(function() {
18400					var nodes = [], i;
18401
18402					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
18403						// Mark children
18404						i = nodes.length;
18405						while (i--) {
18406							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
18407						}
18408					}
18409				});
18410
18411				// Remove caret container on mouse up and on key up
18412				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
18413					ed[name].addToTop(function() {
18414						removeCaretContainer();
18415						unmarkBogusCaretParents();
18416					});
18417				});
18418
18419				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
18420				ed.onKeyDown.addToTop(function(ed, e) {
18421					var keyCode = e.keyCode;
18422
18423					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
18424						removeCaretContainer(getParentCaretContainer(selection.getStart()));
18425					}
18426
18427					unmarkBogusCaretParents();
18428				});
18429
18430				// Remove bogus state if they got filled by contents using editor.selection.setContent
18431				selection.onSetContent.add(unmarkBogusCaretParents);
18432
18433				self._hasCaretEvents = true;
18434			}
18435
18436			// Do apply or remove caret format
18437			if (type == "apply") {
18438				applyCaretFormat();
18439			} else {
18440				removeCaretFormat();
18441			}
18442		};
18443
18444		function moveStart(rng) {
18445			var container = rng.startContainer,
18446					offset = rng.startOffset, isAtEndOfText,
18447					walker, node, nodes, tmpNode;
18448
18449			// Convert text node into index if possible
18450			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18451				// Get the parent container location and walk from there
18452				offset = nodeIndex(container);
18453				container = container.parentNode;
18454				isAtEndOfText = true;
18455			}
18456
18457			// Move startContainer/startOffset in to a suitable node
18458			if (container.nodeType == 1) {
18459				nodes = container.childNodes;
18460				container = nodes[Math.min(offset, nodes.length - 1)];
18461				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18462
18463				// If offset is at end of the parent node walk to the next one
18464				if (offset >
18464 nodes.length - 1 || isAtEndOfText)
18465					walker.next();
18466
18467				for (node = walker.current(); node; node = walker.next()) {
18468					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18469						// IE has a "neat" feature where it moves the start node into the closest element
18470						// we can avoid this by inserting an element before it and then remove it after we set the selection
18471						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18472						node.parentNode.insertBefore(tmpNode, node);
18473
18474						// Set selection and remove tmpNode
18475						rng.setStart(node, 0);
18476						selection.setRng(rng);
18477						dom.remove(tmpNode);
18478
18479						return;
18480					}
18481				}
18482			}
18483		};
18484	};
18485})(tinymce);
18486tinymce.onAddEditor.add(function(tinymce, ed) {
18487	var filters, fontSizes, dom, settings = ed.settings;
18488
18489	function replaceWithSpan(node, styles) {
18490		tinymce.each(styles, function(value, name) {
18491			if (value)
18492				dom.setStyle(node, name, value);
18493		});
18494
18495		dom.rename(node, 'span');
18496	};
18497
18498	function convert(editor, params) {
18499		dom = editor.dom;
18500
18501		if (settings.convert_fonts_to_spans) {
18502			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18503				filters[node.nodeName.toLowerCase()](ed.dom, node);
18504			});
18505		}
18506	};
18507
18508	if (settings.inline_styles) {
18509		fontSizes = tinymce.explode(settings.font_size_legacy_values);
18510
18511		filters = {
18512			font : function(dom, node) {
18513				replaceWithSpan(node, {
18514					backgroundColor : node.style.backgroundColor,
18515					color : node.color,
18516					fontFamily : node.face,
18517					fontSize : fontSizes[parseInt(node.size, 10) - 1]
18518				});
18519			},
18520
18521			u : function(dom, node) {
18522				replaceWithSpan(node, {
18523					textDecoration : 'underline'
18524				});
18525			},
18526
18527			strike : function(dom, node) {
18528				replaceWithSpan(node, {
18529					textDecoration : 'line-through'
18530				});
18531			}
18532		};
18533
18534		ed.onPreProcess.add(convert);
18535		ed.onSetContent.add(convert);
18536
18537		ed.onInit.add(function() {
18538			ed.selection.onSetContent.add(convert);
18539		});
18540	}
18541});
18542(function(tinymce) {
18543	var TreeWalker = tinymce.dom.TreeWalker;
18544
18545	tinymce.EnterKey = function(editor) {
18546		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18547
18548		function handleEnterKey(evt) {
18549			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
18550				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18551
18552			// Returns true if the block can be split into two blocks or not
18553			function canSplitBlock(node) {
18554				return node &&
18555					dom.isBlock(node) &&
18556					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18557					!/^(fixed|absolute)/i.test(node.style.position) && 
18558					dom.getContentEditable(node) !== "true";
18559			};
18560
18561			// Renders empty block on IE
18562			function renderBlockOnIE(block) {
18563				var oldRng;
18564
18565				if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) {
18566					oldRng = selection.getRng();
18567					block.appendChild(dom.create('span', null, '\u00a0'));
18568					selection.select(block);
18569					block.lastChild.outerHTML = '';
18570					selection.setRng(oldRng);
18571				}
18572			};
18573
18574			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18575			function trimInlineElementsOnLeftSideOfBlock(block) {
18576				var node = block, firstChilds = [], i;
18577
18578				// Find inner most first child ex: <p><i><b>*</b></i></p>
18579				while (node = node.firstChild) {
18580					if (dom.isBlock(node)) {
18581						return;
18582					}
18583
18584					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18585						firstChilds.push(node);
18586					}
18587				}
18588
18589				i = firstChilds.length;
18590				while (i--) {
18591					node = firstChilds[i];
18592					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18593						dom.remove(node);
18594					} else {
18595						// Remove <a> </a> see #5381
18596						if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
18597							dom.remove(node);
18598						}
18599					}
18600				}
18601			};
18602			
18603			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18604			function moveToCaretPosition(root) {
18605				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18606
18607				rng = dom.createRng();
18608
18609				if (root.hasChildNodes()) {
18610					walker = new TreeWalker(root, root);
18611
18612					while (node = walker.current()) {
18613						if (node.nodeType == 3) {
18614							rng.setStart(node, 0);
18615							rng.setEnd(node, 0);
18616							break;
18617						}
18618
18619						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18620							rng.setStartBefore(node);
18621							rng.setEndBefore(node);
18622							break;
18623						}
18624
18625						lastNode = node;
18626						node = walker.next();
18627					}
18628
18629					if (!node) {
18630						rng.setStart(lastNode, 0);
18631						rng.setEnd(lastNode, 0);
18632					}
18633				} else {
18634					if (root.nodeName == 'BR') {
18635						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18636							// Trick on older IE versions to render the caret before the BR between two lists
18637							if (!documentMode || documentMode < 9) {
18638								tempElm = dom.create('br');
18639								root.parentNode.insertBefore(tempElm, root);
18640							}
18641
18642							rng.setStartBefore(root);
18643							rng.setEndBefore(root);
18644						} else {
18645							rng.setStartAfter(root);
18646							rng.setEndAfter(root);
18647						}
18648					} else {
18649						rng.setStart(root, 0);
18650						rng.setEnd(root, 0);
18651					}
18652				}
18653
18654				selection.setRng(rng);
18655
18656				// Remove tempElm created for old IE:s
18657				dom.remove(tempElm);
18658
18659				viewPort = dom.getViewPort(editor.getWin());
18660
18661				// 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
18662				y = dom.getPos(root).y;
18663				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18664					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18665				}
18666			};
18667
18668			// Creates a new block element by cloning the current one or creating a new one if the name is specified
18669			// This function will also copy any text formatting from the parent block and add it to the new one
18670			function createNewBlock(name) {
18671				var node = container, block, clonedNode, caretNode;
18672
18673				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18674				caretNode = block;
18675
18676				// Clone any parent styles
18677				if (settings.keep_styles !== false) {
18678					do {
18679						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18680							// Never clone a caret containers
18681							if (node.id == '_mce_caret') {
18682								continue;
18683							}
18684
18685							clonedNode = node.cloneNode(false);
18686							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18687
18688							if (block.hasChildNodes()) {
18689								clonedNode.appendChild(block.firstChild);
18690								block.appendChild(clonedNode);
18691							} else {
18692								caretNode = clonedNode;
18693								block.appendChild(clonedNode);
18694							}
18695						}
18696					} while (node = node.parentNode);
18697				}
18698
18699				// BR is needed in empty blocks on non IE browsers
18700				if (!tinymce.isIE || tinymce.isIE11) {
18701					caretNode.innerHTML = '<br data-mce-bogus="1">';
18702				}
18703
18704				return block;
18705			};
18706
18707			// Returns true/false if the caret is at the start/end of the parent block element
18708			function isCaretAtStartOrEndOfBlock(start) {
18709				var walker, node, name;
18710
18711				// Caret is in the middle of a text node like "a|b"
18712				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18713					return false;
18714				}
18715
18716				// If after the last element in block node edge case for #5091
18717				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18718					return true;
18719				}
18720
18721				// If the caret if before the first element in parentBlock
18722				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18723					return true;
18724				}
18725
18726				// Caret can be before/after a table
18727				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18728					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18729				}
18730
18731				// Walk the DOM and look for text nodes or non empty elements
18732				walker = new TreeWalker(container, parentBlock);
18733	
18734				// If caret is in beginning or end of a text block then jump to the next/previous node
18735				if (container.nodeType == 3) {
18736					if (start && offset == 0) {
18737						walker.prev();
18738					} else if (!start && offset == container.nodeValue.length) {
18739						walker.next();
18740					}
18741				}
18742
18743				while (node = walker.current()) {
18744					if (node.nodeType === 1) {
18745						// Ignore bogus elements
18746						if (!node.getAttribute('data-mce-bogus')) {
18747							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18748							name = node.nodeName.toLowerCase();
18749							if (nonEmptyElementsMap[name] && name !== 'br') {
18750								return false;
18751							}
18752						}
18753					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18754						return false;
18755					}
18756
18757					if (start) {
18758						walker.prev();
18759					} else {
18760						walker.next();
18761					}
18762				}
18763
18764				return true;
18765			};
18766
18767			// Wraps any text nodes or inline elements in the specified forced root block name
18768			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18769				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18770
18771				// Not in a block element or in a table cell or caption
18772				parentBlock = dom.getParent(container, dom.isBlock);
18773				if (!parentBlock || !canSplitBlock(parentBlock)) {
18774					parentBlock = parentBlock || editableRoot;
18775
18776					if (!parentBlock.hasChildNodes()) {
18777						newBlock = dom.create(blockName);
18778						parentBlock.appendChild(newBlock);
18779						rng.setStart(newBlock, 0);
18780						rng.setEnd(newBlock, 0);
18781						return newBlock;
18782					}
18783
18784					// Find parent that is the first child of parentBlock
18785					node = container;
18786					while (node.parentNode != parentBlock) {
18787						node = node.parentNode;
18788					}
18789
18790					// Loop left to find start node start wrapping at
18791					while (node && !dom.isBlock(node)) {
18792						startNode = node;
18793						node = node.previousSibling;
18794					}
18795
18796					if (startNode) {
18797						newBlock = dom.create(blockName);
18798						startNode.parentNode.insertBefore(newBlock, startNode);
18799
18800						// Start wrapping until we hit a block
18801						node = startNode;
18802						while (node && !dom.isBlock(node)) {
18803							next = node.nextSibling;
18804							newBlock.appendChild(node);
18805							node = next;
18806						}
18807
18808						// Restore range to it's past location
18809						rng.setStart(container, offset);
18810						rng.setEnd(container, offset);
18811					}
18812				}
18813
18814				return container;
18815			};
18816
18817			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
18818			function handleEmptyListItem() {
18819				function isFirstOrLastLi(first) {
18820					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18821
18822					// Find first/last element since there might be whitespace there
18823					while (node) {
18824						if (node.nodeType == 1) {
18825							break;
18826						}
18827
18828						node = node[first ? 'nextSibling' : 'previousSibling'];
18829					}
18830
18831					return node === parentBlock;
18832				};
18833
18834				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18835
18836				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18837					// Is first and last list item then replace the OL/UL with a text block
18838					dom.replace(newBlock, containerBlock);
18839				} else if (isFirstOrLastLi(true)) {
18840					// First LI in list then remove LI and add text block before list
18841					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18842				} else if (isFirstOrLastLi()) {
18843					// Last LI in list then temove LI and add text block after list
18844					dom.insertAfter(newBlock, containerBlock);
18845					renderBlockOnIE(newBlock);
18846				} else {
18847					// Middle LI in list the split the list and insert a text block in the middle
18848					// Extract after fragment and insert it after the current block
18849					tmpRng = rng.cloneRange();
18850					tmpRng.setStartAfter(parentBlock);
18851					tmpRng.setEndAfter(containerBlock);
18852					fragment = tmpRng.extractContents();
18853					dom.insertAfter(fragment, containerBlock);
18854					dom.insertAfter(newBlock, containerBlock);
18855				}
18856
18857				dom.remove(parentBlock);
18858				moveToCaretPosition(newBlock);
18859				undoManager.add();
18860			};
18861
18862			// Walks the parent block to the right and look for any contents
18863			function hasRightSideContent() {
18864				var walker = new TreeWalker(container, parentBlock), node;
18865
18866				while (node = walker.next()) {
18867					if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
18868						return true;
18869					}
18870				}
18871			}
18872
18873			// Inserts a BR element if the forced_root_block option is set to false or empty string
18874			function insertBr() {
18875				var brElm, extraBr, marker;
18876
18877				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18878					// Insert extra BR element at the end block elements
18879					if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) {
18880						brElm = dom.create('br');
18881						rng.insertNode(brElm);
18882						rng.setStartAfter(brElm);
18883						rng.setEndAfter(brElm);
18884						extraBr = true;
18885					}
18886				}
18887
18888				brElm = dom.create('br');
18889				rng.insertNode(brElm);
18890
18891				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18892				if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18893					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18894				}
18895
18896				// Insert temp marker and scroll to that
18897				marker = dom.create('span', {}, '&nbsp;');
18898				brElm.parentNode.insertBefore(marker, brElm);
18899				selection.scrollIntoView(marker);
18900				dom.remove(marker);
18901
18902				if (!extraBr) {
18903					rng.setStartAfter(brElm);
18904					rng.setEndAfter(brElm);
18905				} else {
18906					rng.setStartBefore(brElm);
18907					rng.setEndBefore(brElm);
18908				}
18909
18910				selection.setRng(rng);
18911				undoManager.add();
18912			};
18913
18914			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18915			function trimLeadingLineBreaks(node) {
18916				do {
18917					if (node.nodeType === 3) {
18918						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18919					}
18920
18921					node = node.firstChild;
18922				} while (node);
18923			};
18924
18925			function getEditableRoot(node) {
18926				var root = dom.getRoot(), parent, editableRoot;
18927
18928				// Get all parents until we hit a non editable parent or the root
18929				parent = node;
18930				while (parent !== root && dom.getContentEditable(parent) !== "false") {
18931					if (dom.getContentEditable(parent) === "true") {
18932						editableRoot = parent;
18933					}
18934
18935					parent = parent.parentNode;
18936				}
18937				
18938				return parent !== root ? editableRoot : root;
18939			};
18940
18941			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18942			function addBrToBlockIfNeeded(block) {
18943				var lastChild;
18944
18945				// IE will render the blocks correctly other browsers needs a BR
18946				if (!tinymce.isIE || tinymce.isIE11) {
18947					block.normalize(); // Remove empty text nodes that got left behind by the extract
18948
18949					// Check if the block is empty or contains a floated last child
18950					lastChild = block.lastChild;
18951					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18952						dom.add(block, 'br');
18953					}
18954				}
18955			};
18956
18957			// Delete any selected contents
18958			if (!rng.collapsed) {
18959				editor.execCommand('Delete');
18960				return;
18961			}
18962
18963			// Event is blocked by some other handler for example the lists plugin
18964			if (evt.isDefaultPrevented()) {
18965				return;
18966			}
18967
18968			// Setup range items and newBlockName
18969			container = rng.startContainer;
18970			offset = rng.startOffset;
18971			newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
18972			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18973			documentMode = dom.doc.documentMode;
18974			shiftKey = evt.shiftKey;
18975
18976			// Resolve node index
18977			if (container.nodeType == 1 && container.hasChildNodes()) {
18978				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18979				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18980				if (isAfterLastNodeInContainer && container.nodeType == 3) {
18981					offset = container.nodeValue.length;
18982				} else {
18983					offset = 0;
18984				}
18985			}
18986
18987			// Get editable root node normaly the body element but sometimes a div or span
18988			editableRoot = getEditableRoot(container);
18989
18990			// If there is no editable root then enter is done inside a contentEditable false element
18991			if (!editableRoot) {
18992				return;
18993			}
18994
18995			undoManager.beforeChange();
18996
18997			// If editable root isn't block nor the root of the editor
18998			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18999				if (!newBlockName || shiftKey) {
19000					insertBr();
19001				}
19002
19003				return;
19004			}
19005
19006			// Wrap the current node and it's sibling in a default block if it's needed.
19007			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
19008			// This won't happen if root blocks are disabled or the shiftKey is pressed
19009			if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
19010				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
19011			}
19012
19013			// Find parent block and setup empty block paddings
19014			parentBlock = dom.getParent(container, dom.isBlock);
19015			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
19016
19017			// Setup block names
19018			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
19019			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
19020
19021			// Enter inside block contained within a LI then split or insert before/after LI
19022			if (containerBlockName == 'LI' && !evt.ctrlKey) {
19023				parentBlock = containerBlock;
19024				parentBlockName = containerBlockName;
19025			}
19026
19027			// Handle enter in LI
19028			if (parentBlockName == 'LI') {
19029				if (!newBlockName && shiftKey) {
19030					insertBr();
19031					return;
19032				}
19033
19034				// Handle enter inside an empty list item
19035				if (dom.isEmpty(parentBlock)) {
19036					// Let the list plugin or browser handle nested lists for now
19037					if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
19038						return false;
19039					}
19040
19041					handleEmptyListItem();
19042					return;
19043				}
19044			}
19045
19046			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
19047			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
19048				if (!shiftKey) {
19049					insertBr();
19050					return;
19051				}
19052			} else {
19053				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
19054				if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
19055					insertBr();
19056					return;
19057				}
19058			}
19059
19060			// Default block name if it's not configured
19061			newBlockName = newBlockName || 'P';
19062
19063			// Insert new block before/after the parent block depending on caret location
19064			if (isCaretAtStartOrEndOfBlock()) {
19065				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
19066				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
19067					newBlock = createNewBlock(newBlockName);
19068				} else {
19069					newBlock = createNewBlock();
19070				}
19071
19072				// Split the current container block element if enter is pressed inside an empty inner block element
19073				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
19074					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
19075					newBlock = dom.split(containerBlock, parentBlock);
19076				} else {
19077					dom.insertAfter(newBlock, parentBlock);
19078				}
19079
19080				moveToCaretPosition(newBlock);
19081			} else if (isCaretAtStartOrEndOfBlock(true)) {
19082				// Insert new block before
19083				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
19084				renderBlockOnIE(newBlock);
19085			} else {
19086				// Extract after fragment and insert it after the current block
19087				tmpRng = rng.cloneRange();
19088				tmpRng.setEndAfter(parentBlock);
19089				fragment = tmpRng.extractContents();
19090				trimLeadingLineBreaks(fragment);
19091				newBlock = fragment.firstChild;
19092				dom.insertAfter(fragment, parentBlock);
19093				trimInlineElementsOnLeftSideOfBlock(newBlock);
19094				addBrToBlockIfNeeded(parentBlock);
19095				moveToCaretPosition(newBlock);
19096			}
19097
19098			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
19099			undoManager.add();
19100		}
19101
19102		editor.onKeyDown.add(function(ed, evt) {
19103			if (evt.keyCode == 13) {
19104				if (handleEnterKey(evt) !== false) {
19105					evt.preventDefault();
19106				}
19107			}
19108		});
19109	};
19110})(tinymce);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.