PageSourceSearch

https://members.fsac.ca/global/vendorExt/tinymce/tinymce.jquery.js

js fsac.ca collected 2026-09-25 01:05:33 UTC 950,287 bytes, 35,629 lines download raw bytes

1// 4.1.7 (2014-11-27)
2
3/**
4 * Compiled inline version. (Library mode)
5 */
6
7/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
8/*globals $code */
9
10(function(exports, undefined) {
11	"use strict";
12
13	var modules = {};
14
15	function require(ids, callback) {
16		var module, defs = [];
17
18		for (var i = 0; i < ids.length; ++i) {
19			module = modules[ids[i]] || resolve(ids[i]);
20			if (!module) {
21				throw 'module definition dependecy not found: ' + ids[i];
22			}
23
24			defs.push(module);
25		}
26
27		callback.apply(null, defs);
28	}
29
30	function define(id, dependencies, definition) {
31		if (typeof id !== 'string') {
32			throw 'invalid module definition, module id must be defined and be a string';
33		}
34
35		if (dependencies === undefined) {
36			throw 'invalid module definition, dependencies must be specified';
37		}
38
39		if (definition === undefined) {
40			throw 'invalid module definition, definition function must be specified';
41		}
42
43		require(dependencies, function() {
44			modules[id] = definition.apply(null, arguments);
45		});
46	}
47
48	function defined(id) {
49		return !!modules[id];
50	}
51
52	function resolve(id) {
53		var target = exports;
54		var fragments = id.split(/[.\/]/);
55
56		for (var fi = 0; fi < fragments.length; ++fi) {
57			if (!target[fragments[fi]]) {
58				return;
59			}
60
61			target = target[fragments[fi]];
62		}
63
64		return target;
65	}
66
67	function expose(ids) {
68		for (var i = 0; i < ids.length; i++) {
69			var target = exports;
70			var id = ids[i];
71			var fragments = id.split(/[.\/]/);
72
73			for (var fi = 0; fi < fragments.length - 1; ++fi) {
74				if (target[fragments[fi]] === undefined) {
75					target[fragments[fi]] = {};
76				}
77
78				target = target[fragments[fi]];
79			}
80
81			target[fragments[fragments.length - 1]] = modules[id];
82		}
83	}
84
85// Included from: js/tinymce/classes/dom/EventUtils.js
86
87/**
88 * EventUtils.js
89 *
90 * Copyright, Moxiecode Systems AB
91 * Released under LGPL License.
92 *
93 * License: http://www.tinymce.com/license
94 * Contributing: http://www.tinymce.com/contributing
95 */
96
97/*jshint loopfunc:true*/
98/*eslint no-loop-func:0 */
99
100/**
101 * This class wraps the browsers native event logic with more convenient methods.
102 *
103 * @class tinymce.dom.EventUtils
104 */
105define("tinymce/dom/EventUtils", [], function() {
106	"use strict";
107
108	var eventExpandoPrefix = "mce-data-";
109	var mouseEventRe = /^(?:mouse|contextmenu)|click/;
110	var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1};
111
112	/**
113	 * Binds a native event to a callback on the speified target.
114	 */
115	function addEvent(target, name, callback, capture) {
116		if (target.addEventListener) {
117			target.addEventListener(name, callback, capture || false);
118		} else if (target.attachEvent) {
119			target.attachEvent('on' + name, callback);
120		}
121	}
122
123	/**
124	 * Unbinds a native event callback on the specified target.
125	 */
126	function removeEvent(target, name, callback, capture) {
127		if (target.removeEventListener) {
128			target.removeEventListener(name, callback, capture || false);
129		} else if (target.detachEvent) {
130			target.detachEvent('on' + name, callback);
131		}
132	}
133
134	/**
135	 * Normalizes a native event object or just adds the event specific methods on a custom event.
136	 */
137	function fix(originalEvent, data) {
138		var name, event = data || {}, undef;
139
140		// Dummy function that gets replaced on the delegation state functions
141		function returnFalse() {
142			return false;
143		}
144
145		// Dummy function that gets replaced on the delegation state functions
146		function returnTrue() {
147			return true;
148		}
149
150		// Copy all properties from the original event
151		for (name in originalEvent) {
152			// layerX/layerY is deprecated in Chrome and produces a warning
153			if (!deprecated[name]) {
154				event[name] = originalEvent[name];
155			}
156		}
157
158		// Normalize target IE uses srcElement
159		if (!event.target) {
160			event.target = event.srcElement || document;
161		}
162
163		// Calculate pageX/Y if missing and clientX/Y available
164		if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
165			var eventDoc = event.target.ownerDocument || document;
166			var doc = eventDoc.documentElement;
167			var body = eventDoc.body;
168
169			event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
170				(doc && doc.clientLeft || body && body.clientLeft || 0);
171
172			event.pageY = originalEvent.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) -
173				(doc && doc.clientTop  || body && body.clientTop  || 0);
174		}
175
176		// Add preventDefault method
177		event.preventDefault = function() {
178			event.isDefaultPrevented = returnTrue;
179
180			// Execute preventDefault on the original event object
181			if (originalEvent) {
182				if (originalEvent.preventDefault) {
183					originalEvent.preventDefault();
184				} else {
185					originalEvent.returnValue = false; // IE
186				}
187			}
188		};
189
190		// Add stopPropagation
191		event.stopPropagation = function() {
192			event.isPropagationStopped = returnTrue;
193
194			// Execute stopPropagation on the original event object
195			if (originalEvent) {
196				if (originalEvent.stopPropagation) {
197					originalEvent.stopPropagation();
198				} else {
199					originalEvent.cancelBubble = true; // IE
200				}
201			}
202		};
203
204		// Add stopImmediatePropagation
205		event.stopImmediatePropagation = function() {
206			event.isImmediatePropagationStopped = returnTrue;
207			event.stopPropagation();
208		};
209
210		// Add event delegation states
211		if (!event.isDefaultPrevented) {
212			event.isDefaultPrevented = returnFalse;
213			event.isPropagationStopped = returnFalse;
214			event.isImmediatePropagationStopped = returnFalse;
215		}
216
217		return event;
218	}
219
220	/**
221	 * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
222	 * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
223	 */
224	function bindOnReady(win, callback, eventUtils) {
225		var doc = win.document, event = {type: 'ready'};
226
227		if (eventUtils.domLoaded) {
228			callback(event);
229			return;
230		}
231
232		// Gets called when the DOM is ready
233		function readyHandler() {
234			if (!eventUtils.domLoaded) {
235				eventUtils.domLoaded = true;
236				callback(event);
237			}
238		}
239
240		function waitForDomLoaded() {
241			// Check complete or interactive state if there is a body
242			// element on some iframes IE 8 will produce a null body
243			if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
244				removeEvent(doc, "readystatechange", waitForDomLoaded);
245				readyHandler();
246			}
247		}
248
249		function tryScroll() {
250			try {
251				// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
252				// http://javascript.nwbox.com/IEContentLoaded/
253				doc.documentElement.doScroll("left");
254			} catch (ex) {
255				setTimeout(tryScroll, 0);
256				return;
257			}
258
259			readyHandler();
260		}
261
262		// Use W3C method
263		if (doc.addEventListener) {
264			if (doc.readyState === "complete") {
265				readyHandler();
266			} else {
267				addEvent(win, 'DOMContentLoaded', readyHandler);
268			}
269		} else {
270			// Use IE method
271			addEvent(doc, "readystatechange", waitForDomLoaded);
272
273			// Wait until we can scroll, when we can the DOM is initialized
274			if (doc.documentElement.doScroll && win.self === win.top) {
275				tryScroll();
276			}
277		}
278
279		// Fallback if any of the above methods should fail for some odd reason
280		addEvent(win, 'load', readyHandler);
281	}
282
283	/**
284	 * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
285	 */
286	function EventUtils() {
287		var self = this, events = {}
287, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
288
289		expando = eventExpandoPrefix + (+new Date()).toString(32);
290		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
291		hasFocusIn = "onfocusin" in document.documentElement;
292		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
293		count = 1;
294
295		// State if the DOMContentLoaded was executed or not
296		self.domLoaded = false;
297		self.events = events;
298
299		/**
300		 * Executes all event handler callbacks for a specific event.
301		 *
302		 * @private
303		 * @param {Event} evt Event object.
304		 * @param {String} id Expando id value to look for.
305		 */
306		function executeHandlers(evt, id) {
307			var callbackList, i, l, callback, container = events[id];
308
309			callbackList = container && container[evt.type];
310			if (callbackList) {
311				for (i = 0, l = callbackList.length; i < l; i++) {
312					callback = callbackList[i];
313
314					// Check if callback exists might be removed if a unbind is called inside the callback
315					if (callback && callback.func.call(callback.scope, evt) === false) {
316						evt.preventDefault();
317					}
318
319					// Should we stop propagation to immediate listeners
320					if (evt.isImmediatePropagationStopped()) {
321						return;
322					}
323				}
324			}
325		}
326
327		/**
328		 * Binds a callback to an event on the specified target.
329		 *
330		 * @method bind
331		 * @param {Object} target Target node/window or custom object.
332		 * @param {String} names Name of the event to bind.
333		 * @param {function} callback Callback function to execute when the event occurs.
334		 * @param {Object} scope Scope to call the callback function on, defaults to target.
335		 * @return {function} Callback function that got bound.
336		 */
337		self.bind = function(target, names, callback, scope) {
338			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
339
340			// Native event handler function patches the event and executes the callbacks for the expando
341			function defaultNativeHandler(evt) {
342				executeHandlers(fix(evt || win.event), id);
343			}
344
345			// Don't bind to text nodes or comments
346			if (!target || target.nodeType === 3 || target.nodeType === 8) {
347				return;
348			}
349
350			// Create or get events id for the target
351			if (!target[expando]) {
352				id = count++;
353				target[expando] = id;
354				events[id] = {};
355			} else {
356				id = target[expando];
357			}
358
359			// Setup the specified scope or use the target as a default
360			scope = scope || target;
361
362			// Split names and bind each event, enables you to bind multiple events with one call
363			names = names.split(' ');
364			i = names.length;
365			while (i--) {
366				name = names[i];
367				nativeHandler = defaultNativeHandler;
368				fakeName = capture = false;
369
370				// Use ready instead of DOMContentLoaded
371				if (name === "DOMContentLoaded") {
372					name = "ready";
373				}
374
375				// DOM is already ready
376				if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
377					callback.call(scope, fix({type: name}));
378					continue;
379				}
380
381				// Handle mouseenter/mouseleaver
382				if (!hasMouseEnterLeave) {
383					fakeName = mouseEnterLeave[name];
384
385					if (fakeName) {
386						nativeHandler = function(evt) {
387							var current, related;
388
389							current = evt.currentTarget;
390							related = evt.relatedTarget;
391
392							// Check if related is inside the current target if it's not then the event should
393							// be ignored since it's a mouseover/mouseout inside the element
394							if (related && current.contains) {
395								// Use contains for performance
396								related = current.contains(related);
397							} else {
398								while (related && related !== current) {
399									related = related.parentNode;
400								}
401							}
402
403							// Fire fake event
404							if (!related) {
405								evt = fix(evt || win.event);
406								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
407								evt.target = current;
408								executeHandlers(evt, id);
409							}
410						};
411					}
412				}
413
414				// Fake bubbeling of focusin/focusout
415				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
416					capture = true;
417					fakeName = name === "focusin" ? "focus" : "blur";
418					nativeHandler = function(evt) {
419						evt = fix(evt || win.event);
420						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
421						executeHandlers(evt, id);
422					};
423				}
424
425				// Setup callback list and bind native event
426				callbackList = events[id][name];
427				if (!callbackList) {
428					events[id][name] = callbackList = [{func: callback, scope: scope}];
429					callbackList.fakeName = fakeName;
430					callbackList.capture = capture;
431					//callbackList.callback = callback;
432
433					// Add the nativeHandler to the callback list so that we can later unbind it
434					callbackList.nativeHandler = nativeHandler;
435
436					// Check if the target has native events support
437
438					if (name === "ready") {
439						bindOnReady(target, nativeHandler, self);
440					} else {
441						addEvent(target, fakeName || name, nativeHandler, capture);
442					}
443				} else {
444					if (name === "ready" && self.domLoaded) {
445						callback({type: name});
446					} else {
447						// If it already has an native handler then just push the callback
448						callbackList.push({func: callback, scope: scope});
449					}
450				}
451			}
452
453			target = callbackList = 0; // Clean memory for IE
454
455			return callback;
456		};
457
458		/**
459		 * Unbinds the specified event by name, name and callback or all events on the target.
460		 *
461		 * @method unbind
462		 * @param {Object} target Target node/window or custom object.
463		 * @param {String} names Optional event name to unbind.
464		 * @param {function} callback Optional callback function to unbind.
465		 * @return {EventUtils} Event utils instance.
466		 */
467		self.unbind = function(target, names, callback) {
468			var id, callbackList, i, ci, name, eventMap;
469
470			// Don't bind to text nodes or comments
471			if (!target || target.nodeType === 3 || target.nodeType === 8) {
472				return self;
473			}
474
475			// Unbind event or events if the target has the expando
476			id = target[expando];
477			if (id) {
478				eventMap = events[id];
479
480				// Specific callback
481				if (names) {
482					names = names.split(' ');
483					i = names.length;
484					while (i--) {
485						name = names[i];
486						callbackList = eventMap[name];
487
488						// Unbind the event if it exists in the map
489						if (callbackList) {
490							// Remove specified callback
491							if (callback) {
492								ci = callbackList.length;
493								while (ci--) {
494									if (callbackList[ci].func === callback) {
495										var nativeHandler = callbackList.nativeHandler;
496										var fakeName = callbackList.fakeName, capture = callbackList.capture;
497
498										// Clone callbackList since unbind inside a callback would otherwise break the handlers loop
499										callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
500										callbackList.nativeHandler = nativeHandler;
501										callbackList.fakeName = fakeName;
502										callbackList.capture = capture;
503
504										eventMap[name] = callbackList;
505									}
506								}
507							}
508
509							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
510							if (!callback || callbackList.length === 0) {
511								delete eventMap[name];
512								removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
513							}
514						}
515					}
516				} else {
517					// All events for a specific element
518					for (name in eventMap) {
519						callbackList = eventMap[name];
520						removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
521					}
522
523					eventMap = {};
524				}
525
526				// Check if object is empty, if it isn't then we won't remove the expando map
527				for (name in eventMap) {
528					return self;
529				}
530
531				// Delete event object
532				delete events[id];
533
534				// Remove expando from target
535				try {
536					// IE will fail here since it can't delete properties from window
537					delete target[expando];
538				} catch (ex) {
539					// IE will set it to null
540					target[expando] = null;
541				}
542			}
543
544			return self;
545		};
546
547		/**
548		 * Fires the specified event on the specified target.
549		 *
550		 * @method fire
551		 * @param {Object} target Target node/window or custom object.
552		 * @param {String} name Event name to fire.
553		 * @param {Object} args Optional arguments to send to the observers.
554		 * @return {EventUtils} Event utils instance.
555		 */
556		self.fire = function(target, name, args) {
557			var id;
558
559			// Don't bind to text nodes or comments
560			if (!target || target.nodeType === 3 || target.nodeType === 8) {
561				return self;
562			}
563
564			// Build event object by patching the args
565			args = fix(null, args);
566			args.type = name;
567			args.target = target;
568
569			do {
570				// Found an expando that means there is listeners to execute
571				id = target[expando];
572				if (id) {
573					executeHandlers(args, id);
574				}
575
576				// Walk up the DOM
577				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
578			} while (target && !args.isPropagationStopped());
579
580			return self;
581		};
582
583		/**
584		 * Removes all bound event listeners for the specified target. This will also remove any bound
585		 * listeners to child nodes within that target.
586		 *
587		 * @method clean
588		 * @param {Object} target Target node/window object.
589		 * @return {EventUtils} Event utils instance.
590		 */
591		self.clean = function(target) {
592			var i, children, unbind = self.unbind;
593
594			// Don't bind to text nodes or comments
595			if (!target || target.nodeType === 3 || target.nodeType === 8) {
596				return self;
597			}
598
599			// Unbind any element on the specificed target
600			if (target[expando]) {
601				unbind(target);
602			}
603
604			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
605			if (!target.getElementsByTagName) {
606				target = target.document;
607			}
608
609			// Remove events from each child element
610			if (target && target.getElementsByTagName) {
611				unbind(target);
612
613				children = target.getElementsByTagName('*');
614				i = children.length;
615				while (i--) {
616					target = children[i];
617
618					if (target[expando]) {
619						unbind(target);
620					}
621				}
622			}
623
624			return self;
625		};
626
627		/**
628		 * Destroys the event object. Call this on IE to remove memory leaks.
629		 */
630		self.destroy = function() {
631			events = {};
632		};
633
634		// Legacy function for canceling events
635		self.cancel = function(e) {
636			if (e) {
637				e.preventDefault();
638				e.stopImmediatePropagation();
639			}
640
641			return false;
642		};
643	}
644
645	EventUtils.Event = new EventUtils();
646	EventUtils.Event.bind(window, 'ready', function() {});
647
648	return EventUtils;
649});
650
651// Included from: js/tinymce/classes/dom/Sizzle.jQuery.js
652
653/**
654 * Sizzle.jQuery.js
655 *
656 * Copyright, Moxiecode Systems AB
657 * Released under LGPL License.
658 *
659 * License: http://www.tinymce.com/license
660 * Contributing: http://www.tinymce.com/contributing
661 */
662
663/*global jQuery:true */
664
665/*
666 * Fake Sizzle using jQuery.
667 */
668define("tinymce/dom/Sizzle", [], function() {
669	// Detect if jQuery is loaded
670	if (!window.jQuery) {
671		throw new Error("Load jQuery first");
672	}
673
674	return jQuery.find;
675});
676
677// Included from: js/tinymce/classes/Env.js
678
679/**
680 * Env.js
681 *
682 * Copyright, Moxiecode Systems AB
683 * Released under LGPL License.
684 *
685 * License: http://www.tinymce.com/license
686 * Contributing: http://www.tinymce.com/contributing
687 */
688
689/**
690 * This class contains various environment constants like browser versions etc.
691 * Normally you don't want to sniff specific browser versions but sometimes you have
692 * to when it's impossible to feature detect. So use this with care.
693 *
694 * @class tinymce.Env
695 * @static
696 */
697define("tinymce/Env", [], function() {
698	var nav = navigator, userAgent = nav.userAgent;
699	var opera, webkit, ie, ie11, gecko, mac, iDevice;
700
701	opera = window.opera && window.opera.buildNumber;
702	webkit = /WebKit/.test(userAgent);
703	ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
704	ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
705	ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
706	ie = ie || ie11;
707	gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
708	mac = userAgent.indexOf('Mac') != -1;
709	iDevice = /(iPad|iPhone)/.test(userAgent);
710
711	// Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
712	// says it has contentEditable support but there is no visible caret.
713	var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
714
715	return {
716		/**
717		 * Constant that is true if the browser is Opera.
718		 *
719		 * @property opera
720		 * @type Boolean
721		 * @final
722		 */
723		opera: opera,
724
725		/**
726		 * Constant that is true if the browser is WebKit (Safari/Chrome).
727		 *
728		 * @property webKit
729		 * @type Boolean
730		 * @final
731		 */
732		webkit: webkit,
733
734		/**
735		 * Constant that is more than zero if the browser is IE.
736		 *
737		 * @property ie
738		 * @type Boolean
739		 * @final
740		 */
741		ie: ie,
742
743		/**
744		 * Constant that is true if the browser is Gecko.
745		 *
746		 * @property gecko
747		 * @type Boolean
748		 * @final
749		 */
750		gecko: gecko,
751
752		/**
753		 * Constant that is true if the os is Mac OS.
754		 *
755		 * @property mac
756		 * @type Boolean
757		 * @final
758		 */
759		mac: mac,
760
761		/**
762		 * Constant that is true if the os is iOS.
763		 *
764		 * @property iOS
765		 * @type Boolean
766		 * @final
767		 */
768		iOS: iDevice,
769
770		/**
771		 * Constant that is true if the browser supports editing.
772		 *
773		 * @property contentEditable
774		 * @type Boolean
775		 * @final
776		 */
777		contentEditable: contentEditable,
778
779		/**
780		 * Transparent image data url.
781		 *
782		 * @property transparentSrc
783		 * @type Boolean
784		 * @final
785		 */
786		transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
787
788		/**
789		 * Returns true/false if the browser can or can't place the caret after a inline block like an image.
790		 *
791		 * @property noCaretAfter
792		 * @type Boolean
793		 * @final
794		 */
795		caretAfter: ie != 8,
796
797		/**
798		 * Constant that is true if the browser supports native DOM Ranges. IE 9+.
799		 *
800		 * @property range
801		 * @type Boolean
802		 */
803		range: window.getSelection && "Range" in window,
804
805		/**
806		 * Returns the IE document mode for non IE browsers this will fake IE 10.
807		 *
808		 * @property documentMode
809		 * @type Number
810		 */
811		documentMode: ie ? (document.documentMode || 7) : 10
812	};
813});
814
815// Included from: js/tinymce/classes/util/Tools.js
816
817/**
818 * Tools.js
819 *
820 * Copyright, Moxiecode Systems AB
821 * Released under LGPL License.
822 *
823 * License: http://www.tinymce.com/license
824 * Contributing: http://www.tinymce.com/contributing
825 */
826
827/**
828 * This class contains various utlity functions. These are also exposed
829 * directly on the tinymce namespace.
830 *
831 * @class tinymce.util.Tools
832 */
833define("tinymce/util/Tools", [
834	"tinymce/Env"
835], function(Env) {
836	/**
837	 * Removes whitespace from the beginning and end of a string.
838	 *
839	 * @method trim
840	 * @param {String} s String to remove whitespace from.
841	 * @return {String} New string with removed whitespace.
842	 */
843	var whiteSpaceRegExp = /^\s*|\s*$/g;
844
845	function trim(str) {
846		return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
847	}
848
849	/**
850	 * Returns true/false if the object is an array or not.
851	 *
852	 * @method isArray
853	 * @param {Object} obj Object to check.
854	 * @return {boolean} true/false state if the object is an array or not.
855	 */
856	var isArray = Array.isArray || function(obj) {
857		return Object.prototype.toString.call(obj) === "[object Array]";
858	};
859
860	/**
861	 * Checks if a object is of a specific type for example an array.
862	 *
863	 * @method is
864	 * @param {Object} o Object to check type of.
865	 * @param {string} t Optional type to check for.
866	 * @return {Boolean} true/false if the object is of the specified type.
867	 */
868	function is(o, t) {
869		if (!t) {
870			return o !== undefined;
871		}
872
873		if (t == 'array' && isArray(o)) {
874			return true;
875		}
876
877		return typeof(o) == t;
878	}
879
880	/**
881	 * Converts the specified object into a real JavaScript array.
882	 *
883	 * @method toArray
884	 * @param {Object} obj Object to convert into array.
885	 * @return {Array} Array object based in input.
886	 */
887	function toArray(obj) {
888		var array = obj, i, l;
889
890		if (!isArray(obj)) {
891			array = [];
892			for (i = 0, l = obj.length; i < l; i++) {
893				array[i] = obj[i];
894			}
895		}
896
897		return array;
898	}
899
900	/**
901	 * Makes a name/object map out of an array with names.
902	 *
903	 * @method makeMap
904	 * @param {Array/String} items Items to make map out of.
905	 * @param {String} delim Optional delimiter to split string by.
906	 * @param {Object} map Optional map to add items to.
907	 * @return {Object} Name/value map of items.
908	 */
909	function makeMap(items, delim, map) {
910		var i;
911
912		items = items || [];
913		delim = delim || ',';
914
915		if (typeof(items) == "string") {
916			items = items.split(delim);
917		}
918
919		map = map || {};
920
921		i = items.length;
922		while (i--) {
923			map[items[i]] = {};
924		}
925
926		return map;
927	}
928
929	/**
930	 * Performs an iteration of all items in a collection such as an object or array. This method will execure the
931	 * callback function for each item in the collection, if the callback returns false the iteration will terminate.
932	 * The callback has the following format: cb(value, key_or_index).
933	 *
934	 * @method each
935	 * @param {Object} o Collection to iterate.
936	 * @param {function} cb Callback function to execute for each item.
937	 * @param {Object} s Optional scope to execute the callback in.
938	 * @example
939	 * // Iterate an array
940	 * tinymce.each([1,2,3], function(v, i) {
941	 *     console.debug("Value: " + v + ", Index: " + i);
942	 * });
943	 *
944	 * // Iterate an object
945	 * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
946	 *     console.debug("Value: " + v + ", Key: " + k);
947	 * });
948	 */
949	function each(o, cb, s) {
950		var n, l;
951
952		if (!o) {
953			return 0;
954		}
955
956		s = s || o;
957
958		if (o.length !== undefined) {
959			// Indexed arrays, needed for Safari
960			for (n = 0, l = o.length; n < l; n++) {
961				if (cb.call(s, o[n], n, o) === false) {
962					return 0;
963				}
964			}
965		} else {
966			// Hashtables
967			for (n in o) {
968				if (o.hasOwnProperty(n)) {
969					if (cb.call(s, o[n], n, o) === false) {
970						return 0;
971					}
972				}
973			}
974		}
975
976		return 1;
977	}
978
979	/**
980	 * Creates a new array by the return value of each iteration function call. This enables you to convert
981	 * one array list into another.
982	 *
983	 * @method map
984	 * @param {Array} array Array of items to iterate.
985	 * @param {function} callback Function to call for each item. It's return value will be the new value.
986	 * @return {Array} Array with new values based on function return values.
987	 */
988	function map(array, callback) {
989		var out = [];
990
991		each(array, function(item) {
992			out.push(callback(item));
993		});
994
995		return out;
996	}
997
998	/**
999	 * Filters out items from the input array by calling the specified function for each item.
1000	 * If the function returns false the item will be excluded if it returns true it will be included.
1001	 *
1002	 * @method grep
1003	 * @param {Array} a Array of items to loop though.
1004	 * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
1005	 * @return {Array} New array with values imported and filtered based in input.
1006	 * @example
1007	 * // Filter out some items, this will return an array with 4 and 5
1008	 * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
1009	 */
1010	function grep(a, f) {
1011		var o = [];
1012
1013		each(a, function(v) {
1014			if (!f || f(v)) {
1015				o.push(v);
1016			}
1017		});
1018
1019		return o;
1020	}
1021
1022	/**
1023	 * Creates a class, subclass or static singleton.
1024	 * More details on this method can be found in the Wiki.
1025	 *
1026	 * @method create
1027	 * @param {String} s Class name, inheritage and prefix.
1028	 * @param {Object} p Collection of methods to add to the class.
1029	 * @param {Object} root Optional root object defaults to the global window object.
1030	 * @example
1031	 * // Creates a basic class
1032	 * tinymce.create('tinymce.somepackage.SomeClass', {
1033	 *     SomeClass: function() {
1034	 *         // Class constructor
1035	 *     },
1036	 *
1037	 *     method: function() {
1038	 *         // Some method
1039	 *     }
1040	 * });
1041	 *
1042	 * // Creates a basic subclass class
1043	 * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
1044	 *     SomeSubClass: function() {
1045	 *         // Class constructor
1046	 *         this.parent(); // Call parent constructor
1047	 *     },
1048	 *
1049	 *     method: function() {
1050	 *         // Some method
1051	 *         this.parent(); // Call parent method
1052	 *     },
1053	 *
1054	 *     'static': {
1055	 *         staticMethod: function() {
1056	 *             // Static method
1057	 *         }
1058	 *     }
1059	 * });
1060	 *
1061	 * // Creates a singleton/static class
1062	 * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
1063	 *     method: function() {
1064	 *         // Some method
1065	 *     }
1066	 * });
1067	 */
1068	function create(s, p, root) {
1069		var self = this, sp, ns, cn, scn, c, de = 0;
1070
1071		// Parse : <prefix> <class>:<super class>
1072		s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
1073		cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
1074
1075		// Create namespace for new class
1076		ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);
1077
1078		// Class already exists
1079		if (ns[cn]) {
1080			return;
1081		}
1082
1083		// Make pure static class
1084		if (s[2] == 'static') {
1085			ns[cn] = p;
1086
1087			if (this.onCreate) {
1088				this.onCreate(s[2], s[3], ns[cn]);
1089			}
1090
1091			return;
1092		}
1093
1094		// Create default constructor
1095		if (!p[cn]) {
1096			p[cn] = function() {};
1097			de = 1;
1098		}
1099
1100		// Add constructor and methods
1101		ns[cn] = p[cn];
1102		self.extend(ns[cn].prototype, p);
1103
1104		// Extend
1105		if (s[5]) {
1106			sp = self.resolve(s[5]).prototype;
1107			scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
1108
1109			// Extend constructor
1110			c = ns[cn];
1111			if (de) {
1112				// Add passthrough constructor
1113				ns[cn] = function() {
1114					return sp[scn].apply(this, arguments);
1115				};
1116			} else {
1117				// Add inherit constructor
1118				ns[cn] = function() {
1119					this.parent = sp[scn];
1120					return c.apply(this, arguments);
1121				};
1122			}
1123			ns[cn].prototype[cn] = ns[cn];
1124
1125			// Add super methods
1126			self.each(sp, function(f, n) {
1127				ns[cn].prototype[n] = sp[n];
1128			});
1129
1130			// Add overridden methods
1131			self.each(p, function(f, n) {
1132				// Extend methods if needed
1133				if (sp[n]) {
1134					ns[cn].prototype[n] = function() {
1135						this.parent = sp[n];
1136						return f.apply(this, arguments);
1137					};
1138				} else {
1139					if (n != cn) {
1140						ns[cn].prototype[n] = f;
1141					}
1142				}
1143			});
1144		}
1145
1146		// Add static methods
1147		/*jshint sub:true*/
1148		self.each(p['static'], function(f, n) {
1149			ns[cn][n] = f;
1150		});
1151	}
1152
1153	/**
1154	 * Returns the index of a value in an array, this method will return -1 if the item wasn't found.
1155	 *
1156	 * @method inArray
1157	 * @param {Array} a Array/Object to search for value in.
1158	 * @param {Object} v Value to check for inside the array.
1159	 * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
1160	 * @example
1161	 * // Get index of value in array this will alert 1 since 2 is at that index
1162	 * alert(tinymce.inArray([1,2,3], 2));
1163	 */
1164	function inArray(a, v) {
1165		var i, l;
1166
1167		if (a) {
1168			for (i = 0, l = a.length; i < l; i++) {
1169				if (a[i] === v) {
1170					return i;
1171				}
1172			}
1173		}
1174
1175		return -1;
1176	}
1177
1178	function extend(obj, ext) {
1179		var i, l, name, args = arguments, value;
1180
1181		for (i = 1, l = args.length; i < l; i++) {
1182			ext = args[i];
1183			for (name in ext) {
1184				if (ext.hasOwnProperty(name)) {
1185					value = ext[name];
1186
1187					if (value !== undefined) {
1188						obj[name] = value;
1189					}
1190				}
1191			}
1192		}
1193
1194		return obj;
1195	}
1196
1197	/**
1198	 * Executed the specified function for each item in a object tree.
1199	 *
1200	 * @method walk
1201	 * @param {Object} o Object tree to walk though.
1202	 * @param {function} f Function to call for each item.
1203	 * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
1204	 * @param {String} s Optional scope to execute the function in.
1205	 */
1206	function walk(o, f, n, s) {
1207		s = s || this;
1208
1209		if (o) {
1210			if (n) {
1211				o = o[n];
1212			}
1213
1214			each(o, function(o, i) {
1215				if (f.call(s, o, i, n) === false) {
1216					return false;
1217				}
1218
1219				walk(o, f, n, s);
1220			});
1221		}
1222	}
1223
1224	/**
1225	 * Creates a namespace on a specific object.
1226	 *
1227	 * @method createNS
1228	 * @param {String} n Namespace to create for example a.b.c.d.
1229	 * @param {Object} o Optional object to add namespace to, defaults to window.
1230	 * @return {Object} New namespace object the last item in path.
1231	 * @example
1232	 * // Create some namespace
1233	 * tinymce.createNS('tinymce.somepackage.subpackage');
1234	 *
1235	 * // Add a singleton
1236	 * var tinymce.somepackage.subpackage.SomeSingleton = {
1237	 *     method: function() {
1238	 *         // Some method
1239	 *     }
1240	 * };
1241	 */
1242	function createNS(n, o) {
1243		var i, v;
1244
1245		o = o || window;
1246
1247		n = n.split('.');
1248		for (i = 0; i < n.length; i++) {
1249			v = n[i];
1250
1251			if (!o[v]) {
1252				o[v] = {};
1253			}
1254
1255			o = o[v];
1256		}
1257
1258		return o;
1259	}
1260
1261	/**
1262	 * Resolves a string and returns the object from a specific structure.
1263	 *
1264	 * @method resolve
1265	 * @param {String} n Path to resolve for example a.b.c.d.
1266	 * @param {Object} o Optional object to search though, defaults to window.
1267	 * @return {Object} Last object in path or null if it couldn't be resolved.
1268	 * @example
1269	 * // Resolve a path into an object reference
1270	 * var obj = tinymce.resolve('a.b.c.d');
1271	 */
1272	function resolve(n, o) {
1273		var i, l;
1274
1275		o = o || window;
1276
1277		n = n.split('.');
1278		for (i = 0, l = n.length; i < l; i++) {
1279			o = o[n[i]];
1280
1281			if (!o) {
1282				break;
1283			}
1284		}
1285
1286		return o;
1287	}
1288
1289	/**
1290	 * Splits a string but removes the whitespace before and after each value.
1291	 *
1292	 * @method explode
1293	 * @param {string} s String to split.
1294	 * @param {string} d Delimiter to split by.
1295	 * @example
1296	 * // Split a string into an array with a,b,c
1297	 * var arr = tinymce.explode('a, b,   c');
1298	 */
1299	function explode(s, d) {
1300		if (!s || is(s, 'array')) {
1301			return s;
1302		}
1303
1304		return map(s.split(d || ','), trim);
1305	}
1306
1307	function _addCacheSuffix(url) {
1308		var cacheSuffix = Env.cacheSuffix;
1309
1310		if (cacheSuffix) {
1311			url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix;
1312		}
1313
1314		return url;
1315	}
1316
1317	return {
1318		trim: trim,
1319		isArray: isArray,
1320		is: is,
1321		toArray: toArray,
1322		makeMap: makeMap,
1323		each: each,
1324		map: map,
1325		grep: grep,
1326		inArray: inArray,
1327		extend: extend,
1328		create: create,
1329		walk: walk,
1330		createNS: createNS,
1331		resolve: resolve,
1332		explode: explode,
1333		_addCacheSuffix: _addCacheSuffix
1334	};
1335});
1336
1337// Included from: js/tinymce/classes/dom/DomQuery.js
1338
1339/**
1340 * DomQuery.js
1341 *
1342 * Copyright, Moxiecode Systems AB
1343 * Released under LGPL License.
1344 *
1345 * License: http://www.tinymce.com/license
1346 * Contributing: http://www.tinymce.com/contributing
1347 */
1348
1349/**
1350 * This class mimics most of the jQuery API:
1351 *
1352 * This is whats currently implemented:
1353 * - Utility functions
1354 * - DOM traversial
1355 * - DOM manipulation
1356 * - Event binding
1357 *
1358 * This is not currently implemented:
1359 * - Dimension
1360 * - Ajax
1361 * - Animation
1362 * - Advanced chaining
1363 *
1364 * @example
1365 * var $ = tinymce.dom.DomQuery;
1366 * $('p').attr('attr', 'value').addClass('class');
1367 *
1368 * @class tinymce.dom.DomQuery
1369 */
1370define("tinymce/dom/DomQuery", [
1371	"tinymce/dom/EventUtils",
1372	"tinymce/dom/Sizzle",
1373	"tinymce/util/Tools",
1374	"tinymce/Env"
1375], function(EventUtils, Sizzle, Tools, Env) {
1376	var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
1377	var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
1378	var Event = EventUtils.Event, undef;
1379
1380	function isDefined(obj) {
1381		return typeof obj !== 'undefined';
1382	}
1383
1384	function isString(obj) {
1385		return typeof obj === 'string';
1386	}
1387
1388	function isWindow(obj) {
1389		return obj && obj == obj.window;
1390	}
1391
1392	function createFragment(html, fragDoc) {
1393		var frag, node, container;
1394
1395		fragDoc = fragDoc || doc;
1396		container = fragDoc.createElement('div');
1397		frag = fragDoc.createDocumentFragment();
1398		container.innerHTML = html;
1399
1400		while ((node = container.firstChild)) {
1401			frag.appendChild(node);
1402		}
1403
1404		return frag;
1405	}
1406
1407	function domManipulate(targetNodes, sourceItem, callback, reverse) {
1408		var i;
1409
1410		if (isString(sourceItem)) {
1411			sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0]));
1412		} else if (sourceItem.length && !sourceItem.nodeType) {
1413			sourceItem = DomQuery.makeArray(sourceItem);
1414
1415			if (reverse) {
1416				for (i = sourceItem.length - 1; i >= 0; i--) {
1417					domManipulate(targetNodes, sourceItem[i], callback, reverse);
1418				}
1419			} else {
1420				for (i = 0; i < sourceItem.length; i++) {
1421					domManipulate(targetNodes, sourceItem[i], callback, reverse);
1422				}
1423			}
1424
1425			return targetNodes;
1426		}
1427
1428		if (sourceItem.nodeType) {
1429			i = targetNodes.length;
1430			while (i--) {
1431				callback.call(targetNodes[i], sourceItem);
1432			}
1433		}
1434
1435		return targetNodes;
1436	}
1437
1438	function hasClass(node, className) {
1439		return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
1440	}
1441
1442	function wrap(elements, wrapper, all) {
1443		var lastParent, newWrapper;
1444
1445		wrapper = DomQuery(wrapper)[0];
1446
1447		elements.each(function() {
1448			var self = this;
1449
1450			if (!all || lastParent != self.parentNode) {
1451				lastParent = self.parentNode;
1452				newWrapper = wrapper.cloneNode(false);
1453				self.parentNode.insertBefore(newWrapper, self);
1454				newWrapper.appendChild(self);
1455			} else {
1456				newWrapper.appendChild(self);
1457			}
1458		});
1459
1460		return elements;
1461	}
1462
1463	var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
1464	var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref nosh
1464ade nowrap readonly selected', ' ');
1465	var propFix = {
1466		'for': 'htmlFor',
1467		'class': 'className',
1468		'readonly': 'readOnly'
1469	};
1470	var cssFix = {
1471		'float': 'cssFloat'
1472	};
1473
1474	var attrHooks = {}, cssHooks = {};
1475
1476	function DomQuery(selector, context) {
1477		/*eslint new-cap:0 */
1478		return new DomQuery.fn.init(selector, context);
1479	}
1480
1481	function inArray(item, array) {
1482		var i;
1483
1484		if (array.indexOf) {
1485			return array.indexOf(item);
1486		}
1487
1488		i = array.length;
1489		while (i--) {
1490			if (array[i] === item) {
1491				return i;
1492			}
1493		}
1494
1495		return -1;
1496	}
1497
1498	var whiteSpaceRegExp = /^\s*|\s*$/g;
1499
1500	function trim(str) {
1501		return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
1502	}
1503
1504	function each(obj, callback) {
1505		var length, key, i, undef, value;
1506
1507		if (obj) {
1508			length = obj.length;
1509
1510			if (length === undef) {
1511				// Loop object items
1512				for (key in obj) {
1513					if (obj.hasOwnProperty(key)) {
1514						value = obj[key];
1515						if (callback.call(value, key, value) === false) {
1516							break;
1517						}
1518					}
1519				}
1520			} else {
1521				// Loop array items
1522				for (i = 0; i < length; i++) {
1523					value = obj[i];
1524					if (callback.call(value, i, value) === false) {
1525						break;
1526					}
1527				}
1528			}
1529		}
1530
1531		return obj;
1532	}
1533
1534	function grep(array, callback) {
1535		var out = [];
1536
1537		each(array, function(i, item) {
1538			if (callback(item, i)) {
1539				out.push(item);
1540			}
1541		});
1542
1543		return out;
1544	}
1545
1546	function getElementDocument(element) {
1547		if (!element) {
1548			return doc;
1549		}
1550
1551		if (element.nodeType == 9) {
1552			return element;
1553		}
1554
1555		return element.ownerDocument;
1556	}
1557
1558	DomQuery.fn = DomQuery.prototype = {
1559		constructor: DomQuery,
1560
1561		/**
1562		 * Selector for the current set.
1563		 *
1564		 * @property selector
1565		 * @type String
1566		 */
1567		selector: "",
1568
1569		/**
1570		 * Context used to create the set.
1571		 *
1572		 * @property context
1573		 * @type Element
1574		 */
1575		context: null,
1576
1577		/**
1578		 * Number of items in the current set.
1579		 *
1580		 * @property length
1581		 * @type Number
1582		 */
1583		length: 0,
1584
1585		/**
1586		 * Constructs a new DomQuery instance with the specified selector or context.
1587		 *
1588		 * @constructor
1589		 * @method init
1590		 * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string.
1591		 * @param {Document/Element} context Optional context to search in.
1592		 */
1593		init: function(selector, context) {
1594			var self = this, match, node;
1595
1596			if (!selector) {
1597				return self;
1598			}
1599
1600			if (selector.nodeType) {
1601				self.context = self[0] = selector;
1602				self.length = 1;
1603
1604				return self;
1605			}
1606
1607			if (context && context.nodeType) {
1608				self.context = context;
1609			} else {
1610				if (context) {
1611					return DomQuery(selector).attr(context);
1612				} else {
1613					self.context = context = document;
1614				}
1615			}
1616
1617			if (isString(selector)) {
1618				self.selector = selector;
1619
1620				if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
1621					match = [null, selector, null];
1622				} else {
1623					match = rquickExpr.exec(selector);
1624				}
1625
1626				if (match) {
1627					if (match[1]) {
1628						node = createFragment(selector, getElementDocument(context)).firstChild;
1629
1630						while (node) {
1631							push.call(self, node);
1632							node = node.nextSibling;
1633						}
1634					} else {
1635						node = getElementDocument(context).getElementById(match[2]);
1636
1637						if (!node) {
1638							return self;
1639						}
1640
1641						if (node.id !== match[2]) {
1642							return self.find(selector);
1643						}
1644
1645						self.length = 1;
1646						self[0] = node;
1647					}
1648				} else {
1649					return DomQuery(context).find(selector);
1650				}
1651			} else {
1652				this.add(selector, false);
1653			}
1654
1655			return self;
1656		},
1657
1658		/**
1659		 * Converts the current set to an array.
1660		 *
1661		 * @method toArray
1662		 * @param {Array} Array of all nodes in set.
1663		 */
1664		toArray: function() {
1665			return Tools.toArray(this);
1666		},
1667
1668		/**
1669		 * Adds new nodes to the set.
1670		 *
1671		 * @method add
1672		 * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set.
1673		 * @return {tinymce.dom.DomQuery} New instance with nodes added.
1674		 */
1675		add: function(items, sort) {
1676			var self = this, nodes, i;
1677
1678			if (isString(items)) {
1679				return self.add(DomQuery(items));
1680			}
1681
1682			if (sort !== false) {
1683				nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items)));
1684				self.length = nodes.length;
1685				for (i = 0; i < nodes.length; i++) {
1686					self[i] = nodes[i];
1687				}
1688			} else {
1689				push.apply(self, DomQuery.makeArray(items));
1690			}
1691
1692			return self;
1693		},
1694
1695		/**
1696		 * Sets/gets attributes on the elements in the current set.
1697		 *
1698		 * @method attr
1699		 * @param {String/Object} name Name of attribute to get or an object with attributes to set.
1700		 * @param {String} value Optional value to set.
1701		 * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified.
1702		 */
1703		attr: function(name, value) {
1704			var self = this, hook;
1705
1706			if (typeof name === "object") {
1707				each(name, function(name, value) {
1708					self.attr(name, value);
1709				});
1710			} else if (isDefined(value)) {
1711				this.each(function() {
1712					var hook;
1713
1714					if (this.nodeType === 1) {
1715						hook = attrHooks[name];
1716						if (hook && hook.set) {
1717							hook.set(this, value);
1718							return;
1719						}
1720
1721						if (value === null) {
1722							this.removeAttribute(name, 2);
1723						} else {
1724							this.setAttribute(name, value, 2);
1725						}
1726					}
1727				});
1728			} else {
1729				if (self[0] && self[0].nodeType === 1) {
1730					hook = attrHooks[name];
1731					if (hook && hook.get) {
1732						return hook.get(self[0], name);
1733					}
1734
1735					if (booleanMap[name]) {
1736						return self.prop(name) ? name : undef;
1737					}
1738
1739					value = self[0].getAttribute(name, 2);
1740
1741					if (value === null) {
1742						value = undef;
1743					}
1744				}
1745
1746				return value;
1747			}
1748
1749			return self;
1750		},
1751
1752		/**
1753		 * Removes attributse on the elements in the current set.
1754		 *
1755		 * @method removeAttr
1756		 * @param {String/Object}
1756 name Name of attribute to remove.
1757		 * @return {tinymce.dom.DomQuery/String} Current set.
1758		 */
1759		removeAttr: function(name) {
1760			return this.attr(name, null);
1761		},
1762
1763		/**
1764		 * Sets/gets properties on the elements in the current set.
1765		 *
1766		 * @method attr
1767		 * @param {String/Object} name Name of property to get or an object with properties to set.
1768		 * @param {String} value Optional value to set.
1769		 * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified.
1770		 */
1771		prop: function(name, value) {
1772			var self = this;
1773
1774			name = propFix[name] || name;
1775
1776			if (typeof name === "object") {
1777				each(name, function(name, value) {
1778					self.prop(name, value);
1779				});
1780			} else if (isDefined(value)) {
1781				this.each(function() {
1782					if (this.nodeType == 1) {
1783						this[name] = value;
1784					}
1785				});
1786			} else {
1787				if (self[0] && self[0].nodeType && name in self[0]) {
1788					return self[0][name];
1789				}
1790
1791				return value;
1792			}
1793
1794			return self;
1795		},
1796
1797		/**
1798		 * Sets/gets styles on the elements in the current set.
1799		 *
1800		 * @method css
1801		 * @param {String/Object} name Name of style to get or an object with styles to set.
1802		 * @param {String} value Optional value to set.
1803		 * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified.
1804		 */
1805		css: function(name, value) {
1806			var self = this, elm, hook;
1807
1808			function camel(name) {
1809				return name.replace(/-(\D)/g, function(a, b) {
1810					return b.toUpperCase();
1811				});
1812			}
1813
1814			function dashed(name) {
1815				return name.replace(/[A-Z]/g, function(a) {
1816					return '-' + a;
1817				});
1818			}
1819
1820			if (typeof name === "object") {
1821				each(name, function(name, value) {
1822					self.css(name, value);
1823				});
1824			} else {
1825				if (isDefined(value)) {
1826					name = camel(name);
1827
1828					// Default px suffix on these
1829					if (typeof(value) === 'number' && !numericCssMap[name]) {
1830						value += 'px';
1831					}
1832
1833					self.each(function() {
1834						var style = this.style;
1835
1836						hook = cssHooks[name];
1837						if (hook && hook.set) {
1838							hook.set(this, value);
1839							return;
1840						}
1841
1842						try {
1843							this.style[cssFix[name] || name] = value;
1844						} catch (ex) {
1845							// Ignore
1846						}
1847
1848						if (value === null || value === '') {
1849							if (style.removeProperty) {
1850								style.removeProperty(dashed(name));
1851							} else {
1852								style.removeAttribute(name);
1853							}
1854						}
1855					});
1856				} else {
1857					elm = self[0];
1858
1859					hook = cssHooks[name];
1860					if (hook && hook.get) {
1861						return hook.get(elm);
1862					}
1863
1864					if (elm.ownerDocument.defaultView) {
1865						try {
1866							return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name));
1867						} catch (ex) {
1868							return undef;
1869						}
1870					} else if (elm.currentStyle) {
1871						return elm.currentStyle[camel(name)];
1872					}
1873				}
1874			}
1875
1876			return self;
1877		},
1878
1879		/**
1880		 * Removes all nodes in set from the document.
1881		 *
1882		 * @method remove
1883		 * @return {tinymce.dom.DomQuery} Current set with the removed nodes.
1884		 */
1885		remove: function() {
1886			var self = this, node, i = this.length;
1887
1888			while (i--) {
1889				node = self[i];
1890				Event.clean(node);
1891
1892				if (node.parentNode) {
1893					node.parentNode.removeChild(node);
1894				}
1895			}
1896
1897			return this;
1898		},
1899
1900		/**
1901		 * Empties all elements in set.
1902		 *
1903		 * @method empty
1904		 * @return {tinymce.dom.DomQuery} Current set with the empty nodes.
1905		 */
1906		empty: function() {
1907			var self = this, node, i = this.length;
1908
1909			while (i--) {
1910				node = self[i];
1911				while (node.firstChild) {
1912					node.removeChild(node.firstChild);
1913				}
1914			}
1915
1916			return this;
1917		},
1918
1919		/**
1920		 * Sets or gets the HTML of the current set or first set node.
1921		 *
1922		 * @method html
1923		 * @param {String} value Optional innerHTML value to set on each element.
1924		 * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element.
1925		 */
1926		html: function(value) {
1927			var self = this, i;
1928
1929			if (isDefined(value)) {
1930				i = self.length;
1931
1932				try {
1933					while (i--) {
1934						self[i].innerHTML = value;
1935					}
1936				} catch (ex) {
1937					// Workaround for "Unknown runtime error" when DIV is added to P on IE
1938					DomQuery(self[i]).empty().append(value);
1939				}
1940
1941				return self;
1942			}
1943
1944			return self[0] ? self[0].innerHTML : '';
1945		},
1946
1947		/**
1948		 * Sets or gets the text of the current set or first set node.
1949		 *
1950		 * @method text
1951		 * @param {String} value Optional innerText value to set on each element.
1952		 * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element.
1953		 */
1954		text: function(value) {
1955			var self = this, i;
1956
1957			if (isDefined(value)) {
1958				i = self.length;
1959				while (i--) {
1960					if ("innerText" in self[i]) {
1961						self[i].innerText = value;
1962					} else {
1963						self[0].textContent = value;
1964					}
1965				}
1966
1967				return self;
1968			}
1969
1970			return self[0] ? (self[0].innerText || self[0].textContent) : '';
1971		},
1972
1973		/**
1974		 * Appends the specified node/html or node set to the current set nodes.
1975		 *
1976		 * @method append
1977		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set.
1978		 * @return {tinymce.dom.DomQuery} Current set.
1979		 */
1980		append: function() {
1981			return domManipulate(this, arguments, function(node) {
1982				if (this.nodeType === 1) {
1983					this.appendChild(node);
1984				}
1985			});
1986		},
1987
1988		/**
1989		 * Prepends the specified node/html or node set to the current set nodes.
1990		 *
1991		 * @method prepend
1992		 * @param {String/Element/Array/tinymce.dom.DomQuery}
1992 content Content to prepend to each element in set.
1993		 * @return {tinymce.dom.DomQuery} Current set.
1994		 */
1995		prepend: function() {
1996			return domManipulate(this, arguments, function(node) {
1997				if (this.nodeType === 1) {
1998					this.insertBefore(node, this.firstChild);
1999				}
2000			}, true);
2001		},
2002
2003		/**
2004		 * Adds the specified elements before current set nodes.
2005		 *
2006		 * @method before
2007		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set.
2008		 * @return {tinymce.dom.DomQuery} Current set.
2009		 */
2010		before: function() {
2011			var self = this;
2012
2013			if (self[0] && self[0].parentNode) {
2014				return domManipulate(self, arguments, function(node) {
2015					this.parentNode.insertBefore(node, this);
2016				});
2017			}
2018
2019			return self;
2020		},
2021
2022		/**
2023		 * Adds the specified elements after current set nodes.
2024		 *
2025		 * @method after
2026		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add after to each element in set.
2027		 * @return {tinymce.dom.DomQuery} Current set.
2028		 */
2029		after: function() {
2030			var self = this;
2031
2032			if (self[0] && self[0].parentNode) {
2033				return domManipulate(self, arguments, function(node) {
2034					this.parentNode.insertBefore(node, this.nextSibling);
2035				}, true);
2036			}
2037
2038			return self;
2039		},
2040
2041		/**
2042		 * Appends the specified set nodes to the specified selector/instance.
2043		 *
2044		 * @method appendTo
2045		 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to append the current set to.
2046		 * @return {tinymce.dom.DomQuery} Current set with the appended nodes.
2047		 */
2048		appendTo: function(val) {
2049			DomQuery(val).append(this);
2050
2051			return this;
2052		},
2053
2054		/**
2055		 * Prepends the specified set nodes to the specified selector/instance.
2056		 *
2057		 * @method prependTo
2058		 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to prepend the current set to.
2059		 * @return {tinymce.dom.DomQuery} Current set with the prepended nodes.
2060		 */
2061		prependTo: function(val) {
2062			DomQuery(val).prepend(this);
2063
2064			return this;
2065		},
2066
2067		/**
2068		 * Replaces the nodes in set with the specified content.
2069		 *
2070		 * @method replaceWith
2071		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to replace nodes with.
2072		 * @return {tinymce.dom.DomQuery} Set with replaced nodes.
2073		 */
2074		replaceWith: function(content) {
2075			return this.before(content).remove();
2076		},
2077
2078		/**
2079		 * Wraps all elements in set with the specified wrapper.
2080		 *
2081		 * @method wrap
2082		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
2083		 * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
2084		 */
2085		wrap: function(wrapper) {
2086			return wrap(this, wrapper);
2087		},
2088
2089		/**
2090		 * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them
2091		 * will be wrapped in the same wrapper.
2092		 *
2093		 * @method wrapAll
2094		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
2095		 * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
2096		 */
2097		wrapAll: function(wrapper) {
2098			return wrap(this, wrapper, true);
2099		},
2100
2101		/**
2102		 * Wraps all elements inner contents in set with the specified wrapper.
2103		 *
2104		 * @method wrapInner
2105		 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
2106		 * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
2107		 */
2108		wrapInner: function(wrapper) {
2109			this.each(function() {
2110				DomQuery(this).contents().wrapAll(wrapper);
2111			});
2112
2113			return this;
2114		},
2115
2116		/**
2117		 * Unwraps all elements by removing the parent element of each item in set.
2118		 *
2119		 * @method unwrap
2120		 * @return {tinymce.dom.DomQuery} Set with unwrapped nodes.
2121		 */
2122		unwrap: function() {
2123			return this.parent().each(function() {
2124				DomQuery(this).replaceWith(this.childNodes);
2125			});
2126		},
2127
2128		/**
2129		 * Clones all nodes in set.
2130		 *
2131		 * @method clone
2132		 * @return {tinymce.dom.DomQuery} Set with cloned nodes.
2133		 */
2134		clone: function() {
2135			var result = [];
2136
2137			this.each(function() {
2138				result.push(this.cloneNode(true));
2139			});
2140
2141			return DomQuery(result);
2142		},
2143
2144		/**
2145		 * Adds the specified class name to the current set elements.
2146		 *
2147		 * @method addClass
2148		 * @param {String} className Class name to add.
2149		 * @return {tinymce.dom.DomQuery} Current set.
2150		 */
2151		addClass: function(className) {
2152			return this.toggleClass(className, true);
2153		},
2154
2155		/**
2156		 * Removes the specified class name to the current set elements.
2157		 *
2158		 * @method removeClass
2159		 * @param {String} className Class name to remove.
2160		 * @return {tinymce.dom.DomQuery} Current set.
2161		 */
2162		removeClass: function(className) {
2163			return this.toggleClass(className, false);
2164		},
2165
2166		/**
2167		 * Toggles the specified class name on the current set elements.
2168		 *
2169		 * @method toggleClass
2170		 * @param {String} className Class name to add/remove.
2171		 * @param {Boolean} state Optional state to toggle on/off.
2172		 * @return {tinymce.dom.DomQuery} Current set.
2173		 */
2174		toggleClass: function(className, state) {
2175			var self = this;
2176
2177			// Functions are not supported
2178			if (typeof className != 'string') {
2179				return self;
2180			}
2181
2182			if (className.indexOf(' ') !== -1) {
2183				each(className.split(' '), function() {
2184					self.toggleClass(this, state);
2185				});
2186			} else {
2187				self.each(function(index, node) {
2188					var existingClassName, classState;
2189
2190					classState = hasClass(node, className);
2191					if (classState !== state) {
2192						existingClassName = node.className;
2193
2194						if (classState) {
2195							node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
2196						} else {
2197							node.className += existingClassName ? ' ' + className : className;
2198						}
2199					}
2200				});
2201			}
2202
2203			return self;
2204		},
2205
2206		/**
2207		 * Returns true/false if the first item in set has the specified class.
2208		 *
2209		 * @method hasClass
2210		 * @param {String} className Class name to check for.
2211		 * @return {Boolean} True/false if the set has the specified class.
2212		 */
2213		hasClass: function(className) {
2214			return hasClass(this[0], className);
2215		},
2216
2217		/**
2218		 * Executes the callback function for each item DomQuery collection. If you return false in the
2219		 * callback it will break the loop.
2220		 *
2221		 * @method each
2222		 * @param {function} callback Callback function to execute for each item.
2223		 * @return {tinymce.dom.DomQuery} Current set.
2224		 */
2225		each: function(callback) {
2226			return each(this, callback);
2227		},
2228
2229		/**
2230		 * Binds an event with callback function to the elements in set.
2231		 *
2232		 * @method on
2233		 * @param {String} name Name of the event to bind.
2234		 * @param {function} callback Callback function to execute when the event occurs.
2235		 * @return {tinymce.dom.DomQuery} Current set.
2236		 */
2237		on: function(name, callback) {
2238			return this.each(function() {
2239				Event.bind(this, name, callback);
2240			});
2241		},
2242
2243		/**
2244		 * Unbinds an event with callback function to the elements in set.
2245		 *
2246		 * @method off
2247		 * @param {String} name Optional name of the event to bind.
2248		 * @param {function} callback Optional callback function to execute when the event occurs.
2249		 * @return {tinymce.dom.DomQuery} Current set.
2250		 */
2251		off: function(name, callback) {
2252			return this.each(function() {
2253				Event.unbind(this, name, callback);
2254			});
2255		},
2256
2257		/**
2258		 * Triggers the specified event by name or event object.
2259		 *
2260		 * @method trigger
2261		 * @param {String/Object} name Name of the event to trigger or event object.
2262		 * @return {tinymce.dom.DomQuery} Current set.
2263		 */
2264		trigger: function(name) {
2265			return this.each(function() {
2266				if (typeof name == 'object') {
2267					Event.fire(this, name.type, name);
2268				} else {
2269					Event.fire(this, name);
2270				}
2271			});
2272		},
2273
2274		/**
2275		 * Shows all elements in set.
2276		 *
2277		 * @method show
2278		 * @return {tinymce.dom.DomQuery} Current set.
2279		 */
2280		show: function() {
2281			return this.css('display', '');
2282		},
2283
2284		/**
2285		 * Hides all elements in set.
2286		 *
2287		 * @method hide
2288		 * @return {tinymce.dom.DomQuery} Current set.
2289		 */
2290		hide: function() {
2291			return this.css('display', 'none');
2292		},
2293
2294		/**
2295		 * Slices the current set.
2296		 *
2297		 * @method slice
2298		 * @param {Number} start Start index to slice at.
2299		 * @param {Number} end Optional ened index to end slice at.
2300		 * @return {tinymce.dom.DomQuery} Sliced set.
2301		 */
2302		slice: function() {
2303			return new DomQuery(slice.apply(this, arguments));
2304		},
2305
2306		/**
2307		 * Makes the set equal to the specified index.
2308		 *
2309		 * @method eq
2310		 * @param {Number} index Index to set it equal to.
2311		 * @return {tinymce.dom.DomQuery} Single item set.
2312		 */
2313		eq: function(index) {
2314			return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
2315		},
2316
2317		/**
2318		 * Makes the set equal to first element in set.
2319		 *
2320		 * @method first
2321		 * @return {tinymce.dom.DomQuery} Single item set.
2322		 */
2323		first: function() {
2324			return this.eq(0);
2325		},
2326
2327		/**
2328		 * Makes the set equal to last element in set.
2329		 *
2330		 * @method last
2331		 * @return {tinymce.dom.DomQuery} Single item set.
2332		 */
2333		last: function() {
2334			return this.eq(-1);
2335		},
2336
2337		/**
2338		 * Finds elements by the specified selector for each element in set.
2339		 *
2340		 * @method find
2341		 * @param {String} selector Selector to find elements by.
2342		 * @return {tinymce.dom.DomQuery} Set with matches elements.
2343		 */
2344		find: function(selector) {
2345			var i, l, ret = [];
2346
2347			for (i = 0, l = this.length; i < l; i++) {
2348				DomQuery.find(selector, this[i], ret);
2349			}
2350
2351			return DomQuery(ret);
2352		},
2353
2354		/**
2355		 * Filters the current set with the specified selector.
2356		 *
2357		 * @method filter
2358		 * @param {String/function} selector Selector to filter elements by.
2359		 * @return {tinymce.dom.DomQuery} Set with filtered elements.
2360		 */
2361		filter: function(selector) {
2362			if (typeof selector == 'function') {
2363				return DomQuery(grep(this.toArray(), function(item, i) {
2364					return selector(i, item);
2365				}));
2366			}
2367
2368			return DomQuery(DomQuery.filter(selector, this.toArray()));
2369		},
2370
2371		/**
2372		 * Gets the current node or any partent matching the specified selector.
2373		 *
2374		 * @method closest
2375		 * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find.
2376		 * @return {tinymce.dom.DomQuery} Set with closest elements.
2377		 */
2378		closest: function(selector) {
2379			var result = [];
2380
2381			if (selector instanceof DomQuery) {
2382				selector = selector[0];
2383			}
2384
2385			this.each(function(i, node) {
2386				while (node) {
2387					if (typeof selector == 'string' && DomQuery(node).is(selector)) {
2388						result.push(node);
2389						break;
2390					} else if (node == selector) {
2391						result.push(node);
2392						break;
2393					}
2394
2395					node = node.parentNode;
2396				}
2397			});
2398
2399			return DomQuery(result);
2400		},
2401
2402		/**
2403		 * Returns the offset of the first element in set or sets the top/left css properties of all elements in set.
2404		 *
2405		 * @method offset
2406		 * @param {Object} offset Optional offset object to set on each item.
2407		 * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset.
2408		 */
2409		offset: function(offset) {
2410			var elm, doc, docElm;
2411			var x = 0, y = 0, pos;
2412
2413			if (!offset) {
2414				elm = this[0];
2415
2416				if (elm) {
2417					doc = elm.ownerDocument;
2418					docElm = doc.documentElement;
2419
2420					if (elm.getBoundingClientRect) {
2421						pos = elm.getBoundingClientRect();
2422						x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft;
2423						y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop;
2424					}
2425				}
2426
2427				return {
2428					left: x,
2429					top: y
2430				};
2431			}
2432
2433			return this.css(offset);
2434		},
2435
2436		push: push,
2437		sort: [].sort,
2438		splice: [].splice
2439	};
2440
2441	// Static members
2442	Tools.extend(DomQuery, {
2443		/**
2444		 * Extends the specified object with one or more objects.
2445		 *
2446		 * @static
2447		 * @method extend
2448		 * @param {Object} target Target object to extend with new items.
2449		 * @param {Object..} object Object to extend the target with.
2450		 * @return {Object} Extended input object.
2451		 */
2452		extend: Tools.extend,
2453
2454		/**
2455		 * Creates an array out of an array like object.
2456		 *
2457		 * @static
2458		 * @method makeArray
2459		 * @param {Object} object Object to convert to array.
2460		 * @return {Arrau} Array produced from object.
2461		 */
2462		makeArray: function(array) {
2463			if (isWindow(array) || array.nodeType) {
2464				return [array];
2465			}
2466
2467			return Tools.toArray(array);
2468		},
2469
2470		/**
2471		 * Returns the index of the specified item inside the array.
2472		 *
2473		 * @static
2474		 * @method inArray
2475		 * @param {Object} item Item to look for.
2476		 * @param {Array} array Array to look for item in.
2477		 * @return {Number} Index of the item or -1.
2478		 */
2479		inArray: inArray,
2480
2481		/**
2482		 * Returns true/false if the specified object is an array or not.
2483		 *
2484		 * @static
2485		 * @method isArray
2486		 * @param {Object} array Object to check if it's an array or not.
2487		 * @return {Boolean} True/false if the object is an array.
2488		 */
2489		isArray: Tools.isArray,
2490
2491		/**
2492		 * Executes the callback function for each item in array/object. If you return false in the
2493		 * callback it will break the loop.
2494		 *
2495		 * @static
2496		 * @method each
2497		 * @param {Object} obj Object to iterate.
2498		 * @param {function} callback Callback function to execute for each item.
2499		 */
2500		each: each,
2501
2502		/**
2503		 * Removes whitespace from the beginning and end of a string.
2504		 *
2505		 * @static
2506		 * @method trim
2507		 * @param {String} str String to remove whitespace from.
2508		 * @return {String} New string with removed whitespace.
2509		 */
2510		trim: trim,
2511
2512		/**
2513		 * Filters out items from the input array by calling the specified function for each item.
2514		 * If the function returns false the item will be excluded if it returns true it will be included.
2515		 *
2516		 * @static
2517		 * @method grep
2518		 * @param {Array} array Array of items to loop though.
2519		 * @param {function} callback Function to call for each item. Include/exclude depends on it's return value.
2520		 * @return {Array} New array with values imported and filtered based in input.
2521		 * @example
2522		 * // Filter out some items, this will return an array with 4 and 5
2523		 * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;});
2524		 */
2525		grep: grep,
2526
2527		// Sizzle
2528		find: Sizzle,
2529		expr: Sizzle.selectors,
2530		unique: Sizzle.uniqueSort,
2531		text: Sizzle.getText,
2532		contains: Sizzle.contains,
2533		filter: function(expr, elems, not) {
2534			var i = elems.length;
2535
2536			if (not) {
2537				expr = ":not(" + expr + ")";
2538			}
2539
2540			while (i--) {
2541				if (elems[i].nodeType != 1) {
2542					elems.splice(i, 1);
2543				}
2544			}
2545
2546			if (elems.length === 1) {
2547				elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
2548			} else {
2549				elems = DomQuery.find.matches(expr, elems);
2550			}
2551
2552			return elems;
2553		}
2554	});
2555
2556	function dir(el, prop, until) {
2557		var matched = [], cur = el[prop];
2558
2559		if (typeof until != 'string' && until instanceof DomQuery) {
2560			until = until[0];
2561		}
2562
2563		while (cur && cur.nodeType !== 9) {
2564			if (until !== undefined) {
2565				if (cur === until) {
2566					break;
2567				}
2568
2569				if (typeof until == 'string' && DomQuery(cur).is(until)) {
2570					break;
2571				}
2572			}
2573
2574			if (cur.nodeType === 1) {
2575				matched.push(cur);
2576			}
2577
2578			cur = cur[prop];
2579		}
2580
2581		return matched;
2582	}
2583
2584	function sibling(node, siblingName, nodeType, until) {
2585		var result = [];
2586
2587		if (until instanceof DomQuery) {
2588			until = until[0];
2589		}
2590
2591		for (; node; node = node[siblingName]) {
2592			if (nodeType && node.nodeType !== nodeType) {
2593				continue;
2594			}
2595
2596			if (until !== undefined) {
2597				if (node === until) {
2598					break;
2599				}
2600
2601				if (typeof until == 'string' && DomQuery(node).is(until)) {
2602					break;
2603				}
2604			}
2605
2606			result.push(node);
2607		}
2608
2609		return result;
2610	}
2611
2612	function firstSibling(node, siblingName, nodeType) {
2613		for (node = node[siblingName]; node; node = node[siblingName]) {
2614			if (node.nodeType == nodeType) {
2615				return node;
2616			}
2617		}
2618
2619		return null;
2620	}
2621
2622	each({
2623		/**
2624		 * Returns a new collection with the parent of each item in current collection matching the optional selector.
2625		 *
2626		 * @method parent
2627		 * @param {String} selector Selector to match parents agains.
2628		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2629		 */
2630		parent: function(node) {
2631			var parent = node.parentNode;
2632
2633			return parent && parent.nodeType !== 11 ? parent : null;
2634		},
2635
2636		/**
2637		 * Returns a new collection with the all the parents of each item in current collection matching the optional selector.
2638		 *
2639		 * @method parents
2640		 * @param {String} selector Selector to match parents agains.
2641		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2642		 */
2643		parents: function(node) {
2644			return dir(node, "parentNode");
2645		},
2646
2647		/**
2648		 * Returns a new collection with next sibling of each item in current collection matching the optional selector.
2649		 *
2650		 * @method next
2651		 * @param {String} selector Selector to match the next element against.
2652		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2653		 */
2654		next: function(node) {
2655			return firstSibling(node, 'nextSibling', 1);
2656		},
2657
2658		/**
2659		 * Returns a new collection with previous sibling of each item in current collection matching the optional selector.
2660		 *
2661		 * @method prev
2662		 * @param {String} selector Selector to match the previous element against.
2663		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2664		 */
2665		prev: function(node) {
2666			return firstSibling(node, 'previousSibling', 1);
2667		},
2668
2669		/**
2670		 * Returns all child elements matching the optional selector.
2671		 *
2672		 * @method children
2673		 * @param {String} selector Selector to match the elements against.
2674		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2675		 */
2676		children: function(node) {
2677			return sibling(node.firstChild, 'nextSibling', 1);
2678		},
2679
2680		/**
2681		 * Returns all child nodes matching the optional selector.
2682		 *
2683		 * @method contents
2684		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2685		 */
2686		contents: function(node) {
2687			return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
2688		}
2689	}, function(name, fn) {
2690		DomQuery.fn[name] = function(selector) {
2691			var self = this, result = [];
2692
2693			self.each(function() {
2694				var nodes = fn.call(result, this, selector, result);
2695
2696				if (nodes) {
2697					if (DomQuery.isArray(nodes)) {
2698						result.push.apply(result, nodes);
2699					} else {
2700						result.push(nodes);
2701					}
2702				}
2703			});
2704
2705			// If traversing on multiple elements we might get the same elements twice
2706			if (this.length > 1) {
2707				result = DomQuery.unique(result);
2708
2709				if (name.indexOf('parents') === 0) {
2710					result = result.reverse();
2711				}
2712			}
2713
2714			result = DomQuery(result);
2715
2716			if (selector) {
2717				return result.filter(selector);
2718			}
2719
2720			return result;
2721		};
2722	});
2723
2724	each({
2725		/**
2726		 * Returns a new collection with the all the parents until the matching selector/element
2727		 * of each item in current collection matching the optional selector.
2728		 *
2729		 * @method parentsUntil
2730		 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2731		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
2732		 */
2733		parentsUntil: function(node, until) {
2734			return dir(node, "parentNode", until);
2735		},
2736
2737		/**
2738		 * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
2739		 *
2740		 * @method nextUntil
2741		 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2742		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2743		 */
2744		nextUntil: function(node, until) {
2745			return sibling(node, 'nextSibling', 1, until).slice(1);
2746		},
2747
2748		/**
2749		 * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
2750		 *
2751		 * @method prevUntil
2752		 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
2753		 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
2754		 */
2755		prevUntil: function(node, until) {
2756			return sibling(node, 'previousSibling', 1, until).slice(1);
2757		}
2758	}, function(name, fn) {
2759		DomQuery.fn[name] = function(selector, filter) {
2760			var self = this, result = [];
2761
2762			self.each(function() {
2763				var nodes = fn.call(result, this, selector, result);
2764
2765				if (nodes) {
2766					if (DomQuery.isArray(nodes)) {
2767						result.push.apply(result, nodes);
2768					} else {
2769						result.push(nodes);
2770					}
2771				}
2772			});
2773
2774			// If traversing on multiple elements we might get the same elements twice
2775			if (this.length > 1) {
2776				result = DomQuery.unique(result);
2777
2778				if (name.indexOf('parents') === 0 || name === 'prevUntil') {
2779					result = result.reverse();
2780				}
2781			}
2782
2783			result = DomQuery(result);
2784
2785			if (filter) {
2786				return result.filter(filter);
2787			}
2788
2789			return result;
2790		};
2791	});
2792
2793	/**
2794	 * Returns true/false if the current set items matches the selector.
2795	 *
2796	 * @method is
2797	 * @param {String} selector Selector to match the elements against.
2798	 * @return {Boolean} True/false if the current set matches the selector.
2799	 */
2800	DomQuery.fn.is = function(selector) {
2801		return !!selector && this.filter(selector).length > 0;
2802	};
2803
2804	DomQuery.fn.init.prototype = DomQuery.fn;
2805
2806	DomQuery.overrideDefaults = function(callback) {
2807		var defaults;
2808
2809		function sub(selector, context) {
2810			defaults = defaults || callback();
2811
2812			if (arguments.length === 0) {
2813				selector = defaults.element;
2814			}
2815
2816			if (!context) {
2817				context = defaults.context;
2818			}
2819
2820			return new sub.fn.init(selector, context);
2821		}
2822
2823		DomQuery.extend(sub, this);
2824
2825		return sub;
2826	};
2827
2828	function appendHooks(targetHooks, prop, hooks) {
2829		each(hooks, function(name, func) {
2830			targetHooks[name] = targetHooks[name] || {};
2831			targetHooks[name][prop] = func;
2832		});
2833	}
2834
2835	if (Env.ie && Env.ie < 8) {
2836		appendHooks(attrHooks, 'get', {
2837			maxlength: function(elm) {
2838				var value = elm.maxLength;
2839
2840				if (value === 0x7fffffff) {
2841					return undef;
2842				}
2843
2844				return value;
2845			},
2846
2847			size: function(elm) {
2848				var value = elm.size;
2849
2850				if (value === 20) {
2851					return undef;
2852				}
2853
2854				return value;
2855			},
2856
2857			'class': function(elm) {
2858				return elm.className;
2859			},
2860
2861			style: function(elm) {
2862				var value = elm.style.cssText;
2863
2864				if (value.length === 0) {
2865					return undef;
2866				}
2867
2868				return value;
2869			}
2870		});
2871
2872		appendHooks(attrHooks, 'set', {
2873			'class': function(elm, value) {
2874				elm.className = value;
2875			},
2876
2877			style: function(elm, value) {
2878				elm.style.cssText = value;
2879			}
2880		});
2881	}
2882
2883	if (Env.ie && Env.ie < 9) {
2884		/*jshint sub:true */
2885		/*eslint dot-notation: 0*/
2886		cssFix['float'] = 'styleFloat';
2887
2888		appendHooks(cssHooks, 'set', {
2889			opacity: function(elm, value) {
2890				var style = elm.style;
2891
2892				if (value === null || value === '') {
2893					style.removeAttribute('filter');
2894				} else {
2895					style.zoom = 1;
2896					style.filter = 'alpha(opacity=' + (value * 100) + ')';
2897				}
2898			}
2899		});
2900	}
2901
2902	DomQuery.attrHooks = attrHooks;
2903	DomQuery.cssHooks = cssHooks;
2904
2905	return DomQuery;
2906});
2907
2908// Included from: js/tinymce/classes/html/Styles.js
2909
2910/**
2911 * Styles.js
2912 *
2913 * Copyright, Moxiecode Systems AB
2914 * Released under LGPL License.
2915 *
2916 * License: http://www.tinymce.com/license
2917 * Contributing: http://www.tinymce.com/contributing
2918 */
2919
2920/**
2921 * This class is used to parse CSS styles it also compresses styles to reduce the output size.
2922 *
2923 * @example
2924 * var Styles = new tinymce.html.Styles({
2925 *    url_converter: function(url) {
2926 *       return url;
2927 *    }
2928 * });
2929 *
2930 * styles = Styles.parse('border: 1px solid red');
2931 * styles.color = 'red';
2932 *
2933 * console.log(new tinymce.html.StyleSerializer().serialize(styles));
2934 *
2935 * @class tinymce.html.Styles
2936 * @version 3.4
2937 */
2938define("tinymce/html/Styles", [], function() {
2939	return function(settings, schema) {
2940		/*jshint maxlen:255 */
2941		/*eslint max-len:0 */
2942		var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2943			urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2944			styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2945			trimRightRegExp = /\s+$/,
2946			undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
2947
2948		settings = settings || {};
2949
2950		if (schema) {
2951			validStyles = schema.getValidStyles();
2952			invalidStyles = schema.getInvalidStyles();
2953		}
2954
2955		encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
2956		for (i = 0; i < encodingItems.length; i++) {
2957			encodingLookup[encodingItems[i]] = invisibleChar + i;
2958			encodingLookup[invisibleChar + i] = encodingItems[i];
2959		}
2960
2961		function toHex(match, r, g, b) {
2962			function hex(val) {
2963				val = parseInt(val, 10).toString(16);
2964
2965				return val.length > 1 ? val : '0' + val; // 0 -> 00
2966			}
2967
2968			return '#' + hex(r) + hex(g) + hex(b);
2969		}
2970
2971		return {
2972			/**
2973			 * Parses the specified RGB color value and returns a hex version of that color.
2974			 *
2975			 * @method toHex
2976			 * @param {String} color RGB string value like rgb(1,2,3)
2977			 * @return {String} Hex version of that RGB value like #FF00FF.
2978			 */
2979			toHex: function(color) {
2980				return color.replace(rgbRegExp, toHex);
2981			},
2982
2983			/**
2984			 * Parses the specified style value into an object collection. This parser will also
2985			 * merge and remove any redundant items that browsers might have added. It will also convert non hex
2986			 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
2987			 *
2988			 * @method parse
2989			 * @param {String} css Style value to parse for example: border:1px solid red;.
2990			 * @return {Object} Object representation of that style like {border: '1px solid red'}
2991			 */
2992			parse: function(css) {
2993				var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
2994				var urlConverterScope = settings.url_converter_scope || this;
2995
2996				function compress(prefix, suffix, noJoin) {
2997					var top, right, bottom, left;
2998
2999					top = styles[prefix + '-top' + suffix];
3000					if (!top) {
3001						return;
3002					}
3003
3004					right = styles[prefix + '-right' + suffix];
3005					if (!right) {
3006						return;
3007					}
3008
3009					bottom = styles[prefix + '-bottom' + suffix];
3010					if (!bottom) {
3011						return;
3012					}
3013
3014					left = styles[prefix + '-left' + suffix];
3015					if (!left) {
3016						return;
3017					}
3018
3019					var box = [top, right, bottom, left];
3020					i = box.length - 1;
3021					while (i--) {
3022						if (box[i] !== box[i + 1]) {
3023							break;
3024						}
3025					}
3026
3027					if (i > -1 && noJoin) {
3028						return;
3029					}
3030
3031					styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
3032					delete styles[prefix + '-top' + suffix];
3033					delete styles[prefix + '-right' + suffix];
3034					delete styles[prefix + '-bottom' + suffix];
3035					delete styles[prefix + '-left' + suffix];
3036				}
3037
3038				/**
3039				 * Checks if the specific style can be compressed in other words if all border-width are equal.
3040				 */
3041				function canCompress(key) {
3042					var value = styles[key], i;
3043
3044					if (!value) {
3045						return;
3046					}
3047
3048					value = value.split(' ');
3049					i = value.length;
3050					while (i--) {
3051						if (value[i] !== value[0]) {
3052							return false;
3053						}
3054					}
3055
3056					styles[key] = value[0];
3057
3058					return true;
3059				}
3060
3061				/**
3062				 * Compresses multiple styles into one style.
3063				 */
3064				function compress2(target, a, b, c) {
3065					if (!canCompress(a)) {
3066						return;
3067					}
3068
3069					if (!canCompress(b)) {
3070						return;
3071					}
3072
3073					if (!canCompress(c)) {
3074						return;
3075					}
3076
3077					// Compress
3078					styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
3079					delete styles[a];
3080					delete styles[b];
3081					delete styles[c];
3082				}
3083
3084				// Encodes the specified string by replacing all \" \' ; : with _<num>
3085				function encode(str) {
3086					isEncoded = true;
3087
3088					return encodingLookup[str];
3089				}
3090
3091				// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
3092				// It will also decode the \" \' if keep_slashes is set to fale or omitted
3093				function decode(str, keep_slashes) {
3094					if (isEncoded) {
3095						str = str.replace(/\uFEFF[0-9]/g, function(str) {
3096							return encodingLookup[str];
3097						});
3098					}
3099
3100					if (!keep_slashes) {
3101						str = str.replace(/\\([\'\";:])/g, "$1");
3102					}
3103
3104					return str;
3105				}
3106
3107				function processUrl(match, url, url2, url3, str, str2) {
3108					str = str || str2;
3109
3110					if (str) {
3111						str = decode(str);
3112
3113						// Force strings into single quote format
3114						return "'" + str.replace(/\'/g, "\\'") + "'";
3115					}
3116
3117					url = decode(url || url2 || url3);
3118
3119					if (!settings.allow_script_urls) {
3120						var scriptUrl = url.replace(/[\s\r\n]+/, '');
3121
3122						if (/(java|vb)script:/i.test(scriptUrl)) {
3123							return "";
3124						}
3125
3126						if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
3127							return "";
3128						}
3129					}
3130
3131					// Convert the URL to relative/absolute depending on config
3132					if (urlConverter) {
3133						url = urlConverter.call(urlConverterScope, url, 'style');
3134					}
3135
3136					// Output new URL format
3137					return "url('" + url.replace(/\'/g, "\\'") + "')";
3138				}
3139
3140				if (css) {
3141					css = css.replace(/[\u0000-\u001F]/g, '');
3142
3143					// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
3144					css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
3145						return str.replace(/[;:]/g, encode);
3146					});
3147
3148					// Parse styles
3149					while ((matches = styleRegExp.exec(css))) {
3150						name = matches[1].replace(trimRightRegExp, '').toLowerCase();
3151						value = matches[2].replace(trimRightRegExp, '');
3152
3153						// Decode escaped sequences like \65 -> e
3154						/*jshint loopfunc:true*/
3155						/*eslint no-loop-func:0 */
3156						value = value.replace(/\\[0-9a-f]+/g, function(e) {
3157							return String.fromCharCode(parseInt(e.substr(1), 16));
3158						});
3159
3160						if (name && value.length > 0) {
3161							// Don't allow behavior name or expression/comments within the values
3162							if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
3163								continue;
3164							}
3165
3166							// Opera will produce 700 instead of bold in their style values
3167							if (name === 'font-weight' && value === '700') {
3168								value = 'bold';
3169							} else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
3170								value = value.toLowerCase();
3171							}
3172
3173							// Convert RGB colors to HEX
3174							value = value.replace(rgbRegExp, toHex);
3175
3176							// Convert URLs and force them into url('value') format
3177							value = value.replace(urlOrStrRegExp, processUrl);
3178							styles[name] = isEncoded ? decode(value, true) : value;
3179						}
3180
3181						styleRegExp.lastIndex = matches.index + matches[0].length;
3182					}
3183					// Compress the styles to reduce it's size for example IE will expand styles
3184					compress("border", "", true);
3185					compress("border", "-width");
3186					compress("border", "-color");
3187					compress("border", "-style");
3188					compress("padding", "");
3189					compress("margin", "");
3190					compress2('border', 'border-width', 'border-style', 'border-color');
3191
3192					// Remove pointless border, IE produces these
3193					if (styles.border === 'medium none') {
3194						delete styles.border;
3195					}
3196
3197					// IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
3198					// So lets asume it shouldn't be there
3199					if (styles['border-image'] === 'none') {
3200						delete styles['border-image'];
3201					}
3202				}
3203
3204				return styles;
3205			},
3206
3207			/**
3208			 * Serializes the specified style object into a string.
3209			 *
3210			 * @method serialize
3211			 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
3212			 * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
3213			 * @return {String} String representation of the style object for example: border: 1px solid red.
3214			 */
3215			serialize: function(styles, elementName) {
3216				var css = '', name, value;
3217
3218				function serializeStyles(name) {
3219					var styleList, i, l, value;
3220
3221					styleList = validStyles[name];
3222					if (styleList) {
3223						for (i = 0, l = styleList.length; i < l; i++) {
3224							name = styleList[i];
3225							value = styles[name];
3226
3227							if (value !== undef && value.length > 0) {
3228								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
3229							}
3230						}
3231					}
3232				}
3233
3234				function isValid(name, elementName) {
3235					var styleMap;
3236
3237					styleMap = invalidStyles['*'];
3238					if (styleMap && styleMap[name]) {
3239						return false;
3240					}
3241
3242					styleMap = invalidStyles[elementName];
3243					if (styleMap && styleMap[name]) {
3244						return false;
3245					}
3246
3247					return true;
3248				}
3249
3250				// Serialize styles according to schema
3251				if (elementName && validStyles) {
3252					// Serialize global styles and element specific styles
3253					serializeStyles('*');
3254					serializeStyles(elementName);
3255				} else {
3256					// Output the styles in the order they are inside the object
3257					for (name in styles) {
3258						value = styles[name];
3259
3260						if (value !== undef && value.length > 0) {
3261							if (!invalidStyles || isValid(name, elementName)) {
3262								css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
3263							}
3264						}
3265					}
3266				}
3267
3268				return css;
3269			}
3270		};
3271	};
3272});
3273
3274// Included from: js/tinymce/classes/dom/TreeWalker.js
3275
3276/**
3277 * TreeWalker.js
3278 *
3279 * Copyright, Moxiecode Systems AB
3280 * Released under LGPL License.
3281 *
3282 * License: http://www.tinymce.com/license
3283 * Contributing: http://www.tinymce.com/contributing
3284 */
3285
3286/**
3287 * TreeWalker class enables you to walk the DOM in a linear manner.
3288 *
3289 * @class tinymce.dom.TreeWalker
3290 * @example
3291 * var walker = new tinymce.dom.TreeWalker(startNode);
3292 *
3293 * do {
3294 *     console.log(walker.current());
3295 * } while (walker.next());
3296 */
3297define("tinymce/dom/TreeWalker", [], function() {
3298	/**
3299	 * Constructs a new TreeWalker instance.
3300	 *
3301	 * @constructor
3302	 * @method TreeWalker
3303	 * @param {Node} startNode Node to start walking from.
3304	 * @param {node} rootNode Optional root node to never walk out of.
3305	 */
3306	return function(startNode, rootNode) {
3307		var node = startNode;
3308
3309		function findSibling(node, startName, siblingName, shallow) {
3310			var sibling, parent;
3311
3312			if (node) {
3313				// Walk into nodes if it has a start
3314				if (!shallow && node[startName]) {
3315					return node[startName];
3316				}
3317
3318				// Return the sibling if it has one
3319				if (node != rootNode) {
3320					sibling = node[siblingName];
3321					if (sibling) {
3322						return sibling;
3323					}
3324
3325					// Walk up the parents to look for siblings
3326					for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
3327						sibling = parent[siblingName];
3328						if (sibling) {
3329							return sibling;
3330						}
3331					}
3332				}
3333			}
3334		}
3335
3336		/**
3337		 * Returns the current node.
3338		 *
3339		 * @method current
3340		 * @return {Node} Current node where the walker is.
3341		 */
3342		this.current = function() {
3343			return node;
3344		};
3345
3346		/**
3347		 * Walks to the next node in tree.
3348		 *
3349		 * @method next
3350		 * @return {Node} Current node where the walker is after moving to the next node.
3351		 */
3352		this.next = function(shallow) {
3353			node = findSibling(node, 'firstChild', 'nextSibling', shallow);
3354			return node;
3355		};
3356
3357		/**
3358		 * Walks to the previous node in tree.
3359		 *
3360		 * @method prev
3361		 * @return {Node} Current node where the walker is after moving to the previous node.
3362		 */
3363		this.prev = function(shallow) {
3364			node = findSibling(node, 'lastChild', 'previousSibling', shallow);
3365			return node;
3366		};
3367	};
3368});
3369
3370// Included from: js/tinymce/classes/dom/Range.js
3371
3372/**
3373 * Range.js
3374 *
3375 * Copyright, Moxiecode Systems AB
3376 * Released under LGPL License.
3377 *
3378 * License: http://www.tinymce.com/license
3379 * Contributing: http://www.tinymce.com/contributing
3380 */
3381
3382define("tinymce/dom/Range", [
3383	"tinymce/util/Tools"
3384], function(Tools) {
3385	// Range constructor
3386	function Range(dom) {
3387		var self = this,
3388			doc = dom.doc,
3389			EXTRACT = 0,
3390			CLONE = 1,
3391			DELETE = 2,
3392			TRUE = true,
3393			FALSE = false,
3394			START_OFFSET = 'startOffset',
3395			START_CONTAINER = 'startContainer',
3396			END_CONTAINER = 'endContainer',
3397			END_OFFSET = 'endOffset',
3398			extend = Tools.extend,
3399			nodeIndex = dom.nodeIndex;
3400
3401		function createDocumentFragment() {
3402			return doc.createDocumentFragment();
3403		}
3404
3405		function setStart(n, o) {
3406			_setEndPoint(TRUE, n, o);
3407		}
3408
3409		function setEnd(n, o) {
3410			_setEndPoint(FALSE, n, o);
3411		}
3412
3413		function setStartBefore(n) {
3414			setStart(n.parentNode, nodeIndex(n));
3415		}
3416
3417		function setStartAfter(n) {
3418			setStart(n.parentNode, nodeIndex(n) + 1);
3419		}
3420
3421		function setEndBefore(n) {
3422			setEnd(n.parentNode, nodeIndex(n));
3423		}
3424
3425		function setEndAfter(n) {
3426			setEnd(n.parentNode, nodeIndex(n) + 1);
3427		}
3428
3429		function collapse(ts) {
3430			if (ts) {
3431				self[END_CONTAINER] = self[START_CONTAINER];
3432				self[END_OFFSET] = self[START_OFFSET];
3433			} else {
3434				self[START_CONTAINER] = self[END_CONTAINER];
3435				self[START_OFFSET] = self[END_OFFSET];
3436			}
3437
3438			self.collapsed = TRUE;
3439		}
3440
3441		function selectNode(n) {
3442			setStartBefore(n);
3443			setEndAfter(n);
3444		}
3445
3446		function selectNodeContents(n) {
3447			setStart(n, 0);
3448			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
3449		}
3450
3451		function compareBoundaryPoints(h, r) {
3452			var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
3453			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
3454
3455			// Check START_TO_START
3456			if (h === 0) {
3457				return _compareBoundaryPoints(sc, so, rsc, rso);
3458			}
3459
3460			// Check START_TO_END
3461			if (h === 1) {
3462				return _compareBoundaryPoints(ec, eo, rsc, rso);
3463			}
3464
3465			// Check END_TO_END
3466			if (h === 2) {
3467				return _compareBoundaryPoints(ec, eo, rec, reo);
3468			}
3469
3470			// Check END_TO_START
3471			if (h === 3) {
3472				return _compareBoundaryPoints(sc, so, rec, reo);
3473			}
3474		}
3475
3476		function deleteContents() {
3477			_traverse(DELETE);
3478		}
3479
3480		function extractContents() {
3481			return _traverse(EXTRACT);
3482		}
3483
3484		function cloneContents() {
3485			return _traverse(CLONE);
3486		}
3487
3488		function insertNode(n) {
3489			var startContainer = this[START_CONTAINER],
3490				startOffset = this[START_OFFSET], nn, o;
3491
3492			// Node is TEXT_NODE or CDATA
3493			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
3494				if (!startOffset) {
3495					// At the start of text
3496					startContainer.parentNode.insertBefore(n, startContainer);
3497				} else if (startOffset >= startContainer.nodeValue.length) {
3498					// At the end of text
3499					dom.insertAfter(n, startContainer);
3500				} else {
3501					// Middle, need to split
3502					nn = startContainer.splitText(startOffset);
3503					startContainer.parentNode.insertBefore(n, nn);
3504				}
3505			} else {
3506				// Insert element node
3507				if (startContainer.childNodes.length > 0) {
3508					o = startContainer.childNodes[startOffset];
3509				}
3510
3511				if (o) {
3512					startContainer.insertBefore(n, o);
3513				} else {
3514					if (startContainer.nodeType == 3) {
3515						dom.insertAfter(n, startContainer);
3516					} else {
3517						startContainer.appendChild(n);
3518					}
3519				}
3520			}
3521		}
3522
3523		function surroundContents(n) {
3524			var f = self.extractContents();
3525
3526			self.insertNode(n);
3527			n.appendChild(f);
3528			self.selectNode(n);
3529		}
3530
3531		function cloneRange() {
3532			return extend(new Range(dom), {
3533				startContainer: self[START_CONTAINER],
3534				startOffset: self[START_OFFSET],
3535				endContainer: self[END_CONTAINER],
3536				endOffset: self[END_OFFSET],
3537				collapsed: self.collapsed,
3538				commonAncestorContainer: self.commonAncestorContainer
3539			});
3540		}
3541
3542		// Private methods
3543
3544		function _getSelectedNode(container, offset) {
3545			var child;
3546
3547			if (container.nodeType == 3 /* TEXT_NODE */) {
3548				return container;
3549			}
3550
3551			if (offset < 0) {
3552				return container;
3553			}
3554
3555			child = container.firstChild;
3556			while (child && offset > 0) {
3557				--offset;
3558				child = child.nextSibling;
3559			}
3560
3561			if (child) {
3562				return child;
3563			}
3564
3565			return container;
3566		}
3567
3568		function _isCollapsed() {
3569			return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
3570		}
3571
3572		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
3573			var c, offsetC, n, cmnRoot, childA, childB;
3574
3575			// In the first case the boundary-points have the same container. A is before B
3576			// if its offset is less than the offset of B, A is equal to B if its offset is
3577			// equal to the offset of B, and A is after B if its offset is greater than the
3578			// offset of B.
3579			if (containerA == containerB) {
3580				if (offsetA == offsetB) {
3581					return 0; // equal
3582				}
3583
3584				if (offsetA < offsetB) {
3585					return -1; // before
3586				}
3587
3588				return 1; // after
3589			}
3590
3591			// In the second case a child node C of the container of A is an ancestor
3592			// container of B. In this case, A is before B if the offset of A is less than or
3593			// equal to the index of the child node C and A is after B otherwise.
3594			c = containerB;
3595			while (c && c.parentNode != containerA) {
3596				c = c.parentNode;
3597			}
3598
3599			if (c) {
3600				offsetC = 0;
3601				n = containerA.firstChild;
3602
3603				while (n != c && offsetC < offsetA) {
3604					offsetC++;
3605					n = n.nextSibling;
3606				}
3607
3608				if (offsetA <= offsetC) {
3609					return -1; // before
3610				}
3611
3612				return 1; // after
3613			}
3614
3615			// In the third case a child node C of the container of B is an ancestor container
3616			// of A. In this case, A is before B if the index of the child node C is less than
3617			// the offset of B and A is after B otherwise.
3618			c = containerA;
3619			while (c && c.parentNode != containerB) {
3620				c = c.parentNode;
3621			}
3622
3623			if (c) {
3624				offsetC = 0;
3625				n = containerB.firstChild;
3626
3627				while (n != c && offsetC < offsetB) {
3628					offsetC++;
3629					n = n.nextSibling;
3630				}
3631
3632				if (offsetC < offsetB) {
3633					return -1; // before
3634				}
3635
3636				return 1; // after
3637			}
3638
3639			// In the fourth case, none of three other cases hold: the containers of A and B
3640			// are siblings or descendants of sibling nodes. In this case, A is before B if
3641			// the container of A is before the container of B in a pre-order traversal of the
3642			// Ranges' context tree and A is after B otherwise.
3643			cmnRoot = dom.findCommonAncestor(containerA, containerB);
3644			childA = containerA;
3645
3646			while (childA && childA.parentNode != cmnRoot) {
3647				childA = childA.parentNode;
3648			}
3649
3650			if (!childA) {
3651				childA = cmnRoot;
3652			}
3653
3654			childB = containerB;
3655			while (childB && childB.parentNode != cmnRoot) {
3656				childB = childB.parentNode;
3657			}
3658
3659			if (!childB) {
3660				childB = cmnRoot;
3661			}
3662
3663			if (childA == childB) {
3664				return 0; // equal
3665			}
3666
3667			n = cmnRoot.firstChild;
3668			while (n) {
3669				if (n == childA) {
3670					return -1; // before
3671				}
3672
3673				if (n == childB) {
3674					return 1; // after
3675				}
3676
3677				n = n.nextSibling;
3678			}
3679		}
3680
3681		function _setEndPoint(st, n, o) {
3682			var ec, sc;
3683
3684			if (st) {
3685				self[START_CONTAINER] = n;
3686				self[START_OFFSET] = o;
3687			} else {
3688				self[END_CONTAINER] = n;
3689				self[END_OFFSET] = o;
3690			}
3691
3692			// If one boundary-point of a Range is set to have a root container
3693			// other than the current one for the Range, the Range is collapsed to
3694			// the new position. This enforces the restriction that both boundary-
3695			// points of a Range must have the same root container.
3696			ec = self[END_CONTAINER];
3697			while (ec.parentNode) {
3698				ec = ec.parentNode;
3699			}
3700
3701			sc = self[START_CONTAINER];
3702			while (sc.parentNode) {
3703				sc = sc.parentNode;
3704			}
3705
3706			if (sc == ec) {
3707				// The start position of a Range is guaranteed to never be after the
3708				// end position. To enforce this restriction, if the start is set to
3709				// be at a position after the end, the Range is collapsed to that
3710				// position.
3711				if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
3712					self.collapse(st);
3713				}
3714			} else {
3715				self.collapse(st);
3716			}
3717
3718			self.collapsed = _isCollapsed();
3719			self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
3720		}
3721
3722		function _traverse(how) {
3723			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
3724
3725			if (self[START_CONTAINER] == self[END_CONTAINER]) {
3726				return _traverseSameContainer(how);
3727			}
3728
3729			for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
3730				if (p == self[START_CONTAINER]) {
3731					return _traverseCommonStartContainer(c, how);
3732				}
3733
3734				++endContainerDepth;
3735			}
3736
3737			for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
3738				if (p == self[END_CONTAINER]) {
3739					return _traverseCommonEndContainer(c, how);
3740				}
3741
3742				++startContainerDepth;
3743			}
3744
3745			depthDiff = startContainerDepth - endContainerDepth;
3746
3747			startNode = self[START_CONTAINER];
3748			while (depthDiff > 0) {
3749				startNode = startNode.parentNode;
3750				depthDiff--;
3751			}
3752
3753			endNode = self[END_CONTAINER];
3754			while (depthDiff < 0) {
3755				endNode = endNode.parentNode;
3756				depthDiff++;
3757			}
3758
3759			// ascend the ancestor hierarchy until we have a common parent.
3760			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
3761				startNode = sp;
3762				endNode = ep;
3763			}
3764
3765			return _traverseCommonAncestors(startNode, endNode, how);
3766		}
3767
3768		function _traverseSameContainer(how) {
3769			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
3770
3771			if (how != DELETE) {
3772				frag = createDocumentFragment();
3773			}
3774
3775			// If selection is empty, just return the fragment
3776			if (self[START_OFFSET] == self[END_OFFSET]) {
3777				return frag;
3778			}
3779
3780			// Text node needs special case handling
3781			if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
3782				// get the substring
3783				s = self[START_CONTAINER].nodeValue;
3784				sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
3785
3786				// set the original text node to its new value
3787				if (how != CLONE) {
3788					n = self[START_CONTAINER];
3789					start = self[START_OFFSET];
3790					len = self[END_OFFSET] - self[START_OFFSET];
3791
3792					if (start === 0 && len >= n.nodeValue.length - 1) {
3793						n.parentNode.removeChild(n);
3794					} else {
3795						n.deleteData(start, len);
3796					}
3797
3798					// Nothing is partially selected, so collapse to start point
3799					self.collapse(TRUE);
3800				}
3801
3802				if (how == DELETE) {
3803					return;
3804				}
3805
3806				if (sub.length > 0) {
3807					frag.appendChild(doc.createTextNode(sub));
3808				}
3809
3810				return frag;
3811			}
3812
3813			// Copy nodes between the start/end offsets.
3814			n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
3815			cnt = self[END_OFFSET] - self[START_OFFSET];
3816
3817			while (n && cnt > 0) {
3818				sibling = n.nextSibling;
3819				xferNode = _traverseFullySelected(n, how);
3820
3821				if (frag) {
3822					frag.appendChild(xferNode);
3823				}
3824
3825				--cnt;
3826				n = sibling;
3827			}
3828
3829			// Nothing is partially selected, so collapse to start point
3830			if (how != CLONE) {
3831				self.collapse(TRUE);
3832			}
3833
3834			return frag;
3835		}
3836
3837		function _traverseCommonStartContainer(endAncestor, how) {
3838			var frag, n, endIdx, cnt, sibling, xferNode;
3839
3840			if (how != DELETE) {
3841				frag = createDocumentFragment();
3842			}
3843
3844			n = _traverseRightBoundary(endAncestor, how);
3845
3846			if (frag) {
3847				frag.appendChild(n);
3848			}
3849
3850			endIdx = nodeIndex(endAncestor);
3851			cnt = endIdx - self[START_OFFSET];
3852
3853			if (cnt <= 0) {
3854				// Collapse to just before the endAncestor, which
3855				// is partially selected.
3856				if (how != CLONE) {
3857					self.setEndBefore(endAncestor);
3858					self.collapse(FALSE);
3859				}
3860
3861				return frag;
3862			}
3863
3864			n = endAncestor.previousSibling;
3865			while (cnt > 0) {
3866				sibling = n.previousSibling;
3867				xferNode = _traverseFullySelected(n, how);
3868
3869				if (frag) {
3870					frag.insertBefore(xferNode, frag.firstChild);
3871				}
3872
3873				--cnt;
3874				n = sibling;
3875			}
3876
3877			// Collapse to just before the endAncestor, which
3878			// is partially selected.
3879			if (how != CLONE) {
3880				self.setEndBefore(endAncestor);
3881				self.collapse(FALSE);
3882			}
3883
3884			return frag;
3885		}
3886
3887		function _traverseCommonEndContainer(startAncestor, how) {
3888			var frag, startIdx, n, cnt, sibling, xferNode;
3889
3890			if (how != DELETE) {
3891				frag = createDocumentFragment();
3892			}
3893
3894			n = _traverseLeftBoundary(startAncestor, how);
3895			if (frag) {
3896				frag.appendChild(n);
3897			}
3898
3899			startIdx = nodeIndex(startAncestor);
3900			++startIdx; // Because we already traversed it
3901
3902			cnt = self[END_OFFSET] - startIdx;
3903			n = startAncestor.nextSibling;
3904			while (n && cnt > 0) {
3905				sibling = n.nextSibling;
3906				xferNode = _traverseFullySelected(n, how);
3907
3908				if (frag) {
3909					frag.appendChild(xferNode);
3910				}
3911
3912				--cnt;
3913				n = sibling;
3914			}
3915
3916			if (how != CLONE) {
3917				self.setStartAfter(startAncestor);
3918				self.collapse(TRUE);
3919			}
3920
3921			return frag;
3922		}
3923
3924		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
3925			var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
3926
3927			if (how != DELETE) {
3928				frag = createDocumentFragment();
3929			}
3930
3931			n = _traverseLeftBoundary(startAncestor, how);
3932			if (frag) {
3933				frag.appendChild(n);
3934			}
3935
3936			startOffset = nodeIndex(startAncestor);
3937			endOffset = nodeIndex(endAncestor);
3938			++startOffset;
3939
3940			cnt = endOffset - startOffset;
3941			sibling = startAncestor.nextSibling;
3942
3943			while (cnt > 0) {
3944				nextSibling = sibling.nextSibling;
3945				n = _traverseFullySelected(sibling, how);
3946
3947				if (frag) {
3948					frag.appendChild(n);
3949				}
3950
3951				sibling = nextSibling;
3952				--cnt;
3953			}
3954
3955			n = _traverseRightBoundary(endAncestor, how);
3956
3957			if (frag) {
3958				frag.appendChild(n);
3959			}
3960
3961			if (how != CLONE) {
3962				self.setStartAfter(startAncestor);
3963				self.collapse(TRUE);
3964			}
3965
3966			return frag;
3967		}
3968
3969		function _traverseRightBoundary(root, how) {
3970			var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
3971			var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
3972
3973			if (next == root) {
3974				return _traverseNode(next, isFullySelected, FALSE, how);
3975			}
3976
3977			parent = next.parentNode;
3978			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
3979
3980			while (parent) {
3981				while (next) {
3982					prevSibling = next.previousSibling;
3983					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
3984
3985					if (how != DELETE) {
3986						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
3987					}
3988
3989					isFullySelected = TRUE;
3990					next = prevSibling;
3991				}
3992
3993				if (parent == root) {
3994					return clonedParent;
3995				}
3996
3997				next = parent.previousSibling;
3998				parent = parent.parentNode;
3999
4000				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
4001
4002				if (how != DELETE) {
4003					clonedGrandParent.appendChild(clonedParent);
4004				}
4005
4006				clonedParent = clonedGrandParent;
4007			}
4008		}
4009
4010		function _traverseLeftBoundary(root, how) {
4011			var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
4012			var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
4013
4014			if (next == root) {
4015				return _traverseNode(next, isFullySelected, TRUE, how);
4016			}
4017
4018			parent = next.parentNode;
4019			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
4020
4021			while (parent) {
4022				while (next) {
4023					nextSibling = next.nextSibling;
4024					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
4025
4026					if (how != DELETE) {
4027						clonedParent.appendChild(clonedChild);
4028					}
4029
4030					isFullySelected = TRUE;
4031					next = nextSibling;
4032				}
4033
4034				if (parent == root) {
4035					return clonedParent;
4036				}
4037
4038				next = parent.nextSibling;
4039				parent = parent.parentNode;
4040
4041				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
4042
4043				if (how != DELETE) {
4044					clonedGrandParent.appendChild(clonedParent);
4045				}
4046
4047				clonedParent = clonedGrandParent;
4048			}
4049		}
4050
4051		function _traverseNode(n, isFullySelected, isLeft, how) {
4052			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
4053
4054			if (isFullySelected) {
4055				return _traverseFullySelected(n, how);
4056			}
4057
4058			if (n.nodeType == 3 /* TEXT_NODE */) {
4059				txtValue = n.nodeValue;
4060
4061				if (isLeft) {
4062					offset = self[START_OFFSET];
4063					newNodeValue = txtValue.substring(offset);
4064					oldNodeValue = txtValue.substring(0, offset);
4065				} else {
4066					offset = self[END_OFFSET];
4067					newNodeValue = txtValue.substring(0, offset);
4068					oldNodeValue = txtValue.substring(offset);
4069				}
4070
4071				if (how != CLONE) {
4072					n.nodeValue = oldNodeValue;
4073				}
4074
4075				if (how == DELETE) {
4076					return;
4077				}
4078
4079				newNode = dom.clone(n, FALSE);
4080				newNode.nodeValue = newNodeValue;
4081
4082				return newNode;
4083			}
4084
4085			if (how == DELETE) {
4086				return;
4087			}
4088
4089			return dom.clone(n, FALSE);
4090		}
4091
4092		function _traverseFullySelected(n, how) {
4093			if (how != DELETE) {
4094				return how == CLONE ? dom.clone(n, TRUE) : n;
4095			}
4096
4097			n.parentNode.removeChild(n);
4098		}
4099
4100		function toStringIE() {
4101			return dom.create('body', null, cloneContents()).outerText;
4102		}
4103
4104		extend(self, {
4105			// Inital states
4106			startContainer: doc,
4107			startOffset: 0,
4108			endContainer: doc,
4109			endOffset: 0,
4110			collapsed: TRUE,
4111			commonAncestorContainer: doc,
4112
4113			// Range constants
4114			START_TO_START: 0,
4115			START_TO_END: 1,
4116			END_TO_END: 2,
4117			END_TO_START: 3,
4118
4119			// Public methods
4120			setStart: setStart,
4121			setEnd: setEnd,
4122			setStartBefore: setStartBefore,
4123			setStartAfter: setStartAfter,
4124			setEndBefore: setEndBefore,
4125			setEndAfter: setEndAfter,
4126			collapse: collapse,
4127			selectNode: selectNode,
4128			selectNodeContents: selectNodeContents,
4129			compareBoundaryPoints: compareBoundaryPoints,
4130			deleteContents: deleteContents,
4131			extractContents: extractContents,
4132			cloneContents: cloneContents,
4133			insertNode: insertNode,
4134			surroundContents: surroundContents,
4135			cloneRange: cloneRange,
4136			toStringIE: toStringIE
4137		});
4138
4139		return self;
4140	}
4141
4142	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
4143	Range.prototype.toString = function() {
4144		return this.toStringIE();
4145	};
4146
4147	return Range;
4148});
4149
4150// Included from: js/tinymce/classes/html/Entities.js
4151
4152/**
4153 * Entities.js
4154 *
4155 * Copyright, Moxiecode Systems AB
4156 * Released under LGPL License.
4157 *
4158 * License: http://www.tinymce.com/license
4159 * Contributing: http://www.tinymce.com/contributing
4160 */
4161
4162/*jshint bitwise:false */
4163/*eslint no-bitwise:0 */
4164
4165/**
4166 * Entity encoder class.
4167 *
4168 * @class tinymce.html.Entities
4169 * @static
4170 * @version 3.4
4171 */
4172define("tinymce/html/Entities", [
4173	"tinymce/util/Tools"
4174], function(Tools) {
4175	var makeMap = Tools.makeMap;
4176
4177	var namedEntities, baseEntities, reverseEntities,
4178		attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
4179		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
4180		rawCharsRegExp = /[<>&\"\']/g,
4181		entityRegExp = /&(#x|#)?([\w]+);/g,
4182		asciiMap = {
4183			128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
4184			135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
4185			142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
4186			150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
4187			156: "\u0153", 158: "\u017E", 159: "\u0178"
4188		};
4189
4190	// Raw entities
4191	baseEntities = {
4192		'\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
4193		"'": '&#39;',
4194		'<': '&lt;',
4195		'>': '&gt;',
4196		'&': '&amp;',
4197		'\u0060': '&#96;'
4198	};
4199
4200	// Reverse lookup table for raw entities
4201	reverseEntities = {
4202		'&lt;': '<',
4203		'&gt;': '>',
4204		'&amp;': '&',
4205		'&quot;': '"',
4206		'&apos;': "'"
4207	};
4208
4209	// Decodes text by using the browser
4210	function nativeDecode(text) {
4211		var elm;
4212
4213		elm = document.createElement("div");
4214		elm.innerHTML = text;
4215
4216		return elm.textContent || elm.innerText || text;
4217	}
4218
4219	// Build a two way lookup table for the entities
4220	function buildEntitiesLookup(items, radix) {
4221		var i, chr, entity, lookup = {};
4222
4223		if (items) {
4224			items = items.split(',');
4225			radix = radix || 10;
4226
4227			// Build entities lookup table
4228			for (i = 0; i < items.length; i += 2) {
4229				chr = String.fromCharCode(parseInt(items[i], radix));
4230
4231				// Only add non base entities
4232				if (!baseEntities[chr]) {
4233					entity = '&' + items[i + 1] + ';';
4234					lookup[chr] = entity;
4235					lookup[entity] = chr;
4236				}
4237			}
4238
4239			return lookup;
4240		}
4241	}
4242
4243	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
4244	namedEntities = buildEntitiesLookup(
4245		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
4246		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
4247		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
4248		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
4249		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
4250		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
4251		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
4252		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
4253		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
4254		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
4255		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
4256		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
4257		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
4258		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
4259		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
4260		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
4261		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
4262		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
4263		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
4264		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
4265		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
4266		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
4267		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
4268		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
4269		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
4270
4271	var Entities = {
4272		/**
4273		 * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded.
4274		 *
4275		 * @method encodeRaw
4276		 * @param {String} text Text to encode.
4277		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4278		 * @return {String} Entity encoded text.
4279		 */
4280		encodeRaw: function(text, attr) {
4281			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4282				return baseEntities[chr] || chr;
4283			});
4284		},
4285
4286		/**
4287		 * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
4288		 * since it doesn't know if the context is within a attribute or text node. This was added for c
4288ompatibility
4289		 * and is exposed as the DOMUtils.encode function.
4290		 *
4291		 * @method encodeAllRaw
4292		 * @param {String} text Text to encode.
4293		 * @return {String} Entity encoded text.
4294		 */
4295		encodeAllRaw: function(text) {
4296			return ('' + text).replace(rawCharsRegExp, function(chr) {
4297				return baseEntities[chr] || chr;
4298			});
4299		},
4300
4301		/**
4302		 * Encodes the specified string using numeric entities. The core entities will be
4303		 * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
4304		 *
4305		 * @method encodeNumeric
4306		 * @param {String} text Text to encode.
4307		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4308		 * @return {String} Entity encoded text.
4309		 */
4310		encodeNumeric: function(text, attr) {
4311			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4312				// Multi byte sequence convert it to a single entity
4313				if (chr.length > 1) {
4314					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
4315				}
4316
4317				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
4318			});
4319		},
4320
4321		/**
4322		 * Encodes the specified string using named entities. The core entities will be encoded
4323		 * as named ones but all non lower ascii characters will be encoded into named entities.
4324		 *
4325		 * @method encodeNamed
4326		 * @param {String} text Text to encode.
4327		 * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4328		 * @param {Object} entities Optional parameter with entities to use.
4329		 * @return {String} Entity encoded text.
4330		 */
4331		encodeNamed: function(text, attr, entities) {
4332			entities = entities || namedEntities;
4333
4334			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4335				return baseEntities[chr] || entities[chr] || chr;
4336			});
4337		},
4338
4339		/**
4340		 * Returns an encode function based on the name(s) and it's optional entities.
4341		 *
4342		 * @method getEncodeFunc
4343		 * @param {String} name Comma separated list of encoders for example named,numeric.
4344		 * @param {String} entities Optional parameter with entities to use instead of the built in set.
4345		 * @return {function} Encode function to be used.
4346		 */
4347		getEncodeFunc: function(name, entities) {
4348			entities = buildEntitiesLookup(entities) || namedEntities;
4349
4350			function encodeNamedAndNumeric(text, attr) {
4351				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
4352					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
4353				});
4354			}
4355
4356			function encodeCustomNamed(text, attr) {
4357				return Entities.encodeNamed(text, attr, entities);
4358			}
4359
4360			// Replace + with , to be compatible with previous TinyMCE versions
4361			name = makeMap(name.replace(/\+/g, ','));
4362
4363			// Named and numeric encoder
4364			if (name.named && name.numeric) {
4365				return encodeNamedAndNumeric;
4366			}
4367
4368			// Named encoder
4369			if (name.named) {
4370				// Custom names
4371				if (entities) {
4372					return encodeCustomNamed;
4373				}
4374
4375				return Entities.encodeNamed;
4376			}
4377
4378			// Numeric
4379			if (name.numeric) {
4380				return Entities.encodeNumeric;
4381			}
4382
4383			// Raw encoder
4384			return Entities.encodeRaw;
4385		},
4386
4387		/**
4388		 * Decodes the specified string, this will replace entities with raw UTF characters.
4389		 *
4390		 * @method decode
4391		 * @param {String} text Text to entity decode.
4392		 * @return {String} Entity decoded string.
4393		 */
4394		decode: function(text) {
4395			return text.replace(entityRegExp, function(all, numeric, value) {
4396				if (numeric) {
4397					value = parseInt(value, numeric.length === 2 ? 16 : 10);
4398
4399					// Support upper UTF
4400					if (value > 0xFFFF) {
4401						value -= 0x10000;
4402
4403						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
4404					} else {
4405						return asciiMap[value] || String.fromCharCode(value);
4406					}
4407				}
4408
4409				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
4410			});
4411		}
4412	};
4413
4414	return Entities;
4415});
4416
4417// Included from: js/tinymce/classes/dom/StyleSheetLoader.js
4418
4419/**
4420 * StyleSheetLoader.js
4421 *
4422 * Copyright, Moxiecode Systems AB
4423 * Released under LGPL License.
4424 *
4425 * License: http://www.tinymce.com/license
4426 * Contributing: http://www.tinymce.com/contributing
4427 */
4428
4429/**
4430 * This class handles loading of external stylesheets and fires events when these are loaded.
4431 *
4432 * @class tinymce.dom.StyleSheetLoader
4433 * @private
4434 */
4435define("tinymce/dom/StyleSheetLoader", [
4436	"tinymce/util/Tools"
4437], function(Tools) {
4438	"use strict";
4439
4440	return function(document, settings) {
4441		var idCount = 0, loadedStates = {}, maxLoadTime;
4442
4443		settings = settings || {};
4444		maxLoadTime = settings.maxLoadTime || 5000;
4445
4446		function appendToHead(node) {
4447			document.getElementsByTagName('head')[0].appendChild(node);
4448		}
4449
4450		/**
4451		 * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
4452		 *
4453		 * @method load
4454		 * @param {String} url Url to be loaded.
4455		 * @param {Function} loadedCallback Callback to be executed when loaded.
4456		 * @param {Function} errorCallback Callback to be executed when failed loading.
4457		 */
4458		function load(url, loadedCallback, errorCallback) {
4459			var link, style, startTime, state;
4460
4461			function passed() {
4462				var callbacks = state.passed, i = callbacks.length;
4463
4464				while (i--) {
4465					callbacks[i]();
4466				}
4467
4468				state.status = 2;
4469				state.passed = [];
4470				state.failed = [];
4471			}
4472
4473			function failed() {
4474				var callbacks = state.failed, i = callbacks.length;
4475
4476				while (i--) {
4477					callbacks[i]();
4478				}
4479
4480				state.status = 3;
4481				state.passed = [];
4482				state.failed = [];
4483			}
4484
4485			// Sniffs for older WebKit versions that have the link.onload but a broken one
4486			function isOldWebKit() {
4487				var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
4488				return !!(webKitChunks && webKitChunks[1] < 536);
4489			}
4490
4491			// Calls the waitCallback until the test returns true or the timeout occurs
4492			function wait(testCallback, waitCallback) {
4493				if (!testCallback()) {
4494					// Wait for timeout
4495					if ((new Date().getTime()) - startTime < maxLoadTime) {
4496						window.setTimeout(waitCallback, 0);
4497					} else {
4498						failed();
4499					}
4500				}
4501			}
4502
4503			// Workaround for WebKit that doesn't properly support the onload event for link elements
4504			// Or WebKit that fires the onload event before the StyleSheet is added to the document
4505			function waitForWebKitLinkLoaded() {
4506				wait(function() {
4507					var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
4508
4509					while (i--) {
4510						styleSheet = styleSheets[i];
4511						owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
4512						if (owner && owner.id === link.id) {
4513							passed();
4514							return true;
4515						}
4516					}
4517				}, waitForWebKitLinkLoaded);
4518			}
4519
4520			// Workaround for older Geckos that doesn't have any onload event for StyleSheets
4521			function waitForGeckoLinkLoaded() {
4522				wait(function() {
4523					try {
4524						// Accessing the cssRules will throw an exception until the CSS file is loaded
4525						var cssRules = style.sheet.cssRules;
4526						passed();
4527						return !!cssRules;
4528					} catch (ex) {
4529						// Ignore
4530					}
4531				}, waitForGeckoLinkLoaded);
4532			}
4533
4534			url = Tools._addCacheSuffix(url);
4535
4536			if (!loadedStates[url]) {
4537				state = {
4538					passed: [],
4539					failed: []
4540				};
4541
4542				loadedStates[url] = state;
4543			} else {
4544				state = loadedStates[url];
4545			}
4546
4547			if (loadedCallback) {
4548				state.passed.push(loadedCallback);
4549			}
4550
4551			if (errorCallback) {
4552				state.failed.push(errorCallback);
4553			}
4554
4555			// Is loading wait for it to pass
4556			if (state.status == 1) {
4557				return;
4558			}
4559
4560			// Has finished loading and was success
4561			if (state.status == 2) {
4562				passed();
4563				return;
4564			}
4565
4566			// Has finished loading and was a failure
4567			if (state.status == 3) {
4568				failed();
4569				return;
4570			}
4571
4572			// Start loading
4573			state.status = 1;
4574			link = document.createElement('link');
4575			link.rel = 'stylesheet';
4576			link.type = 'text/css';
4577			link.id = 'u' + (idCount++);
4578			link.async = false;
4579			link.defer = false;
4580			startTime = new Date().getTime();
4581
4582			// Feature detect onload on link element and sniff older webkits since it has an broken onload event
4583			if ("onload" in link && !isOldWebKit()) {
4584				link.onload = waitForWebKitLinkLoaded;
4585				link.onerror = failed;
4586			} else {
4587				// Sniff for old Firefox that doesn't support the onload event on link elements
4588				// TODO: Remove this in the future when everyone uses modern browsers
4589				if (navigator.userAgent.indexOf("Firefox") > 0) {
4590					style = document.createElement('style');
4591					style.textContent = '@import "' + url + '"';
4592					waitForGeckoLinkLoaded();
4593					appendToHead(style);
4594					return;
4595				} else {
4596					// Use the id owner on older webkits
4597					waitForWebKitLinkLoaded();
4598				}
4599			}
4600
4601			appendToHead(link);
4602			link.href = url;
4603		}
4604
4605		this.load = load;
4606	};
4607});
4608
4609// Included from: js/tinymce/classes/dom/DOMUtils.js
4610
4611/**
4612 * DOMUtils.js
4613 *
4614 * Copyright, Moxiecode Systems AB
4615 * Released under LGPL License.
4616 *
4617 * License: http://www.tinymce.com/license
4618 * Contributing: http://www.tinymce.com/contributing
4619 */
4620
4621/**
4622 * Utility class for various DOM manipulation and retrieval functions.
4623 *
4624 * @class tinymce.dom.DOMUtils
4625 * @example
4626 * // Add a class to an element by id in the page
4627 * tinymce.DOM.addClass('someid', 'someclass');
4628 *
4629 * // Add a class to an element by id inside the editor
4630 * tinymce.activeEditor.dom.addClass('someid', 'someclass');
4631 */
4632define("tinymce/dom/DOMUtils", [
4633	"tinymce/dom/Sizzle",
4634	"tinymce/dom/DomQuery",
4635	"tinymce/html/Styles",
4636	"tinymce/dom/EventUtils",
4637	"tinymce/dom/TreeWalker",
4638	"tinymce/dom/Range",
4639	"tinymce/html/Entities",
4640	"tinymce/Env",
4641	"tinymce/util/Tools",
4642	"tinymce/dom/StyleSheetLoader"
4643], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
4644	// Shorten names
4645	var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim;
4646	var isIE = Env.ie;
4647	var simpleSelectorRe = /^([a-z0-9],?)+$/i;
4648	var whiteSpaceRegExp = /^[ \t\r\n]*$/;
4649
4650	function setupAttrHooks(domUtils, settings) {
4651		var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook;
4652
4653		keepUrlHook = {
4654			set: function($elm, value, name) {
4655				if (settings.url_converter) {
4656					value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]);
4657				}
4658
4659				$elm.attr('data-mce-' + name, value).attr(name, value);
4660			},
4661
4662			get: function($elm, name) {
4663				return $elm.attr('data-mce-' + name) || $elm.attr(name);
4664			}
4665		};
4666
4667		attrHooks = {
4668			style: {
4669				set: function($elm, value) {
4670					if (value !== null && typeof value === 'object') {
4671						$elm.css(value);
4672						return;
4673					}
4674
4675					if (keepValues) {
4676						$elm.attr('data-mce-style', value);
4677					}
4678
4679					$elm.attr('style', value);
4680				},
4681
4682				get: function($elm) {
4683					var value = $elm.attr('data-mce-style') || $elm.attr('style');
4684
4685					value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
4686
4687					return value;
4688				}
4689			}
4690		};
4691
4692		if (keepValues) {
4693			attrHooks.href = attrHooks.src = keepUrlHook;
4694		}
4695
4696		return attrHooks;
4697	}
4698
4699	/**
4700	 * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
4701	 *
4702	 * @constructor
4703	 * @method DOMUtils
4704	 * @param {Document} d Document reference to bind the utility class to.
4705	 * @param {settings} s Optional settings collection.
4706	 */
4707	function DOMUtils(doc, settings) {
4708		var self = this, blockElementsMap;
4709
4710		self.doc = doc;
4711		self.win = window;
4712		self.files = {};
4713		self.counter = 0;
4714		self.stdMode = !isIE || doc.documentMode >= 8;
4715		self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
4716		self.styleSheetLoader = new StyleSheetLoader(doc);
4717		self.boundEvents = [];
4718		self.settings = settings = settings || {};
4719		self.schema = settings.schema;
4720		self.styles = new Styles({
4721			url_converter: settings.url_converter,
4722			url_converter_scope: settings.url_converter_scope
4723		}, settings.schema);
4724
4725		self.fixDoc(doc);
4726		self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
4727		self.attrHooks = setupAttrHooks(self, settings);
4728		blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
4729		self.$ = $.overrideDefaults(function() {
4730			return {
4731				context: doc,
4732				element: self.getRoot()
4733			};
4734		});
4735
4736		/**
4737		 * Returns true/false if the specified element is a block element or not.
4738		 *
4739		 * @method isBlock
4740		 * @param {Node/String}
4740 node Element/Node to check.
4741		 * @return {Boolean} True/False state if the node is a block element or not.
4742		 */
4743		self.isBlock = function(node) {
4744			// Fix for #5446
4745			if (!node) {
4746				return false;
4747			}
4748
4749			// This function is called in module pattern style since it might be executed with the wrong this scope
4750			var type = node.nodeType;
4751
4752			// If it's a node then check the type and use the nodeName
4753			if (type) {
4754				return !!(type === 1 && blockElementsMap[node.nodeName]);
4755			}
4756
4757			return !!blockElementsMap[node];
4758		};
4759	}
4760
4761	DOMUtils.prototype = {
4762		$$: function(elm) {
4763			if (typeof elm == 'string') {
4764				elm = this.get(elm);
4765			}
4766
4767			return this.$(elm);
4768		},
4769
4770		root: null,
4771
4772		fixDoc: function(doc) {
4773			var settings = this.settings, name;
4774
4775			if (isIE && settings.schema) {
4776				// Add missing HTML 4/5 elements to IE
4777				('abbr article aside audio canvas ' +
4778				'details figcaption figure footer ' +
4779				'header hgroup mark menu meter nav ' +
4780				'output progress section summary ' +
4781				'time video').replace(/\w+/g, function(name) {
4782					doc.createElement(name);
4783				});
4784
4785				// Create all custom elements
4786				for (name in settings.schema.getCustomElements()) {
4787					doc.createElement(name);
4788				}
4789			}
4790		},
4791
4792		clone: function(node, deep) {
4793			var self = this, clone, doc;
4794
4795			// TODO: Add feature detection here in the future
4796			if (!isIE || node.nodeType !== 1 || deep) {
4797				return node.cloneNode(deep);
4798			}
4799
4800			doc = self.doc;
4801
4802			// Make a HTML5 safe shallow copy
4803			if (!deep) {
4804				clone = doc.createElement(node.nodeName);
4805
4806				// Copy attribs
4807				each(self.getAttribs(node), function(attr) {
4808					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
4809				});
4810
4811				return clone;
4812			}
4813
4814			return clone.firstChild;
4815		},
4816
4817		/**
4818		 * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
4819		 * go above the point of this root node.
4820		 *
4821		 * @method getRoot
4822		 * @return {Element} Root element for the utility class.
4823		 */
4824		getRoot: function() {
4825			var self = this;
4826
4827			return self.settings.root_element || self.doc.body;
4828		},
4829
4830		/**
4831		 * Returns the viewport of the window.
4832		 *
4833		 * @method getViewPort
4834		 * @param {Window} win Optional window to get viewport of.
4835		 * @return {Object} Viewport object with fields x, y, w and h.
4836		 */
4837		getViewPort: function(win) {
4838			var doc, rootElm;
4839
4840			win = !win ? this.win : win;
4841			doc = win.document;
4842			rootElm = this.boxModel ? doc.documentElement : doc.body;
4843
4844			// Returns viewport size excluding scrollbars
4845			return {
4846				x: win.pageXOffset || rootElm.scrollLeft,
4847				y: win.pageYOffset || rootElm.scrollTop,
4848				w: win.innerWidth || rootElm.clientWidth,
4849				h: win.innerHeight || rootElm.clientHeight
4850			};
4851		},
4852
4853		/**
4854		 * Returns the rectangle for a specific element.
4855		 *
4856		 * @method getRect
4857		 * @param {Element/String} elm Element object or element ID to get rectangle from.
4858		 * @return {object} Rectangle for specified element object with x, y, w, h fields.
4859		 */
4860		getRect: function(elm) {
4861			var self = this, pos, size;
4862
4863			elm = self.get(elm);
4864			pos = self.getPos(elm);
4865			size = self.getSize(elm);
4866
4867			return {
4868				x: pos.x, y: pos.y,
4869				w: size.w, h: size.h
4870			};
4871		},
4872
4873		/**
4874		 * Returns the size dimensions of the specified element.
4875		 *
4876		 * @method getSize
4877		 * @param {Element/String} elm Element object or element ID to get rectangle from.
4878		 * @return {object} Rectangle for specified element object with w, h fields.
4879		 */
4880		getSize: function(elm) {
4881			var self = this, w, h;
4882
4883			elm = self.get(elm);
4884			w = self.getStyle(elm, 'width');
4885			h = self.getStyle(elm, 'height');
4886
4887			// Non pixel value, then force offset/clientWidth
4888			if (w.indexOf('px') === -1) {
4889				w = 0;
4890			}
4891
4892			// Non pixel value, then force offset/clientWidth
4893			if (h.indexOf('px') === -1) {
4894				h = 0;
4895			}
4896
4897			return {
4898				w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
4899				h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
4900			};
4901		},
4902
4903		/**
4904		 * Returns a node by the specified selector function. This function will
4905		 * loop through all parent nodes and call the specified function for each node.
4906		 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
4907		 * and the node it found will be returned.
4908		 *
4909		 * @method getParent
4910		 * @param {Node/String}
4910 node DOM node to search parents on or ID string.
4911		 * @param {function} selector Selection function or CSS selector to execute on each node.
4912		 * @param {Node} root Optional root element, never go below this point.
4913		 * @return {Node} DOM Node or null if it wasn't found.
4914		 */
4915		getParent: function(node, selector, root) {
4916			return this.getParents(node, selector, root, false);
4917		},
4918
4919		/**
4920		 * Returns a node list of all parents matching the specified selector function or pattern.
4921		 * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
4922		 *
4923		 * @method getParents
4924		 * @param {Node/String} node DOM node to search parents on or ID string.
4925		 * @param {function} selector Selection function to execute on each node or CSS pattern.
4926		 * @param {Node} root Optional root element, never go below this point.
4927		 * @return {Array} Array of nodes or null if it wasn't found.
4928		 */
4929		getParents: function(node, selector, root, collect) {
4930			var self = this, selectorVal, result = [];
4931
4932			node = self.get(node);
4933			collect = collect === undefined;
4934
4935			// Default root on inline mode
4936			root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
4937
4938			// Wrap node name as func
4939			if (is(selector, 'string')) {
4940				selectorVal = selector;
4941
4942				if (selector === '*') {
4943					selector = function(node) {
4944						return node.nodeType == 1;
4945					};
4946				} else {
4947					selector = function(node) {
4948						return self.is(node, selectorVal);
4949					};
4950				}
4951			}
4952
4953			while (node) {
4954				if (node == root || !node.nodeType || node.nodeType === 9) {
4955					break;
4956				}
4957
4958				if (!selector || selector(node)) {
4959					if (collect) {
4960						result.push(node);
4961					} else {
4962						return node;
4963					}
4964				}
4965
4966				node = node.parentNode;
4967			}
4968
4969			return collect ? result : null;
4970		},
4971
4972		/**
4973		 * Returns the specified element by ID or the input element if it isn't a string.
4974		 *
4975		 * @method get
4976		 * @param {String/Element} n Element id to look for or element to just pass though.
4977		 * @return {Element} Element matching the specified id or null if it wasn't found.
4978		 */
4979		get: function(elm) {
4980			var name;
4981
4982			if (elm && this.doc && typeof(elm) == 'string') {
4983				name = elm;
4984				elm = this.doc.getElementById(elm);
4985
4986				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
4987				if (elm && elm.id !== name) {
4988					return this.doc.getElementsByName(name)[1];
4989				}
4990			}
4991
4992			return elm;
4993		},
4994
4995		/**
4996		 * Returns the next node that matches selector or function
4997		 *
4998		 * @method getNext
4999		 * @param {Node} node Node to find siblings from.
5000		 * @param {String/function} selector Selector CSS expression or function.
5001		 * @return {Node} Next node item matching the selector or null if it wasn't found.
5002		 */
5003		getNext: function(node, selector) {
5004			return this._findSib(node, selector, 'nextSibling');
5005		},
5006
5007		/**
5008		 * Returns the previous node that matches selector or function
5009		 *
5010		 * @method getPrev
5011		 * @param {Node} node Node to find siblings from.
5012		 * @param {String/function} selector Selector CSS expression or function.
5013		 * @return {Node} Previous node item matching the selector or null if it wasn't found.
5014		 */
5015		getPrev: function(node, selector) {
5016			return this._findSib(node, selector, 'previousSibling');
5017		},
5018
5019		// #ifndef jquery
5020
5021		/**
5022		 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
5023		 * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
5024		 * on more complex patterns.
5025		 *
5026		 * @method select
5027		 * @param {String} selector CSS level 3 pattern to select/find elements by.
5028		 * @param {Object} scope Optional root element/scope element to search in.
5029		 * @return {Array} Array with all matched elements.
5030		 * @example
5031		 * // Adds a class to all paragraphs in the currently active editor
5032		 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
5033		 *
5034		 * // Adds a class to all spans that have the test class in the currently active editor
5035		 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
5036		 */
5037		select: function(selector, scope) {
5038			var self = this;
5039
5040			/*eslint new-cap:0 */
5041			return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []);
5042		},
5043
5044		/**
5045		 * Returns true/false if the specified element matches the specified css pattern.
5046		 *
5047		 * @method is
5048		 * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
5049		 * @param {String} selector CSS pattern to match the element against.
5050		 */
5051		is: function(elm, selector) {
5052			var i;
5053
5054			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5055			if (elm.length === undefined) {
5056				// Simple all selector
5057				if (selector === '*') {
5058					return elm.nodeType == 1;
5059				}
5060
5061				// Simple selector just elements
5062				if (simpleSelectorRe.test(selector)) {
5063					selector = selector.toLowerCase().split(/,/);
5064					elm = elm.nodeName.toLowerCase();
5065
5066					for (i = selector.length - 1; i >= 0; i--) {
5067						if (selector[i] == elm) {
5068							return true;
5069						}
5070					}
5071
5072					return false;
5073				}
5074			}
5075
5076			// Is non element
5077			if (elm.nodeType && elm.nodeType != 1) {
5078				return false;
5079			}
5080
5081			var elms = elm.nodeType ? [elm] : elm;
5082
5083			/*eslint new-cap:0 */
5084			return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
5085		},
5086
5087		// #endif
5088
5089		/**
5090		 * Adds the specified element to another element or elements.
5091		 *
5092		 * @method add
5093		 * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
5094		 * @param {String/Element} name Name of new element to add or existing element to add.
5095		 * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
5096		 * @param {String} html Optional inner HTML contents to add for each element.
5097		 * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
5098		 * were passed in.
5099		 * @example
5100		 * // Adds a new paragraph to the end of the active editor
5101		 * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
5102		 */
5103		add: function(parentElm, name, attrs, html, create) {
5104			var self = this;
5105
5106			return this.run(parentElm, function(parentElm) {
5107				var newElm;
5108
5109				newElm = is(name, 'string') ? self.doc.createElement(name) : name;
5110				self.setAttribs(newElm, attrs);
5111
5112				if (html) {
5113					if (html.nodeType) {
5114						newElm.appendChild(html);
5115					} else {
5116						self.setHTML(newElm, html);
5117					}
5118				}
5119
5120				return !create ? parentElm.appendChild(newElm) : newElm;
5121			});
5122		},
5123
5124		/**
5125		 * Creates a new element.
5126		 *
5127		 * @method create
5128		 * @param {String} name Name of new element.
5129		 * @param {Object} attrs Optional object name/value collection with element attributes.
5130		 * @param {String} html Optional HTML string to set as inner HTML of the element.
5131		 * @return {Element} HTML DOM node element that got created.
5132		 * @example
5133		 * // Adds an element where the caret/selection is in the active editor
5134		 * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
5135		 * tinymce.activeEditor.selection.setNode(el);
5136		 */
5137		create: function(name, attrs, html) {
5138			return this.add(this.doc.createElement(name), name, attrs, html, 1);
5139		},
5140
5141		/**
5142		 * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
5143		 *
5144		 * @method createHTML
5145		 * @param {String} name Name of new element.
5146		 * @param {Object} attrs Optional object name/value collection with element attributes.
5147		 * @param {String} html Optional HTML string to set as inner HTML of the element.
5148		 * @return {String} String with new HTML element, for example: <a href="#">test</a>.
5149		 * @example
5150		 * // Creates a html chunk and inserts it at the current selection/caret location
5151		 * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
5152		 */
5153		createHTML: function(name, attrs, html) {
5154			var outHtml = '', key;
5155
5156			outHtml += '<' + name;
5157
5158			for (key in attrs) {
5159				if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') {
5160					outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
5161				}
5162			}
5163
5164			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5165			if (typeof(html) != "undefined") {
5166				return outHtml + '>' + html + '</' + name + '>';
5167			}
5168
5169			return outHtml + ' />';
5170		},
5171
5172		/**
5173		 * Creates a document fragment out of the specified HTML string.
5174		 *
5175		 * @method createFragment
5176		 * @param {String} html Html string to create fragment from.
5177		 * @return {DocumentFragment} Document fragment node.
5178		 */
5179		createFragment: function(html) {
5180			var frag, node, doc = this.doc, container;
5181
5182			container = doc.createElement("div");
5183			frag = doc.createDocumentFragment();
5184
5185			if (html) {
5186				container.innerHTML = html;
5187			}
5188
5189			while ((node = container.firstChild)) {
5190				frag.appendChild(node);
5191			}
5192
5193			return frag;
5194		},
5195
5196		/**
5197		 * Removes/deletes the specified element(s) from the DOM.
5198		 *
5199		 * @method remove
5200		 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
5201		 * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
5202		 * placed at the location of the removed element.
5203		 * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
5204		 * were passed in.
5205		 * @example
5206		 * // Removes all paragraphs in the active editor
5207		 * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
5208		 *
5209		 * // Removes an element by id in the document
5210		 * tinymce.DOM.remove('mydiv');
5211		 */
5212		remove: function(node, keepChildren) {
5213			node = this.$$(node);
5214
5215			if (keepChildren) {
5216				node.each(function() {
5217					var child;
5218
5219					while ((child = this.firstChild)) {
5220						if (child.nodeType == 3 && child.data.length === 0) {
5221							this.removeChild(child);
5222						} else {
5223							this.parentNode.insertBefore(child, this);
5224						}
5225					}
5226				}).remove();
5227			} else {
5228				node.remove();
5229			}
5230
5231			return node.length > 1 ? node.toArray() : node[0];
5232		},
5233
5234		/**
5235		 * Sets the CSS style value on a HTML element. The name can be a camelcase string
5236		 * or the CSS style name like background-color.
5237		 *
5238		 * @method setStyle
5239		 * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
5240		 * @param {String} na Name of the style value to set.
5241		 * @param {String} v Value to set on the style.
5242		 * @example
5243		 * // Sets a style value on all paragraphs in the currently active editor
5244		 * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
5245		 *
5246		 * // Sets a style value to an element by id in the current document
5247		 * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
5248		 */
5249		setStyle: function(elm, name, value) {
5250			elm = this.$$(elm).css(name, value);
5251
5252			if (this.settings.update_styles) {
5253				elm.attr('data-mce-style', null);
5254			}
5255		},
5256
5257		/**
5258		 * Returns the current style or runtime/computed value of an element.
5259		 *
5260		 * @method getStyle
5261		 * @param {String/Element} elm HTML element or element id string to get style from.
5262		 * @param {String} name Style name to return.
5263		 * @param {Boolean} computed Computed style.
5264		 * @return {String} Current style or computed style value of an element.
5265		 */
5266		getStyle: function(elm, name, computed) {
5267			elm = this.$$(elm);
5268
5269			if (computed) {
5270				return elm.css(name);
5271			}
5272
5273			// Camelcase it, if needed
5274			name = name.replace(/-(\D)/g, function(a, b) {
5275				return b.toUpperCase();
5276			});
5277
5278			if (name == 'float') {
5279				name = isIE ? 'styleFloat' : 'cssFloat';
5280			}
5281
5282			return elm[0] && elm[0].style ? elm[0].style[name] : undefined;
5283		},
5284
5285		/**
5286		 * Sets multiple styles on the specified element(s).
5287		 *
5288		 * @method setStyles
5289		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
5290		 * @param {Object} o Name/Value collection of style items to add to the element(s).
5291		 * @example
5292		 * // Sets styles on all paragraphs in the currently active editor
5293		 * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
5294		 *
5295		 * // Sets styles to an element by id in the current document
5296		 * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
5297		 */
5298		setStyles: function(elm, styles) {
5299			elm = this.$$(elm).css(styles);
5300
5301			if (this.settings.update_styles) {
5302				elm.attr('data-mce-style', null);
5303			}
5304		},
5305
5306		/**
5307		 * Removes all attributes from an element or elements.
5308		 *
5309		 * @method removeAllAttribs
5310		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
5311		 */
5312		removeAllAttribs: function(e) {
5313			return this.run(e, function(e) {
5314				var i, attrs = e.attributes;
5315				for (i = attrs.length - 1; i >= 0; i--) {
5316					e.removeAttributeNode(attrs.item(i));
5317				}
5318			});
5319		},
5320
5321		/**
5322		 * Sets the specified attribute of an element or elements.
5323		 *
5324		 * @method setAttrib
5325		 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
5326		 * @param {String} n Name of attribute to set.
5327		 * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
5328		 * @example
5329		 * // Sets class attribute on all paragraphs in the active editor
5330		 * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
5331		 *
5332		 * // Sets class attribute on a specific element in the current page
5333		 * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
5334		 */
5335		setAttrib: function(elm, name, value) {
5336			var self = this, originalValue, hook, settings = self.settings;
5337
5338			if (value === '') {
5339				value = null;
5340			}
5341
5342			elm = self.$$(elm);
5343			originalValue = elm.attr(name);
5344
5345			if (!elm.length) {
5346				return;
5347			}
5348
5349			hook = self.attrHooks[name];
5350			if (hook && hook.set) {
5351				hook.set(elm, value, name);
5352			} else {
5353				elm.attr(name, value);
5354			}
5355
5356			if (originalValue != value && settings.onSetAttrib) {
5357				settings.onSetAttrib({
5358					attrElm: elm,
5359					attrName: name,
5360					attrValue: value
5361				});
5362			}
5363		},
5364
5365		/**
5366		 * Sets two or more specified attributes of an element or elements.
5367		 *
5368		 * @method setAttribs
5369		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
5370		 * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
5371		 * @example
5372		 * // Sets class and title attributes on all paragraphs in the active editor
5373		 * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
5374		 *
5375		 * // Sets class and title attributes on a specific element in the current page
5376		 * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
5377		 */
5378		setAttribs: function(elm, attrs) {
5379			var self = this;
5380
5381			self.$$(elm).each(function(i, node) {
5382				each(attrs, function(value, name) {
5383					self.setAttrib(node, name, value);
5384				});
5385			});
5386		},
5387
5388		/**
5389		 * Returns the specified attribute by name.
5390		 *
5391		 * @method getAttrib
5392		 * @param {String/Element} elm Element string id or DOM element to get attribute from.
5393		 * @param {String} name Name of attribute to get.
5394		 * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
5395		 * @return {String} Attribute value string, default value or null if the attribute wasn't found.
5396		 */
5397		getAttrib: function(elm, name, defaultVal) {
5398			var self = this, hook, value;
5399
5400			elm = self.$$(elm);
5401
5402			if (elm.length) {
5403				hook = self.attrHooks[name];
5404
5405				if (hook && hook.get) {
5406					value = hook.get(elm, name);
5407				} else {
5408					value = elm.attr(name);
5409				}
5410			}
5411
5412			if (typeof value == 'undefined') {
5413				value = defaultVal || '';
5414			}
5415
5416			return value;
5417		},
5418
5419		/**
5420		 * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
5421		 *
5422		 * @method getPos
5423		 * @param {Element/String} elm HTML element or element id to get x, y position from.
5424		 * @param {Element} rootElm Optional root element to stop calculations at.
5425		 * @return {object} Absolute position of the specified element object with x, y fields.
5426		 */
5427		getPos: function(elm, rootElm) {
5428			var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos;
5429
5430			elm = self.get(elm);
5431			rootElm = rootElm || body;
5432
5433			if (elm) {
5434				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5435				// Fallback to offsetParent calculations if the body isn't static better since it stops at the body root
5436				if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') {
5437					pos = elm.getBoundingClientRect();
5438					rootElm = self.boxModel ? doc.documentElement : body;
5439
5440					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5441					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5442					x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft;
5443					y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop;
5444
5445					return {x: x, y: y};
5446				}
5447
5448				offsetParent = elm;
5449				while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5450					x += offsetParent.offsetLeft || 0;
5451					y += offsetParent.offsetTop || 0;
5452					offsetParent = offsetParent.offsetParent;
5453				}
5454
5455				offsetParent = elm.parentNode;
5456				while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
5457					x -= offsetParent.scrollLeft || 0;
5458					y -= offsetParent.scrollTop || 0;
5459					offsetParent = offsetParent.parentNode;
5460				}
5461			}
5462
5463			return {x: x, y: y};
5464		},
5465
5466		/**
5467		 * Parses the specified style value into an object collection. This parser will also
5468		 * merge and remove any redundant items that browsers might have added. It will also convert non-hex
5469		 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
5470		 *
5471		 * @method parseStyle
5472		 * @param {String} cssText Style value to parse, for example: border:1px solid red;.
5473		 * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
5474		 */
5475		parseStyle: function(cssText) {
5476			return this.styles.parse(cssText);
5477		},
5478
5479		/**
5480		 * Serializes the specified style object into a string.
5481		 *
5482		 * @method serializeStyle
5483		 * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
5484		 * @param {String} name Optional element name.
5485		 * @return {String} String representation of the style object, for example: border: 1px solid red.
5486		 */
5487		serializeStyle: function(styles, name) {
5488			return this.styles.serialize(styles, name);
5489		},
5490
5491		/**
5492		 * Adds a style element at the top of the document with the specified cssText content.
5493		 *
5494		 * @method addStyle
5495		 * @param {String} cssText CSS Text style to add to top of head of document.
5496		 */
5497		addStyle: function(cssText) {
5498			var self = this, doc = self.doc, head, styleElm;
5499
5500			// Prevent inline from loading the same styles twice
5501			if (self !== DOMUtils.DOM && doc === document) {
5502				var addedStyles = DOMUtils.DOM.addedStyles;
5503
5504				addedStyles = addedStyles || [];
5505				if (addedStyles[cssText]) {
5506					return;
5507				}
5508
5509				addedStyles[cssText] = true;
5510				DOMUtils.DOM.addedStyles = addedStyles;
5511			}
5512
5513			// Create style element if needed
5514			styleElm = doc.getElementById('mceDefaultStyles');
5515			if (!styleElm) {
5516				styleElm = doc.createElement('style');
5517				styleElm.id = 'mceDefaultStyles';
5518				styleElm.type = 'text/css';
5519
5520				head = doc.getElementsByTagName('head')[0];
5521				if (head.firstChild) {
5522					head.insertBefore(styleElm, head.firstChild);
5523				} else {
5524					head.appendChild(styleElm);
5525				}
5526			}
5527
5528			// Append style data to old or new style element
5529			if (styleElm.styleSheet) {
5530				styleElm.styleSheet.cssText += cssText;
5531			} else {
5532				styleElm.appendChild(doc.createTextNode(cssText));
5533			}
5534		},
5535
5536		/**
5537		 * Imports/loads the specified CSS file into the document bound to the class.
5538		 *
5539		 * @method loadCSS
5540		 * @param {String} u URL to CSS file to load.
5541		 * @example
5542		 * // Loads a CSS file dynamically into the current document
5543		 * tinymce.DOM.loadCSS('somepath/some.css');
5544		 *
5545		 * // Loads a CSS file into the currently active editor instance
5546		 * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
5547		 *
5548		 * // Loads a CSS file into an editor instance by id
5549		 * tinymce.get('someid').dom.loadCSS('somepath/some.css');
5550		 *
5551		 * // Loads multiple CSS files into the current document
5552		 * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
5553		 */
5554		loadCSS: function(url) {
5555			var self = this, doc = self.doc, head;
5556
5557			// Prevent inline from loading the same CSS file twice
5558			if (self !== DOMUtils.DOM && doc === document) {
5559				DOMUtils.DOM.loadCSS(url);
5560				return;
5561			}
5562
5563			if (!url) {
5564				url = '';
5565			}
5566
5567			head = doc.getElementsByTagName('head')[0];
5568
5569			each(url.split(','), function(url) {
5570				var link;
5571
5572				url = Tools._addCacheSuffix(url);
5573
5574				if (self.files[url]) {
5575					return;
5576				}
5577
5578				self.files[url] = true;
5579				link = self.create('link', {rel: 'stylesheet', href: url});
5580
5581				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5582				// This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
5583				// It's ugly but it seems to work fine.
5584				if (isIE && doc.documentMode && doc.recalc) {
5585					link.onload = function() {
5586						if (doc.recalc) {
5587							doc.recalc();
5588						}
5589
5590						link.onload = null;
5591					};
5592				}
5593
5594				head.appendChild(link);
5595			});
5596		},
5597
5598		/**
5599		 * Adds a class to the specified element or elements.
5600		 *
5601		 * @method addClass
5602		 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
5603		 * @param {String} cls Class name to add to each element.
5604		 * @return {String/Array}
5604 String with new class value or array with new class values for all elements.
5605		 * @example
5606		 * // Adds a class to all paragraphs in the active editor
5607		 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
5608		 *
5609		 * // Adds a class to a specific element in the current page
5610		 * tinymce.DOM.addClass('mydiv', 'myclass');
5611		 */
5612		addClass: function(elm, cls) {
5613			this.$$(elm).addClass(cls);
5614		},
5615
5616		/**
5617		 * Removes a class from the specified element or elements.
5618		 *
5619		 * @method removeClass
5620		 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
5621		 * @param {String} cls Class name to remove from each element.
5622		 * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
5623		 * were passed in.
5624		 * @example
5625		 * // Removes a class from all paragraphs in the active editor
5626		 * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
5627		 *
5628		 * // Removes a class from a specific element in the current page
5629		 * tinymce.DOM.removeClass('mydiv', 'myclass');
5630		 */
5631		removeClass: function(elm, cls) {
5632			this.toggleClass(elm, cls, false);
5633		},
5634
5635		/**
5636		 * Returns true if the specified element has the specified class.
5637		 *
5638		 * @method hasClass
5639		 * @param {String/Element} n HTML element or element id string to check CSS class on.
5640		 * @param {String} c CSS class to check for.
5641		 * @return {Boolean} true/false if the specified element has the specified class.
5642		 */
5643		hasClass: function(elm, cls) {
5644			return this.$$(elm).hasClass(cls);
5645		},
5646
5647		/**
5648		 * Toggles the specified class on/off.
5649		 *
5650		 * @method toggleClass
5651		 * @param {Element} elm Element to toggle class on.
5652		 * @param {[type]} cls Class to toggle on/off.
5653		 * @param {[type]} state Optional state to set.
5654		 */
5655		toggleClass: function(elm, cls, state) {
5656			this.$$(elm).toggleClass(cls, state).each(function() {
5657				if (this.className === '') {
5658					$(this).attr('class', null);
5659				}
5660			});
5661		},
5662
5663		/**
5664		 * Shows the specified element(s) by ID by setting the "display" style.
5665		 *
5666		 * @method show
5667		 * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
5668		 */
5669		show: function(elm) {
5670			this.$$(elm).show();
5671		},
5672
5673		/**
5674		 * Hides the specified element(s) by ID by setting the "display" style.
5675		 *
5676		 * @method hide
5677		 * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
5678		 * @example
5679		 * // Hides an element by id in the document
5680		 * tinymce.DOM.hide('myid');
5681		 */
5682		hide: function(elm) {
5683			this.$$(elm).hide();
5684		},
5685
5686		/**
5687		 * Returns true/false if the element is hidden or not by checking the "display" style.
5688		 *
5689		 * @method isHidden
5690		 * @param {String/Element} e Id or element to check display state on.
5691		 * @return {Boolean} true/false if the element is hidden or not.
5692		 */
5693		isHidden: function(elm) {
5694			return this.$$(elm).css('display') == 'none';
5695		},
5696
5697		/**
5698		 * Returns a unique id. This can be useful when generating elements on the fly.
5699		 * This method will not check if the element already exists.
5700		 *
5701		 * @method uniqueId
5702		 * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
5703		 * @return {String} Unique id.
5704		 */
5705		uniqueId: function(prefix) {
5706			return (!prefix ? 'mce_' : prefix) + (this.counter++);
5707		},
5708
5709		/**
5710		 * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
5711		 * URLs will get converted, hex color values fixed etc. Check processHTML for details.
5712		 *
5713		 * @method setHTML
5714		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
5715		 * @param {String} h HTML content to set as inner HTML of the element.
5716		 * @example
5717		 * // Sets the inner HTML of all paragraphs in the active editor
5718		 * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
5719		 *
5720		 * // Sets the inner HTML of an element by id in the document
5721		 * tinymce.DOM.setHTML('mydiv', 'some inner html');
5722		 */
5723		setHTML: function(elm, html) {
5724			elm = this.$$(elm);
5725
5726			if (isIE) {
5727				elm.each(function(i, target) {
5728					if (target.canHaveHTML === false) {
5729						return;
5730					}
5731
5732					// Remove all child nodes, IE keeps empty text nodes in DOM
5733					while (target.firstChild) {
5734						target.removeChild(target.firstChild);
5735					}
5736
5737					try {
5738						// IE will remove comments from the beginning
5739						// unless you padd the contents with something
5740						target.innerHTML = '<br>' + html;
5741						target.removeChild(target.firstChild);
5742					} catch (ex) {
5743						// IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
5744						$('<div>').html('<br>' + html).contents().slice(1).appendTo(target);
5745					}
5746
5747					return html;
5748				});
5749			} else {
5750				elm.html(html);
5751			}
5752		},
5753
5754		/**
5755		 * Returns the outer HTML of an element.
5756		 *
5757		 * @method getOuterHTML
5758		 * @param {String/Element} elm Element ID or element object to get outer HTML from.
5759		 * @return {String} Outer HTML string.
5760		 * @example
5761		 * tinymce.DOM.getOuterHTML(editorElement);
5762		 * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
5763		 */
5764		getOuterHTML: function(elm) {
5765			elm = this.get(elm);
5766			return elm.nodeType == 1 ? elm.outerHTML : $('<div>').append($(elm).clone()).html();
5767		},
5768
5769		/**
5770		 * Sets the specified outer HTML on an element or elements.
5771		 *
5772		 * @method setOuterHTML
5773		 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
5774		 * @param {Object} html HTML code to set as outer value for the element.
5775		 * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
5776		 * @example
5777		 * // Sets the outer HTML of all paragraphs in the active editor
5778		 * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
5779		 *
5780		 * // Sets the outer HTML of an element by id in the document
5781		 * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
5782		 */
5783		setOuterHTML: function(elm, html) {
5784			var self = this;
5785
5786			self.$$(elm).each(function() {
5787				try {
5788					this.outerHTML = html;
5789				} catch (ex) {
5790					// OuterHTML for IE it sometimes produces an "unknown runtime error"
5791					self.remove($(this).html(html), true);
5792				}
5793			});
5794		},
5795
5796		/**
5797		 * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
5798		 *
5799		 * @method decode
5800		 * @param {String} s String to decode entities on.
5801		 * @return {String} Entity decoded string.
5802		 */
5803		decode: Entities.decode,
5804
5805		/**
5806		 * Entity encodes a string. This method encodes the most common entities, such as <>"&.
5807		 *
5808		 * @method encode
5809		 * @param {String} text String to encode with entities.
5810		 * @return {String} Entity encoded string.
5811		 */
5812		encode: Entities.encodeAllRaw,
5813
5814		/**
5815		 * Inserts an element after the reference element.
5816		 *
5817		 * @method insertAfter
5818		 * @param {Element} node Element to insert after the reference.
5819		 * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
5820		 * @return {Element/Array} Element that got added or an array with elements.
5821		 */
5822		insertAfter: function(node, referenceNode) {
5823			referenceNode = this.get(referenceNode);
5824
5825			return this.run(node, function(node) {
5826				var parent, nextSibling;
5827
5828				parent = referenceNode.parentNode;
5829				nextSibling = referenceNode.nextSibling;
5830
5831				if (nextSibling) {
5832					parent.insertBefore(node, nextSibling);
5833				} else {
5834					parent.appendChild(node);
5835				}
5836
5837				return node;
5838			});
5839		},
5840
5841		/**
5842		 * Replaces the specified element or elements with the new element specified. The new element will
5843		 * be cloned if multiple input elements are passed in.
5844		 *
5845		 * @method replace
5846		 * @param {Element} newElm New element to replace old ones with.
5847		 * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
5848		 * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
5849		 */
5850		replace: function(newElm, oldElm, keepChildren) {
5851			var self = this;
5852
5853			return self.run(oldElm, function(oldElm) {
5854				if (is(oldElm, 'array')) {
5855					newElm = newElm.cloneNode(true);
5856				}
5857
5858				if (keepChildren) {
5859					each(grep(oldElm.childNodes), function(node) {
5860						newElm.appendChild(node);
5861					});
5862				}
5863
5864				return oldElm.parentNode.replaceChild(newElm, oldElm);
5865			});
5866		},
5867
5868		/**
5869		 * Renames the specified element and keeps its attributes and children.
5870		 *
5871		 * @method rename
5872		 * @param {Element} elm Element to rename.
5873		 * @param {String} name Name of the new element.
5874		 * @return {Element} New element or the old element if it needed renaming.
5875		 */
5876		rename: function(elm, name) {
5877			var self = this, newElm;
5878
5879			if (elm.nodeName != name.toUpperCase()) {
5880				// Rename block element
5881				newElm = self.create(name);
5882
5883				// Copy attribs to new block
5884				each(self.getAttribs(elm), function(attrNode) {
5885					self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName));
5886				});
5887
5888				// Replace block
5889				self.replace(newElm, elm, 1);
5890			}
5891
5892			return newElm || elm;
5893		},
5894
5895		/**
5896		 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
5897		 *
5898		 * @method findCommonAncestor
5899		 * @param {Element} a Element to find common ancestor of.
5900		 * @param {Element} b Element to find common ancestor of.
5901		 * @return {Element} Common ancestor element of the two input elements.
5902		 */
5903		findCommonAncestor: function(a, b) {
5904			var ps = a, pe;
5905
5906			while (ps) {
5907				pe = b;
5908
5909				while (pe && ps != pe) {
5910					pe = pe.parentNode;
5911				}
5912
5913				if (ps == pe) {
5914					break;
5915				}
5916
5917				ps = ps.parentNode;
5918			}
5919
5920			if (!ps && a.ownerDocument) {
5921				return a.ownerDocument.documentElement;
5922			}
5923
5924			return ps;
5925		},
5926
5927		/**
5928		 * Parses the specified RGB color value and returns a hex version of that color.
5929		 *
5930		 * @method toHex
5931		 * @param {String} rgbVal RGB string value like rgb(1,2,3)
5932		 * @return {String} Hex version of that RGB value like #FF00FF.
5933		 */
5934		toHex: function(rgbVal) {
5935			return this.styles.toHex(Tools.trim(rgbVal));
5936		},
5937
5938		/**
5939		 * Executes the specified function on the element by id or dom element node or array of elements/id.
5940		 *
5941		 * @method run
5942		 * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
5943		 * @param {function} f Function to execute for each item.
5944		 * @param {Object} s Optional scope to execute the function in.
5945		 * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
5946		 */
5947		run: function(elm, func, scope) {
5948			var self = this, result;
5949
5950			if (typeof(elm) === 'string') {
5951				elm = self.get(elm);
5952			}
5953
5954			if (!elm) {
5955				return false;
5956			}
5957
5958			scope = scope || this;
5959			if (!elm.nodeType && (elm.length || elm.length === 0)) {
5960				result = [];
5961
5962				each(elm, function(elm, i) {
5963					if (elm) {
5964						if (typeof(elm) == 'string') {
5965							elm = self.get(elm);
5966						}
5967
5968						result.push(func.call(scope, elm, i));
5969					}
5970				});
5971
5972				return result;
5973			}
5974
5975			return func.call(scope, elm);
5976		},
5977
5978		/**
5979		 * Returns a NodeList with attributes for the element.
5980		 *
5981		 * @method getAttribs
5982		 * @param {HTMLElement/string} elm Element node or string id to get attributes from.
5983		 * @return {NodeList} NodeList with attributes.
5984		 */
5985		getAttribs: function(elm) {
5986			var attrs;
5987
5988			elm = this.get(elm);
5989
5990			if (!elm) {
5991				return [];
5992			}
5993
5994			if (isIE) {
5995				attrs = [];
5996
5997				// Object will throw exception in IE
5998				if (elm.nodeName == 'OBJECT') {
5999					return elm.attributes;
6000				}
6001
6002				// IE doesn't keep the selected attribute if you clone option elements
6003				if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
6004					attrs.push({specified: 1, nodeName: 'selected'});
6005				}
6006
6007				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6008				var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
6009				elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
6010					attrs.push({specified: 1, nodeName: a});
6011				});
6012
6013				return attrs;
6014			}
6015
6016			return elm.attributes;
6017		},
6018
6019		/**
6020		 * Returns true/false if the specified node is to be considered empty or not.
6021		 *
6022		 * @example
6023		 * tinymce.DOM.isEmpty(node, {img: true});
6024		 * @method isEmpty
6025		 * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
6026		 * @return {Boolean} true/false if the node is empty or not.
6027		 */
6028		isEmpty: function(node, elements) {
6029			var self = this, i, attributes, type, walker, name, brCount = 0;
6030
6031			node = node.firstChild;
6032			if (node) {
6033				walker = new TreeWalker(node, node.parentNode);
6034				elements = elements || (self.schema ? self.schema.getNonEmptyElements() : null);
6035
6036				do {
6037					type = node.nodeType;
6038
6039					if (type === 1) {
6040						// Ignore bogus elements
6041						if (node.getAttribute('data-mce-bogus')) {
6042							continue;
6043						}
6044
6045						// Keep empty elements like <img />
6046						name = node.nodeName.toLowerCase();
6047						if (elements && elements[name]) {
6048							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6049							if (name === 'br') {
6050								brCount++;
6051								continue;
6052							}
6053
6054							return false;
6055						}
6056
6057						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6058						attributes = self.getAttribs(node);
6059						i = attributes.length;
6060						while (i--) {
6061							name = attributes[i].nodeName;
6062							if (name === "name" || name === 'data-mce-bookmark') {
6063								return false;
6064							}
6065						}
6066					}
6067
6068					// Keep comment nodes
6069					if (type == 8) {
6070						return false;
6071					}
6072
6073					// Keep non whitespace text nodes
6074					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
6075						return false;
6076					}
6077				} while ((node = walker.next()));
6078			}
6079
6080			return brCount <= 1;
6081		},
6082
6083		/**
6084		 * Creates a new DOM Range object. This will use the native DOM Range API if it's
6085		 * available. If it's not, it will fall back to the custom TinyMCE implementation.
6086		 *
6087		 * @method createRng
6088		 * @return {DOMRange} DOM Range object.
6089		 * @example
6090		 * var rng = tinymce.DOM.createRng();
6091		 * alert(rng.startContainer + "," + rng.startOffset);
6092		 */
6093		createRng: function() {
6094			var doc = this.doc;
6095
6096			return doc.createRange ? doc.createRange() : new Range(this);
6097		},
6098
6099		/**
6100		 * Returns the index of the specified node within its parent.
6101		 *
6102		 * @method nodeIndex
6103		 * @param {Node} node Node to look for.
6104		 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
6105		 * @return {Number} Index of the specified node.
6106		 */
6107		nodeIndex: function(node, normalized) {
6108			var idx = 0, lastNodeType, nodeType;
6109
6110			if (node) {
6111				for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
6112					nodeType = node.nodeType;
6113
6114					// Normalize text nodes
6115					if (normalized && nodeType == 3) {
6116						if (nodeType == lastNodeType || !node.nodeValue.length) {
6117							continue;
6118						}
6119					}
6120					idx++;
6121					lastNodeType = nodeType;
6122				}
6123			}
6124
6125			return idx;
6126		},
6127
6128		/**
6129		 * Splits an element into two new elements and places the specified split
6130		 * element or elements between the new ones. For example splitting the paragraph at the bold element in
6131		 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
6132		 *
6133		 * @method split
6134		 * @param {Element} parentElm Parent element to split.
6135		 * @param {Element} splitElm Element to split at.
6136		 * @param {Element} replacementElm Optional replacement element to replace the split element with.
6137		 * @return {Element} Returns the split element or the replacement element if that is specified.
6138		 */
6139		split: function(parentElm, splitElm, replacementElm) {
6140			var self = this, r = self.createRng(), bef, aft, pa;
6141
6142			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
6143			// but we don't want that in our code since it serves no purpose for the end user
6144			// For example splitting this html at the bold element:
6145			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6146			// would produce:
6147			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6148			// this function will then trim off empty edges and produce:
6149			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6150			function trimNode(node) {
6151				var i, children = node.childNodes, type = node.nodeType;
6152
6153				function surroundedBySpans(node) {
6154					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6155					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6156					return previousIsSpan && nextIsSpan;
6157				}
6158
6159				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
6160					return;
6161				}
6162
6163				for (i = children.length - 1; i >= 0; i--) {
6164					trimNode(children[i]);
6165				}
6166
6167				if (type != 9) {
6168					// Keep non whitespace text nodes
6169					if (type == 3 && node.nodeValue.length > 0) {
6170						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6171						// Also keep text nodes with only spaces if surrounded by spans.
6172						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6173						var trimmedLength = trim(node.nodeValue).length;
6174						if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
6175							return;
6176						}
6177					} else if (type == 1) {
6178						// If the only child is a bookmark then move it up
6179						children = node.childNodes;
6180
6181						// TODO fix this complex if
6182						if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
6183							children[0].getAttribute('data-mce-type') == 'bookmark') {
6184							node.parentNode.insertBefore(children[0], node);
6185						}
6186
6187						// Keep non empty elements or img, hr etc
6188						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
6189							return;
6190						}
6191					}
6192
6193					self.remove(node);
6194				}
6195
6196				return node;
6197			}
6198
6199			if (parentElm && splitElm) {
6200				// Get before chunk
6201				r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
6202				r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
6203				bef = r.extractContents();
6204
6205				// Get after chunk
6206				r = self.createRng();
6207				r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
6208				r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
6209				aft = r.extractContents();
6210
6211				// Insert before chunk
6212				pa = parentElm.parentNode;
6213				pa.insertBefore(trimNode(bef), parentElm);
6214
6215				// Insert middle chunk
6216				if (replacementElm) {
6217					pa.replaceChild(replacementElm, splitElm);
6218				} else {
6219					pa.insertBefore(splitElm, parentElm);
6220				}
6221
6222				// Insert after chunk
6223				pa.insertBefore(trimNode(aft), parentElm);
6224				self.remove(parentElm);
6225
6226				return replacementElm || splitElm;
6227			}
6228		},
6229
6230		/**
6231		 * Adds an event handler to the specified object.
6232		 *
6233		 * @method bind
6234		 * @param {Element/Document/Window/Array} target Target element to bind events to.
6235		 * handler to or an array of elements/ids/documents.
6236		 * @param {String} name Name of event handler to add, for example: click.
6237		 * @param {function} func Function to execute when the event occurs.
6238		 * @param {Object} scope Optional scope to execute the function in.
6239		 * @return {function} Function callback handler the same as the one passed in.
6240		 */
6241		bind: function(target, name, func, scope) {
6242			var self = this;
6243
6244			if (Tools.isArray(target)) {
6245				var i = target.length;
6246
6247				while (i--) {
6248					target[i] = self.bind(target[i], name, func, scope);
6249				}
6250
6251				return target;
6252			}
6253
6254			// Collect all window/document events bound by editor instance
6255			if (self.settings.collect && (target === self.doc || target === self.win)) {
6256				self.boundEvents.push([target, name, func, scope]);
6257			}
6258
6259			return self.events.bind(target, name, func, scope || self);
6260		},
6261
6262		/**
6263		 * Removes the specified event handler by name and function from an element or collection of elements.
6264		 *
6265		 * @method unbind
6266		 * @param {Element/Document/Window/Array} target Target element to unbind events on.
6267		 * @param {String} name Event handler name, for example: "click"
6268		 * @param {function}
6268 func Function to remove.
6269		 * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
6270		 * were passed in.
6271		 */
6272		unbind: function(target, name, func) {
6273			var self = this, i;
6274
6275			if (Tools.isArray(target)) {
6276				i = target.length;
6277
6278				while (i--) {
6279					target[i] = self.unbind(target[i], name, func);
6280				}
6281
6282				return target;
6283			}
6284
6285			// Remove any bound events matching the input
6286			if (self.boundEvents && (target === self.doc || target === self.win)) {
6287				i = self.boundEvents.length;
6288
6289				while (i--) {
6290					var item = self.boundEvents[i];
6291
6292					if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
6293						this.events.unbind(item[0], item[1], item[2]);
6294					}
6295				}
6296			}
6297
6298			return this.events.unbind(target, name, func);
6299		},
6300
6301		/**
6302		 * Fires the specified event name with object on target.
6303		 *
6304		 * @method fire
6305		 * @param {Node/Document/Window} target Target element or object to fire event on.
6306		 * @param {String} name Name of the event to fire.
6307		 * @param {Object} evt Event object to send.
6308		 * @return {Event} Event object.
6309		 */
6310		fire: function(target, name, evt) {
6311			return this.events.fire(target, name, evt);
6312		},
6313
6314		// Returns the content editable state of a node
6315		getContentEditable: function(node) {
6316			var contentEditable;
6317
6318			// Check type
6319			if (!node || node.nodeType != 1) {
6320				return null;
6321			}
6322
6323			// Check for fake content editable
6324			contentEditable = node.getAttribute("data-mce-contenteditable");
6325			if (contentEditable && contentEditable !== "inherit") {
6326				return contentEditable;
6327			}
6328
6329			// Check for real content editable
6330			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6331		},
6332
6333		getContentEditableParent: function(node) {
6334			var root = this.getRoot(), state = null;
6335
6336			for (; node && node !== root; node = node.parentNode) {
6337				state = this.getContentEditable(node);
6338
6339				if (state !== null) {
6340					break;
6341				}
6342			}
6343
6344			return state;
6345		},
6346
6347		/**
6348		 * Destroys all internal references to the DOM to solve IE leak issues.
6349		 *
6350		 * @method destroy
6351		 */
6352		destroy: function() {
6353			var self = this;
6354
6355			// Unbind all events bound to window/document by editor instance
6356			if (self.boundEvents) {
6357				var i = self.boundEvents.length;
6358
6359				while (i--) {
6360					var item = self.boundEvents[i];
6361					this.events.unbind(item[0], item[1], item[2]);
6362				}
6363
6364				self.boundEvents = null;
6365			}
6366
6367			// Restore sizzle document to window.document
6368			// Since the current document might be removed producing "Permission denied" on IE see #6325
6369			if (Sizzle.setDocument) {
6370				Sizzle.setDocument();
6371			}
6372
6373			self.win = self.doc = self.root = self.events = self.frag = null;
6374		},
6375
6376		isChildOf: function(node, parent) {
6377			while (node) {
6378				if (parent === node) {
6379					return true;
6380				}
6381
6382				node = node.parentNode;
6383			}
6384
6385			return false;
6386		},
6387
6388		// #ifdef debug
6389
6390		dumpRng: function(r) {
6391			return (
6392				'startContainer: ' + r.startContainer.nodeName +
6393				', startOffset: ' + r.startOffset +
6394				', endContainer: ' + r.endContainer.nodeName +
6395				', endOffset: ' + r.endOffset
6396			);
6397		},
6398
6399		// #endif
6400
6401		_findSib: function(node, selector, name) {
6402			var self = this, func = selector;
6403
6404			if (node) {
6405				// If expression make a function of it using is
6406				if (typeof(func) == 'string') {
6407					func = function(node) {
6408						return self.is(node, selector);
6409					};
6410				}
6411
6412				// Loop all siblings
6413				for (node = node[name]; node; node = node[name]) {
6414					if (func(node)) {
6415						return node;
6416					}
6417				}
6418			}
6419
6420			return null;
6421		}
6422	};
6423
6424	/**
6425	 * Instance of DOMUtils for the current document.
6426	 *
6427	 * @static
6428	 * @property DOM
6429	 * @type tinymce.dom.DOMUtils
6430	 * @example
6431	 * // Example of how to add a class to some element by id
6432	 * tinymce.DOM.addClass('someid', 'someclass');
6433	 */
6434	DOMUtils.DOM = new DOMUtils(document);
6435
6436	return DOMUtils;
6437});
6438
6439// Included from: js/tinymce/classes/dom/ScriptLoader.js
6440
6441/**
6442 * ScriptLoader.js
6443 *
6444 * Copyright, Moxiecode Systems AB
6445 * Released under LGPL License.
6446 *
6447 * License: http://www.tinymce.com/license
6448 * Contributing: http://www.tinymce.com/contributing
6449 */
6450
6451/*globals console*/
6452
6453/**
6454 * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
6455 * when various items gets loaded. This class is useful to load external JavaScript files.
6456 *
6457 * @class tinymce.dom.ScriptLoader
6458 * @example
6459 * // Load a script from a specific URL using the global script loader
6460 * tinymce.ScriptLoader.load('somescript.js');
6461 *
6462 * // Load a script using a unique instance of the script loader
6463 * var scriptLoader = new tinymce.dom.ScriptLoader();
6464 *
6465 * scriptLoader.load('somescript.js');
6466 *
6467 * // Load multiple scripts
6468 * var scriptLoader = new tinymce.dom.ScriptLoader();
6469 *
6470 * scriptLoader.add('somescript1.js');
6471 * scriptLoader.add('somescript2.js');
6472 * scriptLoader.add('somescript3.js');
6473 *
6474 * scriptLoader.loadQueue(function() {
6475 *    alert('All scripts are now loaded.');
6476 * });
6477 */
6478define("tinymce/dom/ScriptLoader", [
6479	"tinymce/dom/DOMUtils",
6480	"tinymce/util/Tools"
6481], function(DOMUtils, Tools) {
6482	var DOM = DOMUtils.DOM;
6483	var each = Tools.each, grep = Tools.grep;
6484
6485	function ScriptLoader() {
6486		var QUEUED = 0,
6487			LOADING = 1,
6488			LOADED = 2,
6489			states = {},
6490			queue = [],
6491			scriptLoadedCallbacks = {},
6492			queueLoadedCallbacks = [],
6493			loading = 0,
6494			undef;
6495
6496		/**
6497		 * Loads a specific script directly without adding it to the load queue.
6498		 *
6499		 * @method load
6500		 * @param {String} url Absolute URL to script to add.
6501		 * @param {function} callback Optional callback function to execute ones this script gets loaded.
6502		 * @param {Object} scope Optional scope to execute callback in.
6503		 */
6504		function loadScript(url, callback) {
6505			var dom = DOM, elm, id;
6506
6507			// Execute callback when script is loaded
6508			function done() {
6509				dom.remove(id);
6510
6511				if (elm) {
6512					elm.onreadystatechange = elm.onload = elm = null;
6513				}
6514
6515				callback();
6516			}
6517
6518			function error() {
6519				/*eslint no-console:0 */
6520
6521				// Report the error so it's easier for people to spot loading errors
6522				if (typeof(console) !== "undefined" && console.log) {
6523					console.log("Failed to load: " + url);
6524				}
6525
6526				// We can't mark it as done if there is a load error since
6527				// A) We don't want to produce 404 errors on the server and
6528				// B) the onerror event won't fire on all browsers.
6529				// done();
6530			}
6531
6532			id = dom.uniqueId();
6533
6534			// Create new script element
6535			elm = document.createElement('script');
6536			elm.id = id;
6537			elm.type = 'text/javascript';
6538			elm.src = Tools._addCacheSuffix(url);
6539
6540			// Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
6541			if ("onreadystatechange" in elm) {
6542				elm.onreadystatechange = function() {
6543					if (/loaded|complete/.test(elm.readyState)) {
6544						done();
6545					}
6546				};
6547			} else {
6548				elm.onload = done;
6549			}
6550
6551			// Add onerror event will get fired on some browsers but not all of them
6552			elm.onerror = error;
6553
6554			// Add script to document
6555			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
6556		}
6557
6558		/**
6559		 * Returns true/false if a script has been loaded or not.
6560		 *
6561		 * @method isDone
6562		 * @param {String} url URL to check for.
6563		 * @return {Boolean} true/false if the URL is loaded.
6564		 */
6565		this.isDone = function(url) {
6566			return states[url] == LOADED;
6567		};
6568
6569		/**
6570		 * Marks a specific script to be loaded. This can be useful if a script got loaded outside
6571		 * the script loader or to skip it from loading some script.
6572		 *
6573		 * @method markDone
6574		 * @param {string} u Absolute URL to the script to mark as loaded.
6575		 */
6576		this.markDone = function(url) {
6577			states[url] = LOADED;
6578		};
6579
6580		/**
6581		 * Adds a specific script to the load queue of the script loader.
6582		 *
6583		 * @method add
6584		 * @param {String} url Absolute URL to script to add.
6585		 * @param {function} callback Optional callback function to execute ones this script gets loaded.
6586		 * @param {Object} scope Optional scope to execute callback in.
6587		 */
6588		this.add = this.load = function(url, callback, scope) {
6589			var state = states[url];
6590
6591			// Add url to load queue
6592			if (state == undef) {
6593				queue.push(url);
6594				states[url] = QUEUED;
6595			}
6596
6597			if (callback) {
6598				// Store away callback for later execution
6599				if (!scriptLoadedCallbacks[url]) {
6600					scriptLoadedCallbacks[url] = [];
6601				}
6602
6603				scriptLoadedCallbacks[url].push({
6604					func: callback,
6605					scope: scope || this
6606				});
6607			}
6608		};
6609
6610		/**
6611		 * Starts the loading of the queue.
6612		 *
6613		 * @method loadQueue
6614		 * @param {function} callback Optional callback to execute when all queued items are loaded.
6615		 * @param {Object} scope Optional scope to execute the callback in.
6616		 */
6617		this.loadQueue = function(callback, scope) {
6618			this.loadScripts(queue, callback, scope);
6619		};
6620
6621		/**
6622		 * Loads the specified queue of files and executes the callback ones they are loaded.
6623		 * This method is generally not used outside this class but it might be useful in some scenarios.
6624		 *
6625		 * @method loadScripts
6626		 * @param {Array} scripts Array of queue items to load.
6627		 * @param {function} callback Optional callback to execute ones all items are loaded.
6628		 * @param {Object} scope Optional scope to execute callback in.
6629		 */
6630		this.loadScripts = function(scripts, callback, scope) {
6631			var loadScripts;
6632
6633			function execScriptLoadedCallbacks(url) {
6634				// Execute URL callback functions
6635				each(scriptLoadedCallbacks[url], function(callback) {
6636					callback.func.call(callback.scope);
6637				});
6638
6639				scriptLoadedCallbacks[url] = undef;
6640			}
6641
6642			queueLoadedCallbacks.push({
6643				func: callback,
6644				scope: scope || this
6645			});
6646
6647			loadScripts = function() {
6648				var loadingScripts = grep(scripts);
6649
6650				// Current scripts has been handled
6651				scripts.length = 0;
6652
6653				// Load scripts that needs to be loaded
6654				each(loadingScripts, function(url) {
6655					// Script is already loaded then execute script callbacks directly
6656					if (states[url] == LOADED) {
6657						execScriptLoadedCallbacks(url);
6658						return;
6659					}
6660
6661					// Is script not loading then start loading it
6662					if (states[url] != LOADING) {
6663						states[url] = LOADING;
6664						loading++;
6665
6666						loadScript(url, function() {
6667							states[url] = LOADED;
6668							loading--;
6669
6670							execScriptLoadedCallbacks(url);
6671
6672							// Load more scripts if they where added by the recently loaded script
6673							loadScripts();
6674						});
6675					}
6676				});
6677
6678				// No scripts are currently loading then execute all pending queue loaded callbacks
6679				if (!loading) {
6680					each(queueLoadedCallbacks, function(callback) {
6681						callback.func.call(callback.scope);
6682					});
6683
6684					queueLoadedCallbacks.length = 0;
6685				}
6686			};
6687
6688			loadScripts();
6689		};
6690	}
6691
6692	ScriptLoader.ScriptLoader = new ScriptLoader();
6693
6694	return ScriptLoader;
6695});
6696
6697// Included from: js/tinymce/classes/AddOnManager.js
6698
6699/**
6700 * AddOnManager.js
6701 *
6702 * Copyright, Moxiecode Systems AB
6703 * Released under LGPL License.
6704 *
6705 * License: http://www.tinymce.com/license
6706 * Contributing: http://www.tinymce.com/contributing
6707 */
6708
6709/**
6710 * This class handles the loading of themes/plugins or other add-ons and their language packs.
6711 *
6712 * @class tinymce.AddOnManager
6713 */
6714define("tinymce/AddOnManager", [
6715	"tinymce/dom/ScriptLoader",
6716	"tinymce/util/Tools"
6717], function(ScriptLoader, Tools) {
6718	var each = Tools.each;
6719
6720	function AddOnManager() {
6721		var self = this;
6722
6723		self.items = [];
6724		self.urls = {};
6725		self.lookup = {};
6726	}
6727
6728	AddOnManager.prototype = {
6729		/**
6730		 * Returns the specified add on by the short name.
6731		 *
6732		 * @method get
6733		 * @param {String} name Add-on to look for.
6734		 * @return {tinymce.Theme/tinymce.Plugin}
6734 Theme or plugin add-on instance or undefined.
6735		 */
6736		get: function(name) {
6737			if (this.lookup[name]) {
6738				return this.lookup[name].instance;
6739			} else {
6740				return undefined;
6741			}
6742		},
6743
6744		dependencies: function(name) {
6745			var result;
6746
6747			if (this.lookup[name]) {
6748				result = this.lookup[name].dependencies;
6749			}
6750
6751			return result || [];
6752		},
6753
6754		/**
6755		 * Loads a language pack for the specified add-on.
6756		 *
6757		 * @method requireLangPack
6758		 * @param {String} name Short name of the add-on.
6759		 * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
6760		 */
6761		requireLangPack: function(name, languages) {
6762			var language = AddOnManager.language;
6763
6764			if (language && AddOnManager.languageLoad !== false) {
6765				if (languages) {
6766					languages = ',' + languages + ',';
6767
6768					// Load short form sv.js or long form sv_SE.js
6769					if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
6770						language = language.substr(0, 2);
6771					} else if (languages.indexOf(',' + language + ',') == -1) {
6772						return;
6773					}
6774				}
6775
6776				ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
6777			}
6778		},
6779
6780		/**
6781		 * Adds a instance of the add-on by it's short name.
6782		 *
6783		 * @method add
6784		 * @param {String} id Short name/id for the add-on.
6785		 * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
6786		 * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
6787		 * @example
6788		 * // Create a simple plugin
6789		 * tinymce.create('tinymce.plugins.TestPlugin', {
6790		 *   TestPlugin: function(ed, url) {
6791		 *   ed.on('click', function(e) {
6792		 *      ed.windowManager.alert('Hello World!');
6793		 *   });
6794		 *   }
6795		 * });
6796		 *
6797		 * // Register plugin using the add method
6798		 * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
6799		 *
6800		 * // Initialize TinyMCE
6801		 * tinymce.init({
6802		 *  ...
6803		 *  plugins: '-test' // Init the plugin but don't try to load it
6804		 * });
6805		 */
6806		add: function(id, addOn, dependencies) {
6807			this.items.push(addOn);
6808			this.lookup[id] = {instance: addOn, dependencies: dependencies};
6809
6810			return addOn;
6811		},
6812
6813		createUrl: function(baseUrl, dep) {
6814			if (typeof dep === "object") {
6815				return dep;
6816			} else {
6817				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
6818			}
6819		},
6820
6821		/**
6822		 * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
6823		 * This should be used in development mode.  A new compressor/javascript munger process will ensure that the
6824		 * components are put together into the plugin.js file and compressed correctly.
6825		 *
6826		 * @method addComponents
6827		 * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
6828		 * @param {Array} scripts Array containing the names of the scripts to load.
6829		 */
6830		addComponents: function(pluginName, scripts) {
6831			var pluginUrl = this.urls[pluginName];
6832
6833			each(scripts, function(script) {
6834				ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
6835			});
6836		},
6837
6838		/**
6839		 * Loads an add-on from a specific url.
6840		 *
6841		 * @method load
6842		 * @param {String} name Short name of the add-on that gets loaded.
6843		 * @param {String} addOnUrl URL to the add-on that will get loaded.
6844		 * @param {function} callback Optional callback to execute ones the add-on is loaded.
6845		 * @param {Object} scope Optional scope to execute the callback in.
6846		 * @example
6847		 * // Loads a plugin from an external URL
6848		 * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
6849		 *
6850		 * // Initialize TinyMCE
6851		 * tinymce.init({
6852		 *  ...
6853		 *  plugins: '-myplugin' // Don't try to load it again
6854		 * });
6855		 */
6856		load: function(name, addOnUrl, callback, scope) {
6857			var self = this, url = addOnUrl;
6858
6859			function loadDependencies() {
6860				var dependencies = self.dependencies(name);
6861
6862				each(dependencies, function(dep) {
6863					var newUrl = self.createUrl(addOnUrl, dep);
6864
6865					self.load(newUrl.resource, newUrl, undefined, undefined);
6866				});
6867
6868				if (callback) {
6869					if (scope) {
6870						callback.call(scope);
6871					} else {
6872						callback.call(ScriptLoader);
6873					}
6874				}
6875			}
6876
6877			if (self.urls[name]) {
6878				return;
6879			}
6880
6881			if (typeof addOnUrl === "object") {
6882				url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
6883			}
6884
6885			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
6886				url = AddOnManager.baseURL + '/' + url;
6887			}
6888
6889			self.urls[name] = url.substring(0, url.lastIndexOf('/'));
6890
6891			if (self.lookup[name]) {
6892				loadDependencies();
6893			} else {
6894				ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
6895			}
6896		}
6897	};
6898
6899	AddOnManager.PluginManager = new AddOnManager();
6900	AddOnManager.ThemeManager = new AddOnManager();
6901
6902	return AddOnManager;
6903});
6904
6905/**
6906 * TinyMCE theme class.
6907 *
6908 * @class tinymce.Theme
6909 */
6910
6911/**
6912 * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
6913 *
6914 * @method renderUI
6915 * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
6916 * @return {Object}
6916 an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
6917 */
6918
6919/**
6920 * Plugin base class, this is a pseudo class that describes how a plugin is to be created for TinyMCE. The methods below are all optional.
6921 *
6922 * @class tinymce.Plugin
6923 * @example
6924 * tinymce.PluginManager.add('example', function(editor, url) {
6925 *     // Add a button that opens a window
6926 *     editor.addButton('example', {
6927 *         text: 'My button',
6928 *         icon: false,
6929 *         onclick: function() {
6930 *             // Open window
6931 *             editor.windowManager.open({
6932 *                 title: 'Example plugin',
6933 *                 body: [
6934 *                     {type: 'textbox', name: 'title', label: 'Title'}
6935 *                 ],
6936 *                 onsubmit: function(e) {
6937 *                     // Insert content when the window form is submitted
6938 *                     editor.insertContent('Title: ' + e.data.title);
6939 *                 }
6940 *             });
6941 *         }
6942 *     });
6943 *
6944 *     // Adds a menu item to the tools menu
6945 *     editor.addMenuItem('example', {
6946 *         text: 'Example plugin',
6947 *         context: 'tools',
6948 *         onclick: function() {
6949 *             // Open window with a specific url
6950 *             editor.windowManager.open({
6951 *                 title: 'TinyMCE site',
6952 *                 url: 'http://www.tinymce.com',
6953 *                 width: 800,
6954 *                 height: 600,
6955 *                 buttons: [{
6956 *                     text: 'Close',
6957 *                     onclick: 'close'
6958 *                 }]
6959 *             });
6960 *         }
6961 *     });
6962 * });
6963 */
6964
6965// Included from: js/tinymce/classes/dom/RangeUtils.js
6966
6967/**
6968 * RangeUtils.js
6969 *
6970 * Copyright, Moxiecode Systems AB
6971 * Released under LGPL License.
6972 *
6973 * License: http://www.tinymce.com/license
6974 * Contributing: http://www.tinymce.com/contributing
6975 */
6976
6977/**
6978 * This class contains a few utility methods for ranges.
6979 *
6980 * @class tinymce.dom.RangeUtils
6981 * @private
6982 */
6983define("tinymce/dom/RangeUtils", [
6984	"tinymce/util/Tools",
6985	"tinymce/dom/TreeWalker"
6986], function(Tools, TreeWalker) {
6987	var each = Tools.each;
6988
6989	function getEndChild(container, index) {
6990		var childNodes = container.childNodes;
6991
6992		index--;
6993
6994		if (index > childNodes.length - 1) {
6995			index = childNodes.length - 1;
6996		} else if (index < 0) {
6997			index = 0;
6998		}
6999
7000		return childNodes[index] || container;
7001	}
7002
7003	function RangeUtils(dom) {
7004		/**
7005		 * Walks the specified range like object and executes the callback for each sibling collection it finds.
7006		 *
7007		 * @method walk
7008		 * @param {Object} rng Range like object.
7009		 * @param {function} callback Callback function to execute for each sibling collection.
7010		 */
7011		this.walk = function(rng, callback) {
7012			var startContainer = rng.startContainer,
7013				startOffset = rng.startOffset,
7014				endContainer = rng.endContainer,
7015				endOffset = rng.endOffset,
7016				ancestor, startPoint,
7017				endPoint, node, parent, siblings, nodes;
7018
7019			// Handle table cell selection the table plugin enables
7020			// you to fake select table cells and perform formatting actions on them
7021			nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
7022			if (nodes.length > 0) {
7023				each(nodes, function(node) {
7024					callback([node]);
7025				});
7026
7027				return;
7028			}
7029
7030			/**
7031			 * Excludes start/end text node if they are out side the range
7032			 *
7033			 * @private
7034			 * @param {Array} nodes Nodes to exclude items from.
7035			 * @return {Array} Array with nodes excluding the start/end container if needed.
7036			 */
7037			function exclude(nodes) {
7038				var node;
7039
7040				// First node is excluded
7041				node = nodes[0];
7042				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
7043					nodes.splice(0, 1);
7044				}
7045
7046				// Last node is excluded
7047				node = nodes[nodes.length - 1];
7048				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
7049					nodes.splice(nodes.length - 1, 1);
7050				}
7051
7052				return nodes;
7053			}
7054
7055			/**
7056			 * Collects siblings
7057			 *
7058			 * @private
7059			 * @param {Node} node Node to collect siblings from.
7060			 * @param {String} name Name of the sibling to check for.
7061			 * @return {Array} Array of collected siblings.
7062			 */
7063			function collectSiblings(node, name, end_node) {
7064				var siblings = [];
7065
7066				for (; node && node != end_node; node = node[name]) {
7067					siblings.push(node);
7068				}
7069
7070				return siblings;
7071			}
7072
7073			/**
7074			 * Find an end point this is the node just before the common ancestor root.
7075			 *
7076			 * @private
7077			 * @param {Node} node Node to start at.
7078			 * @param {Node} root Root/ancestor element to stop just before.
7079			 * @return {Node} Node just before the root element.
7080			 */
7081			function findEndPoint(node, root) {
7082				do {
7083					if (node.parentNode == root) {
7084						return node;
7085					}
7086
7087					node = node.parentNode;
7088				} while (node);
7089			}
7090
7091			function walkBoundary(start_node, end_node, next) {
7092				var siblingName = next ? 'nextSibling' : 'previousSibling';
7093
7094				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
7095					parent = node.parentNode;
7096					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
7097
7098					if (siblings.length) {
7099						if (!next) {
7100							siblings.reverse();
7101						}
7102
7103						callback(exclude(siblings));
7104					}
7105				}
7106			}
7107
7108			// If index based start position then resolve it
7109			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
7110				startContainer = startContainer.childNodes[startOffset];
7111			}
7112
7113			// If index based end position then resolve it
7114			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
7115				endContainer = getEndChild(endContainer, endOffset);
7116			}
7117
7118			// Same container
7119			if (startContainer == endContainer) {
7120				return callback(exclude([startContainer]));
7121			}
7122
7123			// Find common ancestor and end points
7124			ancestor = dom.findCommonAncestor(startContainer, endContainer);
7125
7126			// Process left side
7127			for (node = startContainer; node; node = node.parentNode) {
7128				if (node === endContainer) {
7129					return walkBoundary(startContainer, ancestor, true);
7130				}
7131
7132				if (node === ancestor) {
7133					break;
7134				}
7135			}
7136
7137			// Process right side
7138			for (node = endContainer; node; node = node.parentNode) {
7139				if (node === startContainer) {
7140					return walkBoundary(endContainer, ancestor);
7141				}
7142
7143				if (node === ancestor) {
7144					break;
7145				}
7146			}
7147
7148			// Find start/end point
7149			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
7150			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
7151
7152			// Walk left leaf
7153			walkBoundary(startContainer, startPoint, true);
7154
7155			// Walk the middle from start to end point
7156			siblings = collectSiblings(
7157				startPoint == startContainer ? startPoint : startPoint.nextSibling,
7158				'nextSibling',
7159				endPoint == endContainer ? endPoint.nextSibling : endPoint
7160			);
7161
7162			if (siblings.length) {
7163				callback(exclude(siblings));
7164			}
7165
7166			// Walk right leaf
7167			walkBoundary(endContainer, endPoint);
7168		};
7169
7170		/**
7171		 * Splits the specified range at it's start/end points.
7172		 *
7173		 * @private
7174		 * @param {Range/RangeObject} rng Range to split.
7175		 * @return {Object} Range position object.
7176		 */
7177		this.split = function(rng) {
7178			var startContainer = rng.startContainer,
7179				startOffset = rng.startOffset,
7180				endContainer = rng.endContainer,
7181				endOffset = rng.endOffset;
7182
7183			function splitText(node, offset) {
7184				return node.splitText(offset);
7185			}
7186
7187			// Handle single text node
7188			if (startContainer == endContainer && startContainer.nodeType == 3) {
7189				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
7190					endContainer = splitText(startContainer, startOffset);
7191					startContainer = endContainer.previousSibling;
7192
7193					if (endOffset > startOffset) {
7194						endOffset = endOffset - startOffset;
7195						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
7196						endOffset = endContainer.nodeValue.length;
7197						startOffset = 0;
7198					} else {
7199						endOffset = 0;
7200					}
7201				}
7202			} else {
7203				// Split startContainer text node if needed
7204				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
7205					startContainer = splitText(startContainer, startOffset);
7206					startOffset = 0;
7207				}
7208
7209				// Split endContainer text node if needed
7210				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
7211					endContainer = splitText(endContainer, endOffset).previousSibling;
7212					endOffset = endContainer.nodeValue.length;
7213				}
7214			}
7215
7216			return {
7217				startContainer: startContainer,
7218				startOffset: startOffset,
7219				endContainer: endContainer,
7220				endOffset: endOffset
7221			};
7222		};
7223
7224		/**
7225		 * Normalizes the specified range by finding the closest best suitable caret location.
7226		 *
7227		 * @private
7228		 * @param {Range} rng Range to normalize.
7229		 * @return {Boolean} True/false if the specified range was normalized or not.
7230		 */
7231		this.normalize = function(rng) {
7232			var normalized, collapsed;
7233
7234			function normalizeEndPoint(start) {
7235				var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
7236				var directionLeft, isAfterNode;
7237
7238				function hasBrBeforeAfter(node, left) {
7239					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
7240
7241					while ((node = walker[left ? 'prev' : 'next']())) {
7242						if (node.nodeName === "BR") {
7243							return true;
7244						}
7245					}
7246				}
7247
7248				function isPrevNode(node, name) {
7249					return node.previousSibling && node.previousSibling.nodeName == name;
7250				}
7251
7252				// Walks the dom left/right to find a suitable text node to move the endpoint into
7253				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
7254				function findTextNodeRelative(left, startNode) {
7255					var walker, lastInlineElement, parentBlockContainer;
7256
7257					startNode = startNode || container;
7258					parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
7259
7260					// Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
7261					// This: <p><br>|</p> becomes <p>|<br></p>
7262					if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
7263						container = startNode.parentNode;
7264						offset = dom.nodeIndex(startNode);
7265						normalized = true;
7266						return;
7267					}
7268
7269					// Walk left until we hit a text node we can move to or a block/br/img
7270					walker = new TreeWalker(startNode, parentBlockContainer);
7271					while ((node = walker[left ? 'prev' : 'next']())) {
7272						// Break if we hit a non content editable node
7273						if (dom.getContentEditableParent(node) === "false") {
7274							return;
7275						}
7276
7277						// Found text node that has a length
7278						if (node.nodeType === 3 && node.nodeValue.length > 0) {
7279							container = node;
7280							offset = left ? node.nodeValue.length : 0;
7281							normalized = true;
7282							return;
7283						}
7284
7285						// Break if we find a block or a BR/IMG/INPUT etc
7286						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
7287							return;
7288						}
7289
7290						lastInlineElement = node;
7291					}
7292
7293					// Only fetch the last inline element when in caret mode for now
7294					if (collapsed && lastInlineElement) {
7295						container = lastInlineElement;
7296						normalized = true;
7297						offset = 0;
7298					}
7299				}
7300
7301				container = rng[(start ? 'start' : 'end') + 'Container'];
7302				offset = rng[(start ? 'start' : 'end') + 'Offset'];
7303				isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
7304				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
7305				directionLeft = start;
7306
7307				if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
7308					directionLeft = false;
7309				}
7310
7311				// If the container is a document move it to the body element
7312				if (container.nodeType === 9) {
7313					container = dom.getRoot();
7314					offset = 0;
7315				}
7316
7317				// If the container is body try move it into the closest text node or position
7318				if (container === body) {
7319					// If start is before/after a image, table etc
7320					if (directionLeft) {
7321						node = container.childNodes[offset > 0 ? offset - 1 : 0];
7322						if (node) {
7323							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
7324								return;
7325							}
7326						}
7327					}
7328
7329					// Resolve the index
7330					if (container.hasChildNodes()) {
7331						offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
7332						container = container.childNodes[offset];
7333						offset = 0;
7334
7335						// Don't walk into elements that doesn't have any child nodes like a IMG
7336						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
7337							// Walk the DOM to find a text node to place the caret at or a BR
7338							node = container;
7339							walker = new TreeWalker(container, body);
7340
7341							do {
7342								// Found a text node use that position
7343								if (node.nodeType === 3 && node.nodeValue.length > 0) {
7344									offset = directionLeft ? 0 : node.nodeValue.length;
7345									container = node;
7346									normalized = true;
7347									break;
7348								}
7349
7350								// Found a BR/IMG element that we can place the caret before
7351								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
7352									offset = dom.nodeIndex(node);
7353									container = node.parentNode;
7354
7355									// Put caret after image when moving the end point
7356									if (node.nodeName ==  "IMG" && !directionLeft) {
7357										offset++;
7358									}
7359
7360									normalized = true;
7361									break;
7362								}
7363							} while ((node = (directionLeft ? walker.next() : walker.prev())));
7364						}
7365					}
7366				}
7367
7368				// Lean the caret to the left if possible
7369				if (collapsed) {
7370					// So this: <b>x</b><i>|x</i>
7371					// Becomes: <b>x|</b><i>x</i>
7372					// Seems that only gecko has issues with this
7373					if (container.nodeType === 3 && offset === 0) {
7374						findTextNodeRelative(true);
7375					}
7376
7377					// Lean left into empty inline elements when the caret is before a BR
7378					// So this: <i><b></b><i>|<br></i>
7379					// Becomes: <i><b>|</b><i><br></i>
7380					// Seems that only gecko has issues with this.
7381					// Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
7382					if (container.nodeType === 1) {
7383						node = container.childNodes[offset];
7384
7385						// Offset is after the containers last child
7386						// then use the previous child for normalization
7387						if (!node) {
7388							node = container.childNodes[offset - 1];
7389						}
7390
7391						if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
7392							!hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
7393							findTextNodeRelative(true, node);
7394						}
7395					}
7396				}
7397
7398				// Lean the start of the selection right if possible
7399				// So this: x[<b>x]</b>
7400				// Becomes: x<b>[x]</b>
7401				if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
7402					findTextNodeRelative(false);
7403				}
7404
7405				// Set endpoint if it was normalized
7406				if (normalized) {
7407					rng['set' + (start ? 'Start' : 'End')](container, offset);
7408				}
7409			}
7410
7411			collapsed = rng.collapsed;
7412
7413			normalizeEndPoint(true);
7414
7415			if (!collapsed) {
7416				normalizeEndPoint();
7417			}
7418
7419			// If it was collapsed then make sure it still is
7420			if (normalized && collapsed) {
7421				rng.collapse(true);
7422			}
7423
7424			return normalized;
7425		};
7426	}
7427
7428	/**
7429	 * Compares two ranges and checks if they are equal.
7430	 *
7431	 * @static
7432	 * @method compareRanges
7433	 * @param {DOMRange} rng1 First range to compare.
7434	 * @param {DOMRange} rng2 First range to compare.
7435	 * @return {Boolean} true/false if the ranges are equal.
7436	 */
7437	RangeUtils.compareRanges = function(rng1, rng2) {
7438		if (rng1 && rng2) {
7439			// Compare native IE ranges
7440			if (rng1.item || rng1.duplicate) {
7441				// Both are control ranges and the selected element matches
7442				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
7443					return true;
7444				}
7445
7446				// Both are text ranges and the range matches
7447				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
7448					return true;
7449				}
7450			} else {
7451				// Compare w3c ranges
7452				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
7453			}
7454		}
7455
7456		return false;
7457	};
7458
7459	return RangeUtils;
7460});
7461
7462// Included from: js/tinymce/classes/NodeChange.js
7463
7464/**
7465 * NodeChange.js
7466 *
7467 * Copyright, Moxiecode Systems AB
7468 * Released under LGPL License.
7469 *
7470 * License: http://www.tinymce.com/license
7471 * Contributing: http://www.tinymce.com/contributing
7472 */
7473
7474/**
7475 * This class handles the nodechange event dispatching both manual and though selection change events.
7476 *
7477 * @class tinymce.NodeChange
7478 * @private
7479 */
7480define("tinymce/NodeChange", [
7481	"tinymce/dom/RangeUtils"
7482], function(RangeUtils) {
7483	return function(editor) {
7484		var lastRng, lastPath = [];
7485
7486		/**
7487		 * Returns true/false if the current element path has been changed or not.
7488		 *
7489		 * @private
7490		 * @return {Boolean} True if the element path is the same false if it's not.
7491		 */
7492		function isSameElementPath(startElm) {
7493			var i, currentPath;
7494
7495			currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
7496			if (currentPath.length === lastPath.length) {
7497				for (i = currentPath.length; i >= 0; i--) {
7498					if (currentPath[i] !== lastPath[i]) {
7499						break;
7500					}
7501				}
7502
7503				if (i === -1) {
7504					lastPath = currentPath;
7505					return true;
7506				}
7507			}
7508
7509			lastPath = currentPath;
7510
7511			return false;
7512		}
7513
7514		// Gecko doesn't support the "selectionchange" event
7515		if (!('onselectionchange' in editor.getDoc())) {
7516			editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
7517				var nativeRng, fakeRng;
7518
7519				// Since DOM Ranges mutate on modification
7520				// of the DOM we need to clone it's contents
7521				nativeRng = editor.selection.getRng();
7522				fakeRng = {
7523					startContainer: nativeRng.startContainer,
7524					startOffset: nativeRng.startOffset,
7525					endContainer: nativeRng.endContainer,
7526					endOffset: nativeRng.endOffset
7527				};
7528
7529				// Always treat nodechange as a selectionchange since applying
7530				// formatting to the current range wouldn't update the range but it's parent
7531				if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
7532					editor.fire('SelectionChange');
7533				}
7534
7535				lastRng = fakeRng;
7536			});
7537		}
7538
7539		// IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
7540		// When the contextmenu event fires the selection is located at the right location
7541		editor.on('contextmenu', function() {
7542			editor.fire('SelectionChange');
7543		});
7544
7545		editor.on('SelectionChange', function() {
7546			var startElm = editor.selection.getStart(true);
7547
7548			// Fire a nodechange only when the selection isn't collapsed since focusout will c
7548ollapse and remove the selection
7549			if (!editor.selection.isCollapsed() && !isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
7550				editor.nodeChanged({selectionChange: true});
7551			}
7552		});
7553
7554		// Fire an extra nodeChange on mouseup for compatibility reasons
7555		editor.on('MouseUp', function(e) {
7556			if (!e.isDefaultPrevented()) {
7557				// Delay nodeChanged call for WebKit edge case issue where the range
7558				// isn't updated until after you click outside a selected image
7559				setTimeout(function() {
7560					editor.nodeChanged();
7561				}, 0);
7562			}
7563		});
7564
7565		/**
7566		 * Distpaches out a onNodeChange event to all observers. This method should be called when you
7567		 * need to update the UI states or element path etc.
7568		 *
7569		 * @method nodeChanged
7570		 * @param {Object} args Optional args to pass to NodeChange event handlers.
7571		 */
7572		this.nodeChanged = function(args) {
7573			var selection = editor.selection, node, parents, root;
7574
7575			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
7576			if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.settings.readonly) {
7577				// Get start node
7578				root = editor.getBody();
7579				node = selection.getStart() || root;
7580				node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node;
7581
7582				// Edge case for <p>|<img></p>
7583				if (node.nodeName == 'IMG' && selection.isCollapsed()) {
7584					node = node.parentNode;
7585				}
7586
7587				// Get parents and add them to object
7588				parents = [];
7589				editor.dom.getParent(node, function(node) {
7590					if (node === root) {
7591						return true;
7592					}
7593
7594					parents.push(node);
7595				});
7596
7597				args = args || {};
7598				args.element = node;
7599				args.parents = parents;
7600
7601				editor.fire('NodeChange', args);
7602			}
7603		};
7604	};
7605});
7606
7607// Included from: js/tinymce/classes/html/Node.js
7608
7609/**
7610 * Node.js
7611 *
7612 * Copyright, Moxiecode Systems AB
7613 * Released under LGPL License.
7614 *
7615 * License: http://www.tinymce.com/license
7616 * Contributing: http://www.tinymce.com/contributing
7617 */
7618
7619/**
7620 * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
7621 *
7622 * @example
7623 * var node = new tinymce.html.Node('strong', 1);
7624 * someRoot.append(node);
7625 *
7626 * @class tinymce.html.Node
7627 * @version 3.4
7628 */
7629define("tinymce/html/Node", [], function() {
7630	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
7631		'#text': 3,
7632		'#comment': 8,
7633		'#cdata': 4,
7634		'#pi': 7,
7635		'#doctype': 10,
7636		'#document-fragment': 11
7637	};
7638
7639	// Walks the tree left/right
7640	function walk(node, root_node, prev) {
7641		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
7642
7643		// Walk into nodes if it has a start
7644		if (node[startName]) {
7645			return node[startName];
7646		}
7647
7648		// Return the sibling if it has one
7649		if (node !== root_node) {
7650			sibling = node[siblingName];
7651
7652			if (sibling) {
7653				return sibling;
7654			}
7655
7656			// Walk up the parents to look for siblings
7657			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
7658				sibling = parent[siblingName];
7659
7660				if (sibling) {
7661					return sibling;
7662				}
7663			}
7664		}
7665	}
7666
7667	/**
7668	 * Constructs a new Node instance.
7669	 *
7670	 * @constructor
7671	 * @method Node
7672	 * @param {String} name Name of the node type.
7673	 * @param {Number} type Numeric type representing the node.
7674	 */
7675	function Node(name, type) {
7676		this.name = name;
7677		this.type = type;
7678
7679		if (type === 1) {
7680			this.attributes = [];
7681			this.attributes.map = {};
7682		}
7683	}
7684
7685	Node.prototype = {
7686		/**
7687		 * Replaces the current node with the specified one.
7688		 *
7689		 * @example
7690		 * someNode.replace(someNewNode);
7691		 *
7692		 * @method replace
7693		 * @param {tinymce.html.Node} node Node to replace the current node with.
7694		 * @return {tinymce.html.Node} The old node that got replaced.
7695		 */
7696		replace: function(node) {
7697			var self = this;
7698
7699			if (node.parent) {
7700				node.remove();
7701			}
7702
7703			self.insert(node, self);
7704			self.remove();
7705
7706			return self;
7707		},
7708
7709		/**
7710		 * Gets/sets or removes an attribute by name.
7711		 *
7712		 * @example
7713		 * someNode.attr("name", "value"); // Sets an attribute
7714		 * console.log(someNode.attr("name")); // Gets an attribute
7715		 * someNode.attr("name", null); // Removes an attribute
7716		 *
7717		 * @method attr
7718		 * @param {String} name Attribute name to set or get.
7719		 * @param {String} value Optional value to set.
7720		 * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
7721		 */
7722		attr: function(name, value) {
7723			var self = this, attrs, i, undef;
7724
7725			if (typeof name !== "string") {
7726				for (i in name) {
7727					self.attr(i, name[i]);
7728				}
7729
7730				return self;
7731			}
7732
7733			if ((attrs = self.attributes)) {
7734				if (value !== undef) {
7735					// Remove attribute
7736					if (value === null) {
7737						if (name in attrs.map) {
7738							delete attrs.map[name];
7739
7740							i = attrs.length;
7741							while (i--) {
7742								if (attrs[i].name === name) {
7743									attrs = attrs.splice(i, 1);
7744									return self;
7745								}
7746							}
7747						}
7748
7749						return self;
7750					}
7751
7752					// Set attribute
7753					if (name in attrs.map) {
7754						// Set attribute
7755						i = attrs.length;
7756						while (i--) {
7757							if (attrs[i].name === name) {
7758								attrs[i].value = value;
7759								break;
7760							}
7761						}
7762					} else {
7763						attrs.push({name: name, value: value});
7764					}
7765
7766					attrs.map[name] = value;
7767
7768					return self;
7769				} else {
7770					return attrs.map[name];
7771				}
7772			}
7773		},
7774
7775		/**
7776		 * Does a shallow clones the node into a new node. It will also exclude id attributes since
7777		 * there should only be one id per document.
7778		 *
7779		 * @example
7780		 * var clonedNode = node.clone();
7781		 *
7782		 * @method clone
7783		 * @return {tinymce.html.Node} New copy of the original node.
7784		 */
7785		clone: function() {
7786			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
7787
7788			// Clone element attributes
7789			if ((selfAttrs = self.attributes)) {
7790				cloneAttrs = [];
7791				cloneAttrs.map = {};
7792
7793				for (i = 0, l = selfAttrs.length; i < l; i++) {
7794					selfAttr = selfAttrs[i];
7795
7796					// Clone everything except id
7797					if (selfAttr.name !== 'id') {
7798						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
7799						cloneAttrs.map[selfAttr.name] = selfAttr.value;
7800					}
7801				}
7802
7803				clone.attributes = cloneAttrs;
7804			}
7805
7806			clone.value = self.value;
7807			clone.shortEnded = self.shortEnded;
7808
7809			return clone;
7810		},
7811
7812		/**
7813		 * Wraps the node in in another node.
7814		 *
7815		 * @example
7816		 * node.wrap(wrapperNode);
7817		 *
7818		 * @method wrap
7819		 */
7820		wrap: function(wrapper) {
7821			var self = this;
7822
7823			self.parent.insert(wrapper, self);
7824			wrapper.append(self);
7825
7826			return self;
7827		},
7828
7829		/**
7830		 * Unwraps the node in other words it removes the node but keeps the children.
7831		 *
7832		 * @example
7833		 * node.unwrap();
7834		 *
7835		 * @method unwrap
7836		 */
7837		unwrap: function() {
7838			var self = this, node, next;
7839
7840			for (node = self.firstChild; node;) {
7841				next = node.next;
7842				self.insert(node, self, true);
7843				node = next;
7844			}
7845
7846			self.remove();
7847		},
7848
7849		/**
7850		 * Removes the node from it's parent.
7851		 *
7852		 * @example
7853		 * node.remove();
7854		 *
7855		 * @method remove
7856		 * @return {tinymce.html.Node} Current node that got removed.
7857		 */
7858		remove: function() {
7859			var self = this, parent = self.parent, next = self.next, prev = self.prev;
7860
7861			if (parent) {
7862				if (parent.firstChild === self) {
7863					parent.firstChild = next;
7864
7865					if (next) {
7866						next.prev = null;
7867					}
7868				} else {
7869					prev.next = next;
7870				}
7871
7872				if (parent.lastChild === self) {
7873					parent.lastChild = prev;
7874
7875					if (prev) {
7876						prev.next = null;
7877					}
7878				} else {
7879					next.prev = prev;
7880				}
7881
7882				self.parent = self.next = self.prev = null;
7883			}
7884
7885			return self;
7886		},
7887
7888		/**
7889		 * Appends a new node as a child of the current node.
7890		 *
7891		 * @example
7892		 * node.append(someNode);
7893		 *
7894		 * @method append
7895		 * @param {tinymce.html.Node} node Node to append as a child of the current one.
7896		 * @return {tinymce.html.Node} The node that got appended.
7897		 */
7898		append: function(node) {
7899			var self = this, last;
7900
7901			if (node.parent) {
7902				node.remove();
7903			}
7904
7905			last = self.lastChild;
7906			if (last) {
7907				last.next = node;
7908				node.prev = last;
7909				self.lastChild = node;
7910			} else {
7911				self.lastChild = self.firstChild = node;
7912			}
7913
7914			node.parent = self;
7915
7916			return node;
7917		},
7918
7919		/**
7920		 * Inserts a node at a specific position as a child of the current node.
7921		 *
7922		 * @example
7923		 * parentNode.insert(newChildNode, oldChildNode);
7924		 *
7925		 * @method insert
7926		 * @param {tinymce.html.Node} node Node to insert as a child of the current node.
7927		 * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
7928		 * @param {Boolean} before Optional state to insert the node before the reference node.
7929		 * @return {tinymce.html.Node} The node that got inserted.
7930		 */
7931		insert: function(node, ref_node, before) {
7932			var parent;
7933
7934			if (node.parent) {
7935				node.remove();
7936			}
7937
7938			parent = ref_node.parent || this;
7939
7940			if (before) {
7941				if (ref_node === parent.firstChild) {
7942					parent.firstChild = node;
7943				} else {
7944					ref_node.prev.next = node;
7945				}
7946
7947				node.prev = ref_node.prev;
7948				node.next = ref_node;
7949				ref_node.prev = node;
7950			} else {
7951				if (ref_node === parent.lastChild) {
7952					parent.lastChild = node;
7953				} else {
7954					ref_node.next.prev = node;
7955				}
7956
7957				node.next = ref_node.next;
7958				node.prev = ref_node;
7959				ref_node.next = node;
7960			}
7961
7962			node.parent = parent;
7963
7964			return node;
7965		},
7966
7967		/**
7968		 * Get all children by name.
7969		 *
7970		 * @method getAll
7971		 * @param {String} name Name of the child nodes to collect.
7972		 * @return {Array} Array with child nodes matchin the specified name.
7973		 */
7974		getAll: function(name) {
7975			var self = this, node, collection = [];
7976
7977			for (node = self.firstChild; node; node = walk(node, self)) {
7978				if (node.name === name) {
7979					collection.push(node);
7980				}
7981			}
7982
7983			return collection;
7984		},
7985
7986		/**
7987		 * Removes all children of the current node.
7988		 *
7989		 * @method empty
7990		 * @return {tinymce.html.Node} The current node that got cleared.
7991		 */
7992		empty: function() {
7993			var self = this, nodes, i, node;
7994
7995			// Remove all children
7996			if (self.firstChild) {
7997				nodes = [];
7998
7999				// Collect the children
8000				for (node = self.firstChild; node; node = walk(node, self)) {
8001					nodes.push(node);
8002				}
8003
8004				// Remove the children
8005				i = nodes.length;
8006				while (i--) {
8007					node = nodes[i];
8008					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
8009				}
8010			}
8011
8012			self.firstChild = self.lastChild = null;
8013
8014			return self;
8015		},
8016
8017		/**
8018		 * Returns true/false if the node is to be considered empty or not.
8019		 *
8020		 * @example
8021		 * node.isEmpty({img: true});
8022		 * @method isEmpty
8023		 * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
8024		 * @return {Boolean} true/false if the node is empty or not.
8025		 */
8026		isEmpty: function(elements) {
8027			var self = this, node = self.firstChild, i, name;
8028
8029			if (node) {
8030				do {
8031					if (node.type === 1) {
8032						// Ignore bogus elements
8033						if (node.attributes.map['data-mce-bogus']) {
8034							continue;
8035						}
8036
8037						// Keep empty elements like <img />
8038						if (elements[node.name]) {
8039							return false;
8040						}
8041
8042						// Keep bookmark nodes and name attribute like <a name="1"></a>
8043						i = node.attributes.length;
8044						while (i--) {
8045							name = node.attributes[i].name;
8046							if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
8047								return false;
8048							}
8049						}
8050					}
8051
8052					// Keep comments
8053					if (node.type === 8) {
8054						return false;
8055					}
8056
8057					// Keep non whitespace text nodes
8058					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
8059						return false;
8060					}
8061				} while ((node = walk(node, self)));
8062			}
8063
8064			return true;
8065		},
8066
8067		/**
8068		 * Walks to the next or previous node and returns that node or null if it wasn't found.
8069		 *
8070		 * @method walk
8071		 * @param {Boolean} prev Optional previous node state defaults to false.
8072		 * @return {tinymce.html.Node} Node that is next to or previous of the current node.
8073		 */
8074		walk: function(prev) {
8075			return walk(this, null, prev);
8076		}
8077	};
8078
8079	/**
8080	 * Creates a node of a specific type.
8081	 *
8082	 * @static
8083	 * @method create
8084	 * @param {String} name Name of the node type to create for example "b" or "#text".
8085	 * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
8086	 */
8087	Node.create = function(name, attrs) {
8088		var node, attrName;
8089
8090		// Create node
8091		node = new Node(name, typeLookup[name] || 1);
8092
8093		// Add attributes if needed
8094		if (attrs) {
8095			for (attrName in attrs) {
8096				node.attr(attrName, attrs[attrName]);
8097			}
8098		}
8099
8100		return node;
8101	};
8102
8103	return Node;
8104});
8105
8106// Included from: js/tinymce/classes/html/Schema.js
8107
8108/**
8109 * Schema.js
8110 *
8111 * Copyright, Moxiecode Systems AB
8112 * Released under LGPL License.
8113 *
8114 * License: http://www.tinymce.com/license
8115 * Contributing: http://www.tinymce.com/contributing
8116 */
8117
8118/**
8119 * Schema validator class.
8120 *
8121 * @class tinymce.html.Schema
8122 * @example
8123 *  if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
8124 *    alert('span is valid child of p.');
8125 *
8126 *  if (tinymce.activeEditor.schema.getElementRule('p'))
8127 *    alert('P is a valid element.');
8128 *
8129 * @class tinymce.html.Schema
8130 * @version 3.4
8131 */
8132define("tinymce/html/Schema", [
8133	"tinymce/util/Tools"
8134], function(Tools) {
8135	var mapCache = {}, dummyObj = {};
8136	var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
8137
8138	function split(items, delim) {
8139		return items ? items.split(delim || ' ') : [];
8140	}
8141
8142	/**
8143	 * Builds a schema lookup table
8144	 *
8145	 * @private
8146	 * @param {String} type html4, html5 or html5-strict schema type.
8147	 * @return {Object} Schema lookup table.
8148	 */
8149	function compileSchema(type) {
8150		var schema = {}, globalAttributes, blockContent;
8151		var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
8152
8153		function add(name, attributes, children) {
8154			var ni, i, attributesOrder, args = arguments;
8155
8156			function arrayToMap(array, obj) {
8157				var map = {}, i, l;
8158
8159				for (i = 0, l = array.length; i < l; i++) {
8160					map[array[i]] = obj || {};
8161				}
8162
8163				return map;
8164			}
8165
8166			children = children || [];
8167			attributes = attributes || "";
8168
8169			if (typeof(children) === "string") {
8170				children = split(children);
8171			}
8172
8173			// Split string children
8174			for (i = 3; i < args.length; i++) {
8175				if (typeof(args[i]) === "string") {
8176					args[i] = split(args[i]);
8177				}
8178
8179				children.push.apply(children, args[i]);
8180			}
8181
8182			name = split(name);
8183			ni = name.length;
8184			while (ni--) {
8185				attributesOrder = [].concat(globalAttributes, split(attributes));
8186				schema[name[ni]] = {
8187					attributes: arrayToMap(attributesOrder),
8188					attributesOrder: attributesOrder,
8189					children: arrayToMap(children, dummyObj)
8190				};
8191			}
8192		}
8193
8194		function addAttrs(name, attributes) {
8195			var ni, schemaItem, i, l;
8196
8197			name = split(name);
8198			ni = name.length;
8199			attributes = split(attributes);
8200			while (ni--) {
8201				schemaItem = schema[name[ni]];
8202				for (i = 0, l = attributes.length; i < l; i++) {
8203					schemaItem.attributes[attributes[i]] = {};
8204					schemaItem.attributesOrder.push(attributes[i]);
8205				}
8206			}
8207		}
8208
8209		// Use cached schema
8210		if (mapCache[type]) {
8211			return mapCache[type];
8212		}
8213
8214		// Attributes present on all elements
8215		globalAttributes = split("id accesskey class dir lang style tabindex title");
8216
8217		// Event attributes can be opt-in/opt-out
8218		/*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
8219				"ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
8220				"onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
8221				"onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
8222				"onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
8223				"onwaiting"
8224		);*/
8225
8226		// Block content elements
8227		blockContent = split(
8228			"address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
8229		);
8230
8231		// Phrasing content elements from the HTML5 spec (inline)
8232		phrasingContent = split(
8233			"a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
8234			"label map noscript object q s samp script select small span strong sub sup " +
8235			"textarea u var #text #comment"
8236		);
8237
8238		// Add HTML5 items to globalAttributes, blockContent, phrasingContent
8239		if (type != "html4") {
8240			globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
8241				"hidden spellcheck translate"));
8242			blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
8243			phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
8244				"video ruby bdi keygen"));
8245		}
8246
8247		// Add HTML4 elements unless it's html5-strict
8248		if (type != "html5-strict") {
8249			globalAttributes.push("xml:lang");
8250
8251			html4PhrasingContent = split("acronym applet basefont big font strike tt");
8252			phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
8253
8254			each(html4PhrasingContent, function(name) {
8255				add(name, "", phrasingContent);
8256			});
8257
8258			html4BlockContent = split("center dir isindex noframes");
8259			blockContent.push.apply(blockContent, html4BlockContent);
8260
8261			// Flow content elements from the HTML5 spec (block+inline)
8262			flowContent = [].concat(blockContent, phrasingContent);
8263
8264			each(html4BlockContent, function(name) {
8265				add(name, "", flowContent);
8266			});
8267		}
8268
8269		// Flow content elements from the HTML5 spec (block+inline)
8270		flowContent = flowContent || [].concat(blockContent, phrasingContent);
8271
8272		// HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
8273		// Schema items <element name>, <specific attributes>, <children ..>
8274		add("html", "manifest", "head body");
8275		add("head", "", "base command link meta noscript script style title");
8276		add("title hr noscript br");
8277		add("base", "href target");
8278		add("link", "href rel media hreflang type sizes hreflang");
8279		add("meta", "name http-equiv content charset");
8280		add("style", "media type scoped");
8281		add("script", "src async defer type charset");
8282		add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
8283				"onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
8284				"onpopstate onresize onscroll onstorage onunload", flowContent);
8285		add("address dt dd div caption", "", flowContent);
8286		add("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn", "", phrasingContent);
8287		add("blockquote", "cite", flowContent);
8288		add("ol", "reversed start type", "li");
8289		add("ul", "", "li");
8290		add("li", "value", flowContent);
8291		add("dl", "", "dt dd");
8292		add("a", "href target rel media hreflang type", phrasingContent);
8293		add("q", "cite", phrasingContent);
8294		add("ins del", "cite datetime", flowContent);
8295		add("img", "src srcset alt usemap ismap width height");
8296		add("iframe", "src name width height", flowContent);
8297		add("embed", "src type width height");
8298		add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
8299		add("param", "name value");
8300		add("map", "name", flowContent, "area");
8301		add("area", "alt coords shape href target rel media hreflang type");
8302		add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
8303		add("colgroup", "span", "col");
8304		add("col", "span");
8305		add("tbody thead tfoot", "", "tr");
8306		add("tr", "", "td th");
8307		add("td", "colspan rowspan headers", flowContent);
8308		add("th", "colspan rowspan headers scope abbr", flowContent);
8309		add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
8310		add("fieldset", "disabled form name", flowContent, "legend");
8311		add("label", "form for", phrasingContent);
8312		add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
8313				"formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
8314		);
8315		add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
8316			type == "html4" ? flowContent : phrasingContent);
8317		add("select", "disabled form multiple name required size", "option optgroup");
8318		add("optgroup", "disabled label", "option");
8319		add("option", "disabled label selected value");
8320		add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
8321		add("menu", "type label", flowContent, "li");
8322		add("noscript", "", flowContent);
8323
8324		// Extend with HTML5 elements
8325		if (type != "html4") {
8326			add("wbr");
8327			add("ruby", "", phrasingContent, "rt rp");
8328			add("figcaption", "", flowContent);
8329			add("mark rt rp summary bdi", "", phrasingContent);
8330			add("canvas", "width height", flowContent);
8331			add("video", "src crossorigin poster preload autoplay mediagroup loop " +
8332				"muted controls width height buffered", flowContent, "track source");
8333			add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
8334			add("picture", "", "img source");
8335			add("source", "src srcset type media sizes");
8336			add("track", "kind src srclang label default");
8337			add("datalist", "", phrasingContent, "option");
8338			add("article section nav aside header footer", "", flowContent);
8339			add("hgroup", "", "h1 h2 h3 h4 h5 h6");
8340			add("figure", "", flowContent, "figcaption");
8341			add("time", "datetime", phrasingContent);
8342			add("dialog", "open", flowContent);
8343			add("command", "type label icon disabled checked radiogroup command");
8344			add("output", "for form name", phrasingContent);
8345			add("progress", "value max", phrasingContent);
8346			add("meter", "value min max low high optimum", phrasingContent);
8347			add("details", "open", flowContent, "summary");
8348			add("keygen", "autofocus challenge disabled form keytype name");
8349		}
8350
8351		// Extend with HTML4 attributes unless it's html5-strict
8352		if (type != "html5-strict") {
8353			addAttrs("script", "language xml:space");
8354			addAttrs("style", "xml:space");
8355			addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
8356			addAttrs("embed", "align name hspace vspace");
8357			addAttrs("param", "valuetype type");
8358			addAttrs("a", "charset name rev shape coords");
8359			addAttrs("br", "clear");
8360			addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
8361			addAttrs("img", "name longdesc align border hspace vspace");
8362			addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
8363			addAttrs("font basefont", "size color face");
8364			addAttrs("input", "usemap align");
8365			addAttrs("select", "onchange");
8366			addAttrs("textarea");
8367			addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
8368			addAttrs("ul", "type compact");
8369			addAttrs("li", "type");
8370			addAttrs("ol dl menu dir", "compact");
8371			addAttrs("pre", "width xml:space");
8372			addAttrs("hr", "align noshade size width");
8373			addAttrs("isindex", "prompt");
8374			addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
8375			addAttrs("col", "width align char charoff valign");
8376			addAttrs("colgroup", "width align char charoff valign");
8377			addAttrs("thead", "align char charoff valign");
8378			addAttrs("tr", "align char charoff valign bgcolor");
8379			addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
8380			addAttrs("form", "accept");
8381			addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
8382			addAttrs("tfoot", "align char charoff valign");
8383			addAttrs("tbody", "align char charoff valign");
8384			addAttrs("area", "nohref");
8385			addAttrs("body", "background bgcolor text link vlink alink");
8386		}
8387
8388		// Extend with HTML5 attributes unless it's html4
8389		if (type != "html4") {
8390			addAttrs("input button select textarea", "autofocus");
8391			addAttrs("input textarea", "placeholder");
8392			addAttrs("a", "download");
8393			addAttrs("link script img", "crossorigin");
8394			addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
8395		}
8396
8397		// Special: iframe, ruby, video, audio, label
8398
8399		// Delete children of the same name from it's parent
8400		// For example: form can't have a child of the name form
8401		each(split('a form meter progress dfn'), function(name) {
8402			if (schema[name]) {
8403				delete schema[name].children[name];
8404			}
8405		});
8406
8407		// Delete header, footer, sectioning and heading content descendants
8408		/*each('dt th address', function(name) {
8409			delete schema[name].children[name];
8410		});*/
8411
8412		// Caption can't have tables
8413		delete schema.caption.children.table;
8414
8415		// TODO: LI:s can only have value if parent is OL
8416
8417		// TODO: Handle transparent elements
8418		// a ins del canvas map
8419
8420		mapCache[type] = schema;
8421
8422		return schema;
8423	}
8424
8425	function compileElementMap(value, mode) {
8426		var styles;
8427
8428		if (value) {
8429			styles = {};
8430
8431			if (typeof value == 'string') {
8432				value = {
8433					'*': value
8434				};
8435			}
8436
8437			// Convert styles into a rule list
8438			each(value, function(value, key) {
8439				styles[key] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
8440			});
8441		}
8442
8443		return styles;
8444	}
8445
8446	/**
8447	 * Constructs a new Schema instance.
8448	 *
8449	 * @constructor
8450	 * @method Schema
8451	 * @param {Object} settings Name/value settings object.
8452	 */
8453	return function(settings) {
8454		var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
8455		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
8456		var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, textInlineElementsMap;
8457		var customElementsMap = {}, specialElements = {};
8458
8459		// Creates an lookup table map object for the specified option or the default value
8460		function createLookupTable(option, default_value, extendWith) {
8461			var value = settings[option];
8462
8463			if (!value) {
8464				// Get cached default map or make it if needed
8465				value = mapCache[option];
8466
8467				if (!value) {
8468					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
8469					value = extend(value, extendWith);
8470
8471					mapCache[option] = value;
8472				}
8473			} else {
8474				// Create custom map
8475				value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
8476			}
8477
8478			return value;
8479		}
8480
8481		settings = settings || {};
8482		schemaItems = compileSchema(settings.schema);
8483
8484		// Allow all elements and attributes if verify_html is set to false
8485		if (settings.verify_html === false) {
8486			settings.valid_elements = '*[*]';
8487		}
8488
8489		validStyles = compileElementMap(settings.valid_styles);
8490		invalidStyles = compileElementMap(settings.invalid_styles, 'map');
8491		validClasses = compileElementMap(settings.valid_classes, 'map');
8492
8493		// Setup map objects
8494		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
8495		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
8496		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
8497			'meta param embed source wbr track');
8498		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
8499			'noshade nowrap readonly selected autoplay loop controls');
8500		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
8501		textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
8502						'blockquote center dir fieldset header footer article section hgroup aside nav figure');
8503		blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
8504						'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
8505						'datalist select optgroup', textBlockElementsMap);
8506		textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
8507										'dfn code mark q sup sub samp');
8508
8509		each((settings.special || 'script noscript style textarea').split(' '), function(name) {
8510			specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
8511		});
8512
8513		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
8514		function patternToRegExp(str) {
8515			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
8516		}
8517
8518		// Parses the specified valid_elements string and adds to the current rules
8519		// This function is a bit hard to read since it's heavily optimized for speed
8520		function addValidElements(validElements) {
8521			var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
8522				prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
8523				elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
8524				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
8525				hasPatternsRegExp = /[*?+]/;
8526
8527			if (validElements) {
8528				// Split valid elements into an array with rules
8529				validElements = split(validElements, ',');
8530
8531				if (elements['@']) {
8532					globalAttributes = elements['@'].attributes;
8533					globalAttributesOrder = elements['@'].attributesOrder;
8534				}
8535
8536				// Loop all rules
8537				for (ei = 0, el = validElements.length; ei < el; ei++) {
8538					// Parse element rule
8539					matches = elementRuleRegExp.exec(validElements[ei]);
8540					if (matches) {
8541						// Setup local names for matches
8542						prefix = matches[1];
8543						elementName = matches[2];
8544						outputName = matches[3];
8545						attrData = matches[5];
8546
8547						// Create new attributes and attributesOrder
8548						attributes = {};
8549						attributesOrder = [];
8550
8551						// Create the new element
8552						element = {
8553							attributes: attributes,
8554							attributesOrder: attributesOrder
8555						};
8556
8557						// Padd empty elements prefix
8558						if (prefix === '#') {
8559							element.paddEmpty = true;
8560						}
8561
8562						// Remove empty elements prefix
8563						if (prefix === '-') {
8564							element.removeEmpty = true;
8565						}
8566
8567						if (matches[4] === '!') {
8568							element.removeEmptyAttrs = true;
8569						}
8570
8571						// Copy attributes from global rule into current rule
8572						if (globalAttributes) {
8573							for (key in globalAttributes) {
8574								attributes[key] = globalAttributes[key];
8575							}
8576
8577							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
8578						}
8579
8580						// Attributes defined
8581						if (attrData) {
8582							attrData = split(attrData, '|');
8583							for (ai = 0, al = attrData.length; ai < al; ai++) {
8584								matches = attrRuleRegExp.exec(attrData[ai]);
8585								if (matches) {
8586									attr = {};
8587									attrType = matches[1];
8588									attrName = matches[2].replace(/::/g, ':');
8589									prefix = matches[3];
8590									value = matches[4];
8591
8592									// Required
8593									if (attrType === '!') {
8594										element.attributesRequired = element.attributesRequired || [];
8595										element.attributesRequired.push(attrName);
8596										attr.required = true;
8597									}
8598
8599									// Denied from global
8600									if (attrType === '-') {
8601										delete attributes[attrName];
8602										attributesOrder.splice(inArray(attributesOrder, attrName), 1);
8603										continue;
8604									}
8605
8606									// Default value
8607									if (prefix) {
8608										// Default value
8609										if (prefix === '=') {
8610											element.attributesDefault = element.attributesDefault || [];
8611											element.attributesDefault.push({name: attrName, value: value});
8612											attr.defaultValue = value;
8613										}
8614
8615										// Forced value
8616										if (prefix === ':') {
8617											element.attributesForced = element.attributesForced || [];
8618											element.attributesForced.push({name: attrName, value: value});
8619											attr.forcedValue = value;
8620										}
8621
8622										// Required values
8623										if (prefix === '<') {
8624											attr.validValues = makeMap(value, '?');
8625										}
8626									}
8627
8628									// Check for attribute patterns
8629									if (hasPatternsRegExp.test(attrName)) {
8630										element.attributePatterns = element.attributePatterns || [];
8631										attr.pattern = patternToRegExp(attrName);
8632										element.attributePatterns.push(attr);
8633									} else {
8634										// Add attribute to order list if it doesn't already exist
8635										if (!attributes[attrName]) {
8636											attributesOrder.push(attrName);
8637										}
8638
8639										attributes[attrName] = attr;
8640									}
8641								}
8642							}
8643						}
8644
8645						// Global rule, store away these for later usage
8646						if (!globalAttributes && elementName == '@') {
8647							globalAttributes = attributes;
8648							globalAttributesOrder = attributesOrder;
8649						}
8650
8651						// Handle substitute elements such as b/strong
8652						if (outputName) {
8653							element.outputName = elementName;
8654							elements[outputName] = element;
8655						}
8656
8657						// Add pattern or exact element
8658						if (hasPatternsRegExp.test(elementName)) {
8659							element.pattern = patternToRegExp(elementName);
8660							patternElements.push(element);
8661						} else {
8662							elements[elementName] = element;
8663						}
8664					}
8665				}
8666			}
8667		}
8668
8669		function setValidElements(validElements) {
8670			elements = {};
8671			patternElements = [];
8672
8673			addValidElements(validElements);
8674
8675			each(schemaItems, function(element, name) {
8676				children[name] = element.children;
8677			});
8678		}
8679
8680		// Adds custom non HTML elements to the schema
8681		function addCustomElements(customElements) {
8682			var customElementRegExp = /^(~)?(.+)$/;
8683
8684			if (customElements) {
8685				// Flush cached items since we are altering the default maps
8686				mapCache.text_block_elements = mapCache.block_elements = null;
8687
8688				each(split(customElements, ','), function(rule) {
8689					var matches = customElementRegExp.exec(rule),
8690						inline = matches[1] === '~',
8691						cloneName = inline ? 'span' : 'div',
8692						name = matches[2];
8693
8694					children[name] = children[cloneName];
8695					customElementsMap[name] = cloneName;
8696
8697					// If it's not marked as inline then add it to valid block elements
8698					if (!inline) {
8699						blockElementsMap[name.toUpperCase()] = {};
8700						blockElementsMap[name] = {};
8701					}
8702
8703					// Add elements clone if needed
8704					if (!elements[name]) {
8705						var customRule = elements[cloneName];
8706
8707						customRule = extend({}, customRule);
8708						delete customRule.removeEmptyAttrs;
8709						delete customRule.removeEmpty;
8710
8711						elements[name] = customRule;
8712					}
8713
8714					// Add custom elements at span/div positions
8715					each(children, function(element, elmName) {
8716						if (element[cloneName]) {
8717							children[elmName] = element = extend({}, children[elmName]);
8718							element[name] = element[cloneName];
8719						}
8720					});
8721				});
8722			}
8723		}
8724
8725		// Adds valid children to the schema object
8726		function addValidChildren(validChildren) {
8727			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
8728
8729			if (validChildren) {
8730				each(split(validChildren, ','), function(rule) {
8731					var matches = childRuleRegExp.exec(rule), parent, prefix;
8732
8733					if (matches) {
8734						prefix = matches[1];
8735
8736						// Add/remove items from default
8737						if (prefix) {
8738							parent = children[matches[2]];
8739						} else {
8740							parent = children[matches[2]] = {'#comment': {}};
8741						}
8742
8743						parent = children[matches[2]];
8744
8745						each(split(matches[3], '|'), function(child) {
8746							if (prefix === '-') {
8747								// Clone the element before we delete
8748								// things in it to not mess up default schemas
8749								children[matches[2]] = parent = extend({}, children[matches[2]]);
8750
8751								delete parent[child];
8752							} else {
8753								parent[child] = {};
8754							}
8755						});
8756					}
8757				});
8758			}
8759		}
8760
8761		function getElementRule(name) {
8762			var element = elements[name], i;
8763
8764			// Exact match found
8765			if (element) {
8766				return element;
8767			}
8768
8769			// No exact match then try the patterns
8770			i = patternElements.length;
8771			while (i--) {
8772				element = patternElements[i];
8773
8774				if (element.pattern.test(name)) {
8775					return element;
8776				}
8777			}
8778		}
8779
8780		if (!settings.valid_elements) {
8781			// No valid elements defined then clone the elements from the schema spec
8782			each(schemaItems, function(element, name) {
8783				elements[name] = {
8784					attributes: element.attributes,
8785					attributesOrder: element.attributesOrder
8786				};
8787
8788				children[name] = element.children;
8789			});
8790
8791			// Switch these on HTML4
8792			if (settings.schema != "html5") {
8793				each(split('strong/b em/i'), function(item) {
8794					item = split(item, '/');
8795					elements[item[1]].outputName = item[0];
8796				});
8797			}
8798
8799			// Add default alt attribute for images
8800			elements.img.attributesDefault = [{name: 'alt', value: ''}];
8801
8802			// Remove these if they are empty by default
8803			each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
8804				if (elements[name]) {
8805					elements[name].removeEmpty = true;
8806				}
8807			});
8808
8809			// Padd these by default
8810			each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
8811				elements[name].paddEmpty = true;
8812			});
8813
8814			// Remove these if they have no attributes
8815			each(split('span'), function(name) {
8816				elements[name].removeEmptyAttrs = true;
8817			});
8818
8819			// Remove these by default
8820			// TODO: Reenable in 4.1
8821			/*each(split('script style'), function(name) {
8822				delete elements[name];
8823			});*/
8824		} else {
8825			setValidElements(settings.valid_elements);
8826		}
8827
8828		addCustomElements(settings.custom_elements);
8829		addValidChildren(settings.valid_children);
8830		addValidElements(settings.extended_valid_elements);
8831
8832		// Todo: Remove this when we fix list handling to be valid
8833		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
8834
8835		// Delete invalid elements
8836		if (settings.invalid_elements) {
8837			each(explode(settings.invalid_elements), function(item) {
8838				if (elements[item]) {
8839					delete elements[item];
8840				}
8841			});
8842		}
8843
8844		// If the user didn't allow span only allow internal spans
8845		if (!getElementRule('span')) {
8846			addValidElements('span[!data-mce-type|*]');
8847		}
8848
8849		/**
8850		 * Name/value map object with valid parents and children to those parents.
8851		 *
8852		 * @example
8853		 * children = {
8854		 *    div:{p:{}, h1:{}}
8855		 * };
8856		 * @field children
8857		 * @type Object
8858		 */
8859		self.children = children;
8860
8861		/**
8862		 * Name/value map object with valid styles for each element.
8863		 *
8864		 * @method getValidStyles
8865		 * @type Object
8866		 */
8867		self.getValidStyles = function() {
8868			return validStyles;
8869		};
8870
8871		/**
8872		 * Name/value map object with valid styles for each element.
8873		 *
8874		 * @method getInvalidStyles
8875		 * @type Object
8876		 */
8877		self.getInvalidStyles = function() {
8878			return invalidStyles;
8879		};
8880
8881		/**
8882		 * Name/value map object with valid classes for each element.
8883		 *
8884		 * @method getValidClasses
8885		 * @type Object
8886		 */
8887		self.getValidClasses = function() {
8888			return validClasses;
8889		};
8890
8891		/**
8892		 * Returns a map with boolean attributes.
8893		 *
8894		 * @method getBoolAttrs
8895		 * @return {Object} Name/value lookup map for boolean attributes.
8896		 */
8897		self.getBoolAttrs = function() {
8898			return boolAttrMap;
8899		};
8900
8901		/**
8902		 * Returns a map with block elements.
8903		 *
8904		 * @method getBlockElements
8905		 * @return {Object} Name/value lookup map for block elements.
8906		 */
8907		self.getBlockElements = function() {
8908			return blockElementsMap;
8909		};
8910
8911		/**
8912		 * Returns a map with text block elements. Such as: p,h1-h6,div,address
8913		 *
8914		 * @method getTextBlockElements
8915		 * @return {Object} Name/value lookup map for block elements.
8916		 */
8917		self.getTextBlockElements = function() {
8918			return textBlockElementsMap;
8919		};
8920
8921		/**
8922		 * Returns a map of inline text format nodes for example strong/span or ins.
8923		 *
8924		 * @method getTextInlineElements
8925		 * @return {Object} Name/value lookup map for text format elements.
8926		 */
8927		self.getTextInlineElements = function() {
8928			return textInlineElementsMap;
8929		};
8930
8931		/**
8932		 * Returns a map with short ended elements such as BR or IMG.
8933		 *
8934		 * @method getShortEndedElements
8935		 * @return {Object} Name/value lookup map for short ended elements.
8936		 */
8937		self.getShortEndedElements = function() {
8938			return shortEndedElementsMap;
8939		};
8940
8941		/**
8942		 * Returns a map with self closing tags such as <li>.
8943		 *
8944		 * @method getSelfClosingElements
8945		 * @return {Object} Name/value lookup map for self closing tags elements.
8946		 */
8947		self.getSelfClosingElements = function() {
8948			return selfClosingElementsMap;
8949		};
8950
8951		/**
8952		 * Returns a map with elements that should be treated as contents regardless if it has text
8953		 * content in them or not such as TD, VIDEO or IMG.
8954		 *
8955		 * @method getNonEmptyElements
8956		 * @return {Object} Name/value lookup map for non empty elements.
8957		 */
8958		self.getNonEmptyElements = function() {
8959			return nonEmptyElementsMap;
8960		};
8961
8962		/**
8963		 * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
8964		 *
8965		 * @method getWhiteSpaceElements
8966		 * @return {Object} Name/value lookup map for white space elements.
8967		 */
8968		self.getWhiteSpaceElements = function() {
8969			return whiteSpaceElementsMap;
8970		};
8971
8972		/**
8973		 * Returns a map with special elements. These are elements that needs to be parsed
8974		 * in a special way such as script, style, textarea etc. The map object values
8975		 * are regexps used to find the end of the element.
8976		 *
8977		 * @method getSpecialElements
8978		 * @return {Object} Name/value lookup map for special elements.
8979		 */
8980		self.getSpecialElements = function() {
8981			return specialElements;
8982		};
8983
8984		/**
8985		 * Returns true/false if the specified element and it's child is valid or not
8986		 * according to the schema.
8987		 *
8988		 * @method isValidChild
8989		 * @param {String} name Element name to check for.
8990		 * @param {String} child Element child to verify.
8991		 * @return {Boolean} True/false if the element is a valid child of the specified parent.
8992		 */
8993		self.isValidChild = function(name, child) {
8994			var parent = children[name];
8995
8996			return !!(parent && parent[child]);
8997		};
8998
8999		/**
9000		 * Returns true/false if the specified element name and optional attribute is
9001		 * valid according to the schema.
9002		 *
9003		 * @method isValid
9004		 * @param {String} name Name of element to check.
9005		 * @param {String} attr Optional attribute name to check for.
9006		 * @return {Boolean} True/false if the element and attribute is valid.
9007		 */
9008		self.isValid = function(name, attr) {
9009			var attrPatterns, i, rule = getElementRule(name);
9010
9011			// Check if it's a valid element
9012			if (rule) {
9013				if (attr) {
9014					// Check if attribute name exists
9015					if (rule.attributes[attr]) {
9016						return true;
9017					}
9018
9019					// Check if attribute matches a regexp pattern
9020					attrPatterns = rule.attributePatterns;
9021					if (attrPatterns) {
9022						i = attrPatterns.length;
9023						while (i--) {
9024							if (attrPatterns[i].pattern.test(name)) {
9025								return true;
9026							}
9027						}
9028					}
9029				} else {
9030					return true;
9031				}
9032			}
9033
9034			// No match
9035			return false;
9036		};
9037
9038		/**
9039		 * Returns true/false if the specified element is valid or not
9040		 * according to the schema.
9041		 *
9042		 * @method getElementRule
9043		 * @param {String} name Element name to check for.
9044		 * @return {Object} Element object or undefined if the element isn't valid.
9045		 */
9046		self.getElementRule = getElementRule;
9047
9048		/**
9049		 * Returns an map object of all custom elements.
9050		 *
9051		 * @method getCustomElements
9052		 * @return {Object} Name/value map object of all custom elements.
9053		 */
9054		self.getCustomElements = function() {
9055			return customElementsMap;
9056		};
9057
9058		/**
9059		 * Parses a valid elements string and adds it to the schema. The valid elements
9060		 * format is for example "element[attr=default|otherattr]".
9061		 * Existing rules will be replaced with the ones specified, so this extends the schema.
9062		 *
9063		 * @method addValidElements
9064		 * @param {String} valid_elements String in the valid elements format to be parsed.
9065		 */
9066		self.addValidElements = addValidElements;
9067
9068		/**
9069		 * Parses a valid elements string and sets it to the schema. The valid elements
9070		 * format is for example "element[attr=default|otherattr]".
9071		 * Existing rules will be replaced with the ones specified, so this extends the schema.
9072		 *
9073		 * @method setValidElements
9074		 * @param {String} valid_elements String in the valid elements format to be parsed.
9075		 */
9076		self.setValidElements = setValidElements;
9077
9078		/**
9079		 * Adds custom non HTML elements to the schema.
9080		 *
9081		 * @method addCustomElements
9082		 * @param {String} custom_elements Comma separated list of custom elements to add.
9083		 */
9084		self.addCustomElements = addCustomElements;
9085
9086		/**
9087		 * Parses a valid children string and adds them to the schema structure. The valid children
9088		 * format is for example: "element[child1|child2]".
9089		 *
9090		 * @method addValidChildren
9091		 * @param {String} valid_children Valid children elements string to parse
9092		 */
9093		self.addValidChildren = addValidChildren;
9094
9095		self.elements = elements;
9096	};
9097});
9098
9099// Included from: js/tinymce/classes/html/SaxParser.js
9100
9101/**
9102 * SaxParser.js
9103 *
9104 * Copyright, Moxiecode Systems AB
9105 * Released under LGPL License.
9106 *
9107 * License: http://www.tinymce.com/license
9108 * Contributing: http://www.tinymce.com/contributing
9109 */
9110
9111/*eslint max-depth:[2, 9] */
9112
9113/**
9114 * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
9115 * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
9116 * and attributes that doesn't fit the schema if the validate setting is enabled.
9117 *
9118 * @example
9119 * var parser = new tinymce.html.SaxParser({
9120 *     validate: true,
9121 *
9122 *     comment: function(text) {
9123 *         console.log('Comment:', text);
9124 *     },
9125 *
9126 *     cdata: function(text) {
9127 *         console.log('CDATA:', text);
9128 *     },
9129 *
9130 *     text: function(text, raw) {
9131 *         console.log('Text:', text, 'Raw:', raw);
9132 *     },
9133 *
9134 *     start: function(name, attrs, empty) {
9135 *         console.log('Start:', name, attrs, empty);
9136 *     },
9137 *
9138 *     end: function(name) {
9139 *         console.log('End:', name);
9140 *     },
9141 *
9142 *     pi: function(name, text) {
9143 *         console.log('PI:', name, text);
9144 *     },
9145 *
9146 *     doctype: function(text) {
9147 *         console.log('DocType:', text);
9148 *     }
9149 * }, schema);
9150 * @class tinymce.html.SaxParser
9151 * @version 3.4
9152 */
9153define("tinymce/html/SaxParser", [
9154	"tinymce/html/Schema",
9155	"tinymce/html/Entities",
9156	"tinymce/util/Tools"
9157], function(Schema, Entities, Tools) {
9158	var each = Tools.each;
9159
9160	/**
9161	 * Returns the index of the end tag for a specific start tag. This can be
9162	 * used to skip all children of a parent element from being processed.
9163	 *
9164	 * @private
9165	 * @method findEndTag
9166	 * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements.
9167	 * @param {String} html HTML string to find the end tag in.
9168	 * @param {Number} startIndex Indext to start searching at should be after the start tag.
9169	 * @return {Number} Index of the end tag.
9170	 */
9171	function findEndTag(schema, html, startIndex) {
9172		var count = 1, index, matches, tokenRegExp, shortEndedElements;
9173
9174		shortEndedElements = schema.getShortEndedElements();
9175		tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
9176		tokenRegExp.lastIndex = index = startIndex;
9177
9178		while ((matches = tokenRegExp.exec(html))) {
9179			index = tokenRegExp.lastIndex;
9180
9181			if (matches[1] === '/') { // End element
9182				count--;
9183			} else if (!matches[1]) { // Start element
9184				if (matches[2] in shortEndedElements) {
9185					continue;
9186				}
9187
9188				count++;
9189			}
9190
9191			if (count === 0) {
9192				break;
9193			}
9194		}
9195
9196		return index;
9197	}
9198
9199	/**
9200	 * Constructs a new SaxParser instance.
9201	 *
9202	 * @constructor
9203	 * @method SaxParser
9204	 * @param {Object}
9204 settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
9205	 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
9206	 */
9207	function SaxParser(settings, schema) {
9208		var self = this;
9209
9210		function noop() {}
9211
9212		settings = settings || {};
9213		self.schema = schema = schema || new Schema();
9214
9215		if (settings.fix_self_closing !== false) {
9216			settings.fix_self_closing = true;
9217		}
9218
9219		// Add handler functions from settings and setup default handlers
9220		each('comment cdata text start end pi doctype'.split(' '), function(name) {
9221			if (name) {
9222				self[name] = settings[name] || noop;
9223			}
9224		});
9225
9226		/**
9227		 * Parses the specified HTML string and executes the callbacks for each item it finds.
9228		 *
9229		 * @example
9230		 * new SaxParser({...}).parse('<b>text</b>');
9231		 * @method parse
9232		 * @param {String} html Html string to sax parse.
9233		 */
9234		self.parse = function(html) {
9235			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
9236			var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
9237			var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
9238			var attributesRequired, attributesDefault, attributesForced;
9239			var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
9240			var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
9241			var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
9242
9243			function processEndTag(name) {
9244				var pos, i;
9245
9246				// Find position of parent of the same type
9247				pos = stack.length;
9248				while (pos--) {
9249					if (stack[pos].name === name) {
9250						break;
9251					}
9252				}
9253
9254				// Found parent
9255				if (pos >= 0) {
9256					// Close all the open elements
9257					for (i = stack.length - 1; i >= pos; i--) {
9258						name = stack[i];
9259
9260						if (name.valid) {
9261							self.end(name.name);
9262						}
9263					}
9264
9265					// Remove the open elements from the stack
9266					stack.length = pos;
9267				}
9268			}
9269
9270			function parseAttribute(match, name, value, val2, val3) {
9271				var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
9272
9273				name = name.toLowerCase();
9274				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
9275
9276				// Validate name and value pass through all data- attributes
9277				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
9278					attrRule = validAttributesMap[name];
9279
9280					// Find rule by pattern matching
9281					if (!attrRule && validAttributePatterns) {
9282						i = validAttributePatterns.length;
9283						while (i--) {
9284							attrRule = validAttributePatterns[i];
9285							if (attrRule.pattern.test(name)) {
9286								break;
9287							}
9288						}
9289
9290						// No rule matched
9291						if (i === -1) {
9292							attrRule = null;
9293						}
9294					}
9295
9296					// No attribute rule found
9297					if (!attrRule) {
9298						return;
9299					}
9300
9301					// Validate value
9302					if (attrRule.validValues && !(value in attrRule.validValues)) {
9303						return;
9304					}
9305				}
9306
9307				// Block any javascript: urls or non image data uris
9308				if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
9309					var uri = value.replace(trimRegExp, '');
9310
9311					try {
9312						// Might throw malformed URI sequence
9313						uri = decodeURIComponent(uri);
9314					} catch (ex) {
9315						// Fallback to non UTF-8 decoder
9316						uri = unescape(uri);
9317					}
9318
9319					if (scriptUriRegExp.test(uri)) {
9320						return;
9321					}
9322
9323					if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
9324						return;
9325					}
9326				}
9327
9328				// Add attribute to list and map
9329				attrList.map[name] = value;
9330				attrList.push({
9331					name: name,
9332					value: value
9333				});
9334			}
9335
9336			// Precompile RegExps and map objects
9337			tokenRegExp = new RegExp('<(?:' +
9338				'(?:!--([\\w\\W]*?)-->)|' + // Comment
9339				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
9340				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
9341				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
9342				'(?:\\/([^>]+)>)|' + // End element
9343				'(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
9344			')', 'g');
9345
9346			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
9347
9348			// Setup lookup tables for empty elements and boolean attributes
9349			shortEndedElements = schema.getShortEndedElements();
9350			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
9351			fillAttrsMap = schema.getBoolAttrs();
9352			validate = settings.validate;
9353			removeInternalElements = settings.remove_internals;
9354			fixSelfClosing = settings.fix_self_closing;
9355			specialElements = schema.getSpecialElements();
9356
9357			while ((matches = tokenRegExp.exec(html))) {
9358				// Text
9359				if (index < matches.index) {
9360					self.text(decode(html.substr(index, matches.index - index)));
9361				}
9362
9363				if ((value = matches[6])) { // End element
9364					value = value.toLowerCase();
9365
9366					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
9367					if (value.charAt(0) === ':') {
9368						value = value.substr(1);
9369					}
9370
9371					processEndTag(value);
9372				} else if ((value = matches[7])) { // Start element
9373					value = value.toLowerCase();
9374
9375					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
9376					if (value.charAt(0) === ':') {
9377						value = value.substr(1);
9378					}
9379
9380					isShortEnded = value in shortEndedElements;
9381
9382					// Is self closing tag for example an <li> after an open <li>
9383					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
9384						processEndTag(value);
9385					}
9386
9387					// Validate element
9388					if (!validate || (elementRule = schema.getElementRule(value))) {
9389						isValidElement = true;
9390
9391						// Grab attributes map and patters when validation is enabled
9392						if (validate) {
9393							validAttributesMap = elementRule.attributes;
9394							validAttributePatterns = elementRule.attributePatterns;
9395						}
9396
9397						// Parse attributes
9398						if ((attribsValue = matches[8])) {
9399							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
9400
9401							// If the element has internal attributes then remove it if we are told to do so
9402							if (isInternalElement && removeInternalElements) {
9403								isValidElement = false;
9404							}
9405
9406							attrList = [];
9407							attrList.map = {};
9408
9409							attribsValue.replace(attrRegExp, parseAttribute);
9410						} else {
9411							attrList = [];
9412							attrList.map = {};
9413						}
9414
9415						// Process attributes if validation is enabled
9416						if (validate && !isInternalElement) {
9417							attributesRequired = elementRule.attributesRequired;
9418							attributesDefault = elementRule.attributesDefault;
9419							attributesForced = elementRule.attributesForced;
9420							anyAttributesRequired = elementRule.removeEmptyAttrs;
9421
9422							// Check if any attribute exists
9423							if (anyAttributesRequired && !attrList.length) {
9424								isValidElement = false;
9425							}
9426
9427							// Handle forced attributes
9428							if (attributesForced) {
9429								i = attributesForced.length;
9430								while (i--) {
9431									attr = attributesForced[i];
9432									name = attr.name;
9433									attrValue = attr.value;
9434
9435									if (attrValue === '{$uid}') {
9436										attrValue = 'mce_' + idCount++;
9437									}
9438
9439									attrList.map[name] = attrValue;
9440									attrList.push({name: name, value: attrValue});
9441								}
9442							}
9443
9444							// Handle default attributes
9445							if (attributesDefault) {
9446								i = attributesDefault.length;
9447								while (i--) {
9448									attr = attributesDefault[i];
9449									name = attr.name;
9450
9451									if (!(name in attrList.map)) {
9452										attrValue = attr.value;
9453
9454										if (attrValue === '{$uid}') {
9455											attrValue = 'mce_' + idCount++;
9456										}
9457
9458										attrList.map[name] = attrValue;
9459										attrList.push({name: name, value: attrValue});
9460									}
9461								}
9462							}
9463
9464							// Handle required attributes
9465							if (attributesRequired) {
9466								i = attributesRequired.length;
9467								while (i--) {
9468									if (attributesRequired[i] in attrList.map) {
9469										break;
9470									}
9471								}
9472
9473								// None of the required attributes where found
9474								if (i === -1) {
9475									isValidElement = false;
9476								}
9477							}
9478
9479							// Invalidate element if it's marked as bogus
9480							if ((attr = attrList.map['data-mce-bogus'])) {
9481								if (attr === 'all') {
9482									index = findEndTag(schema, html, tokenRegExp.lastIndex);
9483									tokenRegExp.lastIndex = index;
9484									continue;
9485								}
9486
9487								isValidElement = false;
9488							}
9489						}
9490
9491						if (isValidElement) {
9492							self.start(value, attrList, isShortEnded);
9493						}
9494					} else {
9495						isValidElement = false;
9496					}
9497
9498					// Treat script, noscript and style a bit different since they may include code that looks like elements
9499					if ((endRegExp = specialElements[value])) {
9500						endRegExp.lastIndex = index = matches.index + matches[0].length;
9501
9502						if ((matches = endRegExp.exec(html))) {
9503							if (isValidElement) {
9504								text = html.substr(index, matches.index - index);
9505							}
9506
9507							index = matches.index + matches[0].length;
9508						} else {
9509							text = html.substr(index);
9510							index = html.length;
9511						}
9512
9513						if (isValidElement) {
9514							if (text.length > 0) {
9515								self.text(text, true);
9516							}
9517
9518							self.end(value);
9519						}
9520
9521						tokenRegExp.lastIndex = index;
9522						continue;
9523					}
9524
9525					// Push value on to stack
9526					if (!isShortEnded) {
9527						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
9528							stack.push({name: value, valid: isValidElement});
9529						} else if (isValidElement) {
9530							self.end(value);
9531						}
9532					}
9533				} else if ((value = matches[1])) { // Comment
9534					// Padd comment value to avoid browsers from parsing invalid comments as HTML
9535					if (value.charAt(0) === '>') {
9536						value = ' ' + value;
9537					}
9538
9539					if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
9540						value = ' ' + value;
9541					}
9542
9543					self.comment(value);
9544				} else if ((value = matches[2])) { // CDATA
9545					self.cdata(value);
9546				} else if ((value = matches[3])) { // DOCTYPE
9547					self.doctype(value);
9548				} else if ((value = matches[4])) { // PI
9549					self.pi(value, matches[5]);
9550				}
9551
9552				index = matches.index + matches[0].length;
9553			}
9554
9555			// Text
9556			if (index < html.length) {
9557				self.text(decode(html.substr(index)));
9558			}
9559
9560			// Close any open elements
9561			for (i = stack.length - 1; i >= 0; i--) {
9562				value = stack[i];
9563
9564				if (value.valid) {
9565					self.end(value.name);
9566				}
9567			}
9568		};
9569	}
9570
9571	SaxParser.findEndTag = findEndTag;
9572
9573	return SaxParser;
9574});
9575
9576// Included from: js/tinymce/classes/html/DomParser.js
9577
9578/**
9579 * DomParser.js
9580 *
9581 * Copyright, Moxiecode Systems AB
9582 * Released under LGPL License.
9583 *
9584 * License: http://www.tinymce.com/license
9585 * Contributing: http://www.tinymce.com/contributing
9586 */
9587
9588/**
9589 * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
9590 * sure that the node tree is valid according to the specified schema.
9591 * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
9592 *
9593 * @example
9594 * var parser = new tinymce.html.DomParser({validate: true}, schema);
9595 * var rootNode = parser.parse('<h1>content</h1>');
9596 *
9597 * @class tinymce.html.DomParser
9598 * @version 3.4
9599 */
9600define("tinymce/html/DomParser", [
9601	"tinymce/html/Node",
9602	"tinymce/html/Schema",
9603	"tinymce/html/SaxParser",
9604	"tinymce/util/Tools"
9605], function(Node, Schema, SaxParser, Tools) {
9606	var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
9607
9608	/**
9609	 * Constructs a new DomParser instance.
9610	 *
9611	 * @constructor
9612	 * @method DomParser
9613	 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
9614	 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
9615	 */
9616	return function(settings, schema) {
9617		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
9618
9619		settings = settings || {};
9620		settings.validate = "validate" in settings ? settings.validate : true;
9621		settings.root_name = settings.root_name || 'body';
9622		self.schema = schema = schema || new Schema();
9623
9624		function fixInvalidChildren(nodes) {
9625			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
9626			var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
9627
9628			nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
9629			nonEmptyElements = schema.getNonEmptyElements();
9630			textBlockElements = schema.getTextBlockElements();
9631
9632			for (ni = 0; ni < nodes.length; ni++) {
9633				node = nodes[ni];
9634
9635				// Already removed or fixed
9636				if (!node.parent || node.fixed) {
9637					continue;
9638				}
9639
9640				// If the invalid element is a text block and the text block is within a parent LI element
9641				// Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
9642				if (textBlockElements[node.name] && node.parent.name == 'li') {
9643					// Move sibling text blocks after LI element
9644					sibling = node.next;
9645					while (sibling) {
9646						if (textBlockElements[sibling.name]) {
9647							sibling.name = 'li';
9648							sibling.fixed = true;
9649							node.parent.insert(sibling, node.parent);
9650						} else {
9651							break;
9652						}
9653
9654						sibling = sibling.next;
9655					}
9656
9657					// Unwrap current text block
9658					node.unwrap(node);
9659					continue;
9660				}
9661
9662				// Get list of all parent nodes until we find a valid parent to stick the child into
9663				parents = [node];
9664				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
9665					!nonSplitableElements[parent.name]; parent = parent.parent) {
9666					parents.push(parent);
9667				}
9668
9669				// Found a suitable parent
9670				if (parent && parents.length > 1) {
9671					// Reverse the array since it makes looping easier
9672					parents.reverse();
9673
9674					// Clone the related parent and insert that after the moved node
9675					newParent = currentNode = self.filterNode(parents[0].clone());
9676
9677					// Start cloning and moving children on the left side of the target node
9678					for (i = 0; i < parents.length - 1; i++) {
9679						if (schema.isValidChild(currentNode.name, parents[i].name)) {
9680							tempNode = self.filterNode(parents[i].clone());
9681							currentNode.append(tempNode);
9682						} else {
9683							tempNode = currentNode;
9684						}
9685
9686						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) {
9687							nextNode = childNode.next;
9688							tempNode.append(childNode);
9689							childNode = nextNode;
9690						}
9691
9692						currentNode = tempNode;
9693					}
9694
9695					if (!newParent.isEmpty(nonEmptyElements)) {
9696						parent.insert(newParent, parents[0], true);
9697						parent.insert(node, newParent);
9698					} else {
9699						parent.insert(node, parents[0], true);
9700					}
9701
9702					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
9703					parent = parents[0];
9704					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
9705						parent.empty().remove();
9706					}
9707				} else if (node.parent) {
9708					// If it's an LI try to find a UL/OL for it or wrap it
9709					if (node.name === 'li') {
9710						sibling = node.prev;
9711						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
9712							sibling.append(node);
9713							continue;
9714						}
9715
9716						sibling = node.next;
9717						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
9718							sibling.insert(node, sibling.firstChild, true);
9719							continue;
9720						}
9721
9722						node.wrap(self.filterNode(new Node('ul', 1)));
9723						continue;
9724					}
9725
9726					// Try wrapping the element in a DIV
9727					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
9728						node.wrap(self.filterNode(new Node('div', 1)));
9729					} else {
9730						// We failed wrapping it, then remove or unwrap it
9731						if (node.name === 'style' || node.name === 'script') {
9732							node.empty().remove();
9733						} else {
9734							node.unwrap();
9735						}
9736					}
9737				}
9738			}
9739		}
9740
9741		/**
9742		 * Runs the specified node though the element and attributes filters.
9743		 *
9744		 * @method filterNode
9745		 * @param {tinymce.html.Node} Node the node to run filters on.
9746		 * @return {tinymce.html.Node} The passed in node.
9747		 */
9748		self.filterNode = function(node) {
9749			var i, name, list;
9750
9751			// Run element filters
9752			if (name in nodeFilters) {
9753				list = matchedNodes[name];
9754
9755				if (list) {
9756					list.push(node);
9757				} else {
9758					matchedNodes[name] = [node];
9759				}
9760			}
9761
9762			// Run attribute filters
9763			i = attributeFilters.length;
9764			while (i--) {
9765				name = attributeFilters[i].name;
9766
9767				if (name in node.attributes.map) {
9768					list = matchedAttributes[name];
9769
9770					if (list) {
9771						list.push(node);
9772					} else {
9773						matchedAttributes[name] = [node];
9774					}
9775				}
9776			}
9777
9778			return node;
9779		};
9780
9781		/**
9782		 * Adds a node filter function to the parser, the parser will collect the specified nodes by name
9783		 * and then execute the callback ones it has finished parsing the document.
9784		 *
9785		 * @example
9786		 * parser.addNodeFilter('p,h1', function(nodes, name) {
9787		 *		for (var i = 0; i < nodes.length; i++) {
9788		 *			console.log(nodes[i].name);
9789		 *		}
9790		 * });
9791		 * @method addNodeFilter
9792		 * @method {String} name Comma separated list of nodes to collect.
9793		 * @param {function} callback Callback function to execute once it has collected nodes.
9794		 */
9795		self.addNodeFilter = function(name, callback) {
9796			each(explode(name), function(name) {
9797				var list = nodeFilters[name];
9798
9799				if (!list) {
9800					nodeFilters[name] = list = [];
9801				}
9802
9803				list.push(callback);
9804			});
9805		};
9806
9807		/**
9808		 * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
9809		 * and then execute the callback ones it has finished parsing the document.
9810		 *
9811		 * @example
9812		 * parser.addAttributeFilter('src,href', function(nodes, name) {
9813		 *		for (var i = 0; i < nodes.length; i++) {
9814		 *			console.log(nodes[i].name);
9815		 *		}
9816		 * });
9817		 * @method addAttributeFilter
9818		 * @method {String} name Comma separated list of nodes to collect.
9819		 * @param {function} callback Callback function to execute once it has collected nodes.
9820		 */
9821		self.addAttributeFilter = function(name, callback) {
9822			each(explode(name), function(name) {
9823				var i;
9824
9825				for (i = 0; i < attributeFilters.length; i++) {
9826					if (attributeFilters[i].name === name) {
9827						attributeFilters[i].callbacks.push(callback);
9828						return;
9829					}
9830				}
9831
9832				attributeFilters.push({name: name, callbacks: [callback]});
9833			});
9834		};
9835
9836		/**
9837		 * Parses the specified HTML string into a DOM like node tree and returns the result.
9838		 *
9839		 * @example
9840		 * var rootNode = new DomParser({...}).parse('<b>text</b>');
9841		 * @method parse
9842		 * @param {String} html Html string to sax parse.
9843		 * @param {Object} args Optional args object that gets passed to all filter functions.
9844		 * @return {tinymce.html.Node} Root node containing the tree.
9845		 */
9846		self.parse = function(html, args) {
9847			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
9848			var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
9849			var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
9850			var children, nonEmptyElements, rootBlockName;
9851
9852			args = args || {};
9853			matchedNodes = {};
9854			matchedAttributes = {};
9855			blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
9856			nonEmptyElements = schema.getNonEmptyElements();
9857			children = schema.children;
9858			validate = settings.validate;
9859			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
9860
9861			whiteSpaceElements = schema.getWhiteSpaceElements();
9862			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
9863			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
9864			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
9865			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
9866
9867			function addRootBlocks() {
9868				var node = rootNode.firstChild, next, rootBlockNode;
9869
9870				// Removes whitespace at beginning and end of block so:
9871				// <p> x </p> -> <p>x</p>
9872				function trim(rootBlockNode) {
9873					if (rootBlockNode) {
9874						node = rootBlockNode.firstChild;
9875						if (node && node.type == 3) {
9876							node.value = node.value.replace(startWhiteSpaceRegExp, '');
9877						}
9878
9879						node = rootBlockNode.lastChild;
9880						if (node && node.type == 3) {
9881							node.value = node.value.replace(endWhiteSpaceRegExp, '');
9882						}
9883					}
9884				}
9885
9886				// Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
9887				if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
9888					return;
9889				}
9890
9891				while (node) {
9892					next = node.next;
9893
9894					if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
9895						!blockElements[node.name] && !node.attr('data-mce-type'))) {
9896						if (!rootBlockNode) {
9897							// Create a new root block element
9898							rootBlockNode = createNode(rootBlockName, 1);
9899							rootBlockNode.attr(settings.forced_root_block_attrs);
9900							rootNode.insert(rootBlockNode, node);
9901							rootBlockNode.append(node);
9902						} else {
9903							rootBlockNode.append(node);
9904						}
9905					} else {
9906						trim(rootBlockNode);
9907						rootBlockNode = null;
9908					}
9909
9910					node = next;
9911				}
9912
9913				trim(rootBlockNode);
9914			}
9915
9916			function createNode(name, type) {
9917				var node = new Node(name, type), list;
9918
9919				if (name in nodeFilters) {
9920					list = matchedNodes[name];
9921
9922					if (list) {
9923						list.push(node);
9924					} else {
9925						matchedNodes[name] = [node];
9926					}
9927				}
9928
9929				return node;
9930			}
9931
9932			function removeWhitespaceBefore(node) {
9933				var textNode, textVal, sibling;
9934
9935				for (textNode = node.prev; textNode && textNode.type === 3;) {
9936					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
9937
9938					if (textVal.length > 0) {
9939						textNode.value = textVal;
9940						textNode = textNode.prev;
9941					} else {
9942						sibling = textNode.prev;
9943						textNode.remove();
9944						textNode = sibling;
9945					}
9946				}
9947			}
9948
9949			function cloneAndExcludeBlocks(input) {
9950				var name, output = {};
9951
9952				for (name in input) {
9953					if (name !== 'li' && name != 'p') {
9954						output[name] = input[name];
9955					}
9956				}
9957
9958				return output;
9959			}
9960
9961			parser = new SaxParser({
9962				validate: validate,
9963				allow_script_urls: settings.allow_script_urls,
9964				allow_conditional_comments: settings.allow_conditional_comments,
9965
9966				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
9967				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
9968
9969				cdata: function(text) {
9970					node.append(createNode('#cdata', 4)).value = text;
9971				},
9972
9973				text: function(text, raw) {
9974					var textNode;
9975
9976					// Trim all redundant whitespace on non white space elements
9977					if (!isInWhiteSpacePreservedElement) {
9978						text = text.replace(allWhiteSpaceRegExp, ' ');
9979
9980						if (node.lastChild && blockElements[node.lastChild.name]) {
9981							text = text.replace(startWhiteSpaceRegExp, '');
9982						}
9983					}
9984
9985					// Do we need to create the node
9986					if (text.length !== 0) {
9987						textNode = createNode('#text', 3);
9988						textNode.raw = !!raw;
9989						node.append(textNode).value = text;
9990					}
9991				},
9992
9993				comment: function(text) {
9994					node.append(createNode('#comment', 8)).value = text;
9995				},
9996
9997				pi: function(name, text) {
9998					node.append(createNode(name, 7)).value = text;
9999					removeWhitespaceBefore(node);
10000				},
10001
10002				doctype: function(text) {
10003					var newNode;
10004
10005					newNode = node.append(createNode('#doctype', 10));
10006					newNode.value = text;
10007					removeWhitespaceBefore(node);
10008				},
10009
10010				start: function(name, attrs, empty) {
10011					var newNode, attrFiltersLen, elementRule, attrName, parent;
10012
10013					elementRule = validate ? schema.getElementRule(name) : {};
10014					if (elementRule) {
10015						newNode = createNode(elementRule.outputName || name, 1);
10016						newNode.attributes = attrs;
10017						newNode.shortEnded = empty;
10018
10019						node.append(newNode);
10020
10021						// Check if node is valid child of the parent node is the child is
10022						// unknown we don't collect it since it's probably a custom element
10023						parent = children[node.name];
10024						if (parent && children[newNode.name] && !parent[newNode.name]) {
10025							invalidChildren.push(newNode);
10026						}
10027
10028						attrFiltersLen = attributeFilters.length;
10029						while (attrFiltersLen--) {
10030							attrName = attributeFilters[attrFiltersLen].name;
10031
10032							if (attrName in attrs.map) {
10033								list = matchedAttributes[attrName];
10034
10035								if (list) {
10036									list.push(newNode);
10037								} else {
10038									matchedAttributes[attrName] = [newNode];
10039								}
10040							}
10041						}
10042
10043						// Trim whitespace before block
10044						if (blockElements[name]) {
10045							removeWhitespaceBefore(newNode);
10046						}
10047
10048						// Change current node if the element wasn't empty i.e not <br /> or <img />
10049						if (!empty) {
10050							node = newNode;
10051						}
10052
10053						// Check if we are inside a whitespace preserved element
10054						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
10055							isInWhiteSpacePreservedElement = true;
10056						}
10057					}
10058				},
10059
10060				end: function(name) {
10061					var textNode, elementRule, text, sibling, tempNode;
10062
10063					elementRule = validate ? schema.getElementRule(name) : {};
10064					if (elementRule) {
10065						if (blockElements[name]) {
10066							if (!isInWhiteSpacePreservedElement) {
10067								// Trim whitespace of the first node in a block
10068								textNode = node.firstChild;
10069								if (textNode && textNode.type === 3) {
10070									text = textNode.value.replace(startWhiteSpaceRegExp, '');
10071
10072									// Any characters left after trim or should we remove it
10073									if (text.length > 0) {
10074										textNode.value = text;
10075										textNode = textNode.next;
10076									} else {
10077										sibling = textNode.next;
10078										textNode.remove();
10079										textNode = sibling;
10080
10081										// Remove any pure whitespace siblings
10082										while (textNode && textNode.type === 3) {
10083											text = textNode.value;
10084											sibling = textNode.next;
10085
10086											if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
10087												textNode.remove();
10088												textNode = sibling;
10089											}
10090
10091											textNode = sibling;
10092										}
10093									}
10094								}
10095
10096								// Trim whitespace of the last node in a block
10097								textNode = node.lastChild;
10098								if (textNode && textNode.type === 3) {
10099									text = textNode.value.replace(endWhiteSpaceRegExp, '');
10100
10101									// Any characters left after trim or should we remove it
10102									if (text.length > 0) {
10103										textNode.value = text;
10104										textNode = textNode.prev;
10105									} else {
10106										sibling = textNode.prev;
10107										textNode.remove();
10108										textNode = sibling;
10109
10110										// Remove any pure whitespace siblings
10111										while (textNode && textNode.type === 3) {
10112											text = textNode.value;
10113											sibling = textNode.prev;
10114
10115											if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
10116												textNode.remove();
10117												textNode = sibling;
10118											}
10119
10120											textNode = sibling;
10121										}
10122									}
10123								}
10124							}
10125
10126							// Trim start white space
10127							// Removed due to: #5424
10128							/*textNode = node.prev;
10129							if (textNode && textNode.type === 3) {
10130								text = textNode.value.replace(startWhiteSpaceRegExp, '');
10131
10132								if (text.length > 0)
10133									textNode.value = text;
10134								else
10135									textNode.remove();
10136							}*/
10137						}
10138
10139						// Check if we exited a whitespace preserved element
10140						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
10141							isInWhiteSpacePreservedElement = false;
10142						}
10143
10144						// Handle empty nodes
10145						if (elementRule.removeEmpty || elementRule.paddEmpty) {
10146							if (node.isEmpty(nonEmptyElements)) {
10147								if (elementRule.paddEmpty) {
10148									node.empty().append(new Node('#text', '3')).value = '\u00a0';
10149								} else {
10150									// Leave nodes that have a name like <a name="name">
10151									if (!node.attributes.map.name && !node.attributes.map.id) {
10152										tempNode = node.parent;
10153
10154										if (blockElements[node.name]) {
10155											node.empty().remove();
10156										} else {
10157											node.unwrap();
10158										}
10159
10160										node = tempNode;
10161										return;
10162									}
10163								}
10164							}
10165						}
10166
10167						node = node.parent;
10168					}
10169				}
10170			}, schema);
10171
10172			rootNode = node = new Node(args.context || settings.root_name, 11);
10173
10174			parser.parse(html);
10175
10176			// Fix invalid children or report invalid children in a contextual parsing
10177			if (validate && invalidChildren.length) {
10178				if (!args.context) {
10179					fixInvalidChildren(invalidChildren);
10180				} else {
10181					args.invalid = true;
10182				}
10183			}
10184
10185			// Wrap nodes in the root into block elements if the root is body
10186			if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
10187				addRootBlocks();
10188			}
10189
10190			// Run filters only when the contents is valid
10191			if (!args.invalid) {
10192				// Run node filters
10193				for (name in matchedNodes) {
10194					list = nodeFilters[name];
10195					nodes = matchedNodes[name];
10196
10197					// Remove already removed children
10198					fi = nodes.length;
10199					while (fi--) {
10200						if (!nodes[fi].parent) {
10201							nodes.splice(fi, 1);
10202						}
10203					}
10204
10205					for (i = 0, l = list.length; i < l; i++) {
10206						list[i](nodes, name, args);
10207					}
10208				}
10209
10210				// Run attribute filters
10211				for (i = 0, l = attributeFilters.length; i < l; i++) {
10212					list = attributeFilters[i];
10213
10214					if (list.name in matchedAttributes) {
10215						nodes = matchedAttributes[list.name];
10216
10217						// Remove already removed children
10218						fi = nodes.length;
10219						while (fi--) {
10220							if (!nodes[fi].parent) {
10221								nodes.splice(fi, 1);
10222							}
10223						}
10224
10225						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
10226							list.callbacks[fi](nodes, list.name, args);
10227						}
10228					}
10229				}
10230			}
10231
10232			return rootNode;
10233		};
10234
10235		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
10236		// make it possible to place the caret inside empty blocks. This logic tries to remove
10237		// these elements and keep br elements that where intended to be there intact
10238		if (settings.remove_trailing_brs) {
10239			self.addNodeFilter('br', function(nodes) {
10240				var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
10241				var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
10242				var elementRule, textNode;
10243
10244				// Remove brs from body element as well
10245				blockElements.body = 1;
10246
10247				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
10248				for (i = 0; i < l; i++) {
10249					node = nodes[i];
10250					parent = node.parent;
10251
10252					if (blockElements[node.parent.name] && node === parent.lastChild) {
10253						// Loop all nodes to the left of the current node and check for other BR elements
10254						// excluding bookmarks since they are invisible
10255						prev = node.prev;
10256						while (prev) {
10257							prevName = prev.name;
10258
10259							// Ignore bookmarks
10260							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
10261								// Found a non BR element
10262								if (prevName !== "br") {
10263									break;
10264								}
10265
10266								// Found another br it's a <br><br> structure then don't remove anything
10267								if (prevName === 'br') {
10268									node = null;
10269									break;
10270								}
10271							}
10272
10273							prev = prev.prev;
10274						}
10275
10276						if (node) {
10277							node.remove();
10278
10279							// Is the parent to be considered empty after we removed the BR
10280							if (parent.isEmpty(nonEmptyElements)) {
10281								elementRule = schema.getElementRule(parent.name);
10282
10283								// Remove or padd the element depending on schema rule
10284								if (elementRule) {
10285									if (elementRule.removeEmpty) {
10286										parent.remove();
10287									} else if (elementRule.paddEmpty) {
10288										parent.empty().append(new Node('#text', 3)).value = '\u00a0';
10289									}
10290								}
10291							}
10292						}
10293					} else {
10294						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
10295						// so they become <p><b><i>&nbsp;</i></b></p>
10296						lastParent = node;
10297						while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
10298							lastParent = parent;
10299
10300							if (blockElements[parent.name]) {
10301								break;
10302							}
10303
10304							parent = parent.parent;
10305						}
10306
10307						if (lastParent === parent) {
10308							textNode = new Node('#text', 3);
10309							textNode.value = '\u00a0';
10310							node.replace(textNode);
10311						}
10312					}
10313				}
10314			});
10315		}
10316
10317		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
10318		if (!settings.allow_html_in_named_anchor) {
10319			self.addAttributeFilter('id,name', function(nodes) {
10320				var i = nodes.length, sibling, prevSibling, parent, node;
10321
10322				while (i--) {
10323					node = nodes[i];
10324					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
10325						parent = node.parent;
10326
10327						// Move children after current node
10328						sibling = node.lastChild;
10329						do {
10330							prevSibling = sibling.prev;
10331							parent.insert(sibling, node);
10332							sibling = prevSibling;
10333						} while (sibling);
10334					}
10335				}
10336			});
10337		}
10338
10339		if (settings.validate && schema.getValidClasses()) {
10340			self.addAttributeFilter('class', function(nodes) {
10341				var i = nodes.length, node, classList, ci, className, classValue;
10342				var validClasses = schema.getValidClasses(), validClassesMap, valid;
10343
10344				while (i--) {
10345					node = nodes[i];
10346					classList = node.attr('class').split(' ');
10347					classValue = '';
10348
10349					for (ci = 0; ci < classList.length; ci++) {
10350						className = classList[ci];
10351						valid = false;
10352
10353						validClassesMap = validClasses['*'];
10354						if (validClassesMap && validClassesMap[className]) {
10355							valid = true;
10356						}
10357
10358						validClassesMap = validClasses[node.name];
10359						if (!valid && validClassesMap && !validClassesMap[className]) {
10360							valid = true;
10361						}
10362
10363						if (valid) {
10364							if (classValue) {
10365								classValue += ' ';
10366							}
10367
10368							classValue += className;
10369						}
10370					}
10371
10372					if (!classValue.length) {
10373						classValue = null;
10374					}
10375
10376					node.attr('class', classValue);
10377				}
10378			});
10379		}
10380	};
10381});
10382
10383// Included from: js/tinymce/classes/html/Writer.js
10384
10385/**
10386 * Writer.js
10387 *
10388 * Copyright, Moxiecode Systems AB
10389 * Released under LGPL License.
10390 *
10391 * License: http://www.tinymce.com/license
10392 * Contributing: http://www.tinymce.com/contributing
10393 */
10394
10395/**
10396 * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
10397 *
10398 * @class tinymce.html.Writer
10399 * @example
10400 * var writer = new tinymce.html.Writer({indent: true});
10401 * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
10402 * console.log(writer.getContent());
10403 *
10404 * @class tinymce.html.Writer
10405 * @version 3.4
10406 */
10407define("tinymce/html/Writer", [
10408	"tinymce/html/Entities",
10409	"tinymce/util/Tools"
10410], function(Entities, Tools) {
10411	var makeMap = Tools.makeMap;
10412
10413	/**
10414	 * Constructs a new Writer instance.
10415	 *
10416	 * @constructor
10417	 * @method Writer
10418	 * @param {Object} settings Name/value settings object.
10419	 */
10420	return function(settings) {
10421		var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
10422
10423		settings = settings || {};
10424		indent = settings.indent;
10425		indentBefore = makeMap(settings.indent_before || '');
10426		indentAfter = makeMap(settings.indent_after || '');
10427		encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
10428		htmlOutput = settings.element_format == "html";
10429
10430		return {
10431			/**
10432			 * Writes the a start element such as <p id="a">.
10433			 *
10434			 * @method start
10435			 * @param {String} name Name of the element.
10436			 * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
10437			 * @param {Boolean} empty Optional empty state if the tag should end like <br />.
10438			 */
10439			start: function(name, attrs, empty) {
10440				var i, l, attr, value;
10441
10442				if (indent && indentBefore[name] && html.length > 0) {
10443					value = html[html.length - 1];
10444
10445					if (value.length > 0 && value !== '\n') {
10446						html.push('\n');
10447					}
10448				}
10449
10450				html.push('<', name);
10451
10452				if (attrs) {
10453					for (i = 0, l = attrs.length; i < l; i++) {
10454						attr = attrs[i];
10455						html.push(' ', attr.name, '="', encode(attr.value, true), '"');
10456					}
10457				}
10458
10459				if (!empty || htmlOutput) {
10460					html[html.length] = '>';
10461				} else {
10462					html[html.length] = ' />';
10463				}
10464
10465				if (empty && indent && indentAfter[name] && html.length > 0) {
10466					value = html[html.length - 1];
10467
10468					if (value.length > 0 && value !== '\n') {
10469						html.push('\n');
10470					}
10471				}
10472			},
10473
10474			/**
10475			 * Writes the a end element such as </p>.
10476			 *
10477			 * @method end
10478			 * @param {String} name Name of the element.
10479			 */
10480			end: function(name) {
10481				var value;
10482
10483				/*if (indent && indentBefore[name] && html.length > 0) {
10484					value = html[html.length - 1];
10485
10486					if (value.length > 0 && value !== '\n')
10487						html.push('\n');
10488				}*/
10489
10490				html.push('</', name, '>');
10491
10492				if (indent && indentAfter[name] && html.length > 0) {
10493					value = html[html.length - 1];
10494
10495					if (value.length > 0 && value !== '\n') {
10496						html.push('\n');
10497					}
10498				}
10499			},
10500
10501			/**
10502			 * Writes a text node.
10503			 *
10504			 * @method text
10505			 * @param {String} text String to write out.
10506			 * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
10507			 */
10508			text: function(text, raw) {
10509				if (text.length > 0) {
10510					html[html.length] = raw ? text : encode(text);
10511				}
10512			},
10513
10514			/**
10515			 * Writes a cdata node such as <![CDATA[data]]>.
10516			 *
10517			 * @method cdata
10518			 * @param {String} text String to write out inside the cdata.
10519			 */
10520			cdata: function(text) {
10521				html.push('<![CDATA[', text, ']]>');
10522			},
10523
10524			/**
10525			 * Writes a comment node such as <!-- Comment -->.
10526			 *
10527			 * @method cdata
10528			 * @param {String} text String to write out inside the comment.
10529			 */
10530			comment: function(text) {
10531				html.push('<!--', text, '-->');
10532			},
10533
10534			/**
10535			 * Writes a PI node such as <?xml attr="value" ?>.
10536			 *
10537			 * @method pi
10538			 * @param {String} name Name of the pi.
10539			 * @param {String} text String to write out inside the pi.
10540			 */
10541			pi: function(name, text) {
10542				if (text) {
10543					html.push('<?', name, ' ', text, '?>');
10544				} else {
10545					html.push('<?', name, '?>');
10546				}
10547
10548				if (indent) {
10549					html.push('\n');
10550				}
10551			},
10552
10553			/**
10554			 * Writes a doctype node such as <!DOCTYPE data>.
10555			 *
10556			 * @method doctype
10557			 * @param {String} text String to write out inside the doctype.
10558			 */
10559			doctype: function(text) {
10560				html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
10561			},
10562
10563			/**
10564			 * Resets the internal buffer if one wants to reuse the writer.
10565			 *
10566			 * @method reset
10567			 */
10568			reset: function() {
10569				html.length = 0;
10570			},
10571
10572			/**
10573			 * Returns the contents that got serialized.
10574			 *
10575			 * @method getContent
10576			 * @return {String} HTML contents that got written down.
10577			 */
10578			getContent: function() {
10579				return html.join('').replace(/\n$/, '');
10580			}
10581		};
10582	};
10583});
10584
10585// Included from: js/tinymce/classes/html/Serializer.js
10586
10587/**
10588 * Serializer.js
10589 *
10590 * Copyright, Moxiecode Systems AB
10591 * Released under LGPL License.
10592 *
10593 * License: http://www.tinymce.com/license
10594 * Contributing: http://www.tinymce.com/contributing
10595 */
10596
10597/**
10598 * This class is used to serialize down the DOM tree into a string using a Writer instance.
10599 *
10600 *
10601 * @example
10602 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
10603 * @class tinymce.html.Serializer
10604 * @version 3.4
10605 */
10606define("tinymce/html/Serializer", [
10607	"tinymce/html/Writer",
10608	"tinymce/html/Schema"
10609], function(Writer, Schema) {
10610	/**
10611	 * Constructs a new Serializer instance.
10612	 *
10613	 * @constructor
10614	 * @method Serializer
10615	 * @param {Object} settings Name/value settings object.
10616	 * @param {tinymce.html.Schema} schema Schema instance to use.
10617	 */
10618	return function(settings, schema) {
10619		var self = this, writer = new Writer(settings);
10620
10621		settings = settings || {};
10622		settings.validate = "validate" in settings ? settings.validate : true;
10623
10624		self.schema = schema = schema || new Schema();
10625		self.writer = writer;
10626
10627		/**
10628		 * Serializes the specified node into a string.
10629		 *
10630		 * @example
10631		 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
10632		 * @method serialize
10633		 * @param {tinymce.html.Node} node Node instance to serialize.
10634		 * @return {String} String with HTML based on DOM tree.
10635		 */
10636		self.serialize = function(node) {
10637			var handlers, validate;
10638
10639			validate = settings.validate;
10640
10641			handlers = {
10642				// #text
10643				3: function(node) {
10644					writer.text(node.value, node.raw);
10645				},
10646
10647				// #comment
10648				8: function(node) {
10649					writer.comment(node.value);
10650				},
10651
10652				// Processing instruction
10653				7: function(node) {
10654					writer.pi(node.name, node.value);
10655				},
10656
10657				// Doctype
10658				10: function(node) {
10659					writer.doctype(node.value);
10660				},
10661
10662				// CDATA
10663				4: function(node) {
10664					writer.cdata(node.value);
10665				},
10666
10667				// Document fragment
10668				11: function(node) {
10669					if ((node = node.firstChild)) {
10670						do {
10671							walk(node);
10672						} while ((node = node.next));
10673					}
10674				}
10675			};
10676
10677			writer.reset();
10678
10679			function walk(node) {
10680				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
10681
10682				if (!handler) {
10683					name = node.name;
10684					isEmpty = node.shortEnded;
10685					attrs = node.attributes;
10686
10687					// Sort attributes
10688					if (validate && attrs && attrs.length > 1) {
10689						sortedAttrs = [];
10690						sortedAttrs.map = {};
10691
10692						elementRule = schema.getElementRule(node.name);
10693						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
10694							attrName = elementRule.attributesOrder[i];
10695
10696							if (attrName in attrs.map) {
10697								attrValue = attrs.map[attrName];
10698								sortedAttrs.map[attrName] = attrValue;
10699								sortedAttrs.push({name: attrName, value: attrValue});
10700							}
10701						}
10702
10703						for (i = 0, l = attrs.length; i < l; i++) {
10704							attrName = attrs[i].name;
10705
10706							if (!(attrName in sortedAttrs.map)) {
10707								attrValue = attrs.map[attrName];
10708								sortedAttrs.map[attrName] = attrValue;
10709								sortedAttrs.push({name: attrName, value: attrValue});
10710							}
10711						}
10712
10713						attrs = sortedAttrs;
10714					}
10715
10716					writer.start(node.name, attrs, isEmpty);
10717
10718					if (!isEmpty) {
10719						if ((node = node.firstChild)) {
10720							do {
10721								walk(node);
10722							} while ((node = node.next));
10723						}
10724
10725						writer.end(name);
10726					}
10727				} else {
10728					handler(node);
10729				}
10730			}
10731
10732			// Serialize element and treat all non elements as fragments
10733			if (node.type == 1 && !settings.inner) {
10734				walk(node);
10735			} else {
10736				handlers[11](node);
10737			}
10738
10739			return writer.getContent();
10740		};
10741	};
10742});
10743
10744// Included from: js/tinymce/classes/dom/Serializer.js
10745
10746/**
10747 * Serializer.js
10748 *
10749 * Copyright, Moxiecode Systems AB
10750 * Released under LGPL License.
10751 *
10752 * License: http://www.tinymce.com/license
10753 * Contributing: http://www.tinymce.com/contributing
10754 */
10755
10756/**
10757 * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
10758 * more details and examples on how to use this class.
10759 *
10760 * @class tinymce.dom.Serializer
10761 */
10762define("tinymce/dom/Serializer", [
10763	"tinymce/dom/DOMUtils",
10764	"tinymce/html/DomParser",
10765	"tinymce/html/Entities",
10766	"tinymce/html/Serializer",
10767	"tinymce/html/Node",
10768	"tinymce/html/Schema",
10769	"tinymce/Env",
10770	"tinymce/util/Tools"
10771], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) {
10772	var each = Tools.each, trim = Tools.trim;
10773	var DOM = DOMUtils.DOM;
10774
10775	/**
10776	 * Constructs a new DOM serializer class.
10777	 *
10778	 * @constructor
10779	 * @method Serializer
10780	 * @param {Object} settings Serializer settings object.
10781	 * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
10782	 */
10783	return function(settings, editor) {
10784		var dom, schema, htmlParser;
10785
10786		if (editor) {
10787			dom = editor.dom;
10788			schema = editor.schema;
10789		}
10790
10791		// Default DOM and Schema if they are undefined
10792		dom = dom || DOM;
10793		schema = schema || new Schema(settings);
10794		settings.entity_encoding = settings.entity_encoding || 'named';
10795		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10796
10797		htmlParser = new DomParser(settings, schema);
10798
10799		// Convert tabindex back to elements when serializing contents
10800		htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
10801			var i = nodes.length, node;
10802
10803			while (i--) {
10804				node = nodes[i];
10805				node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
10806				node.attr(name, null);
10807			}
10808		});
10809
10810		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10811		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10812			var i = nodes.length, node, value, internalName = 'data-mce-' + name;
10813			var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10814
10815			while (i--) {
10816				node = nodes[i];
10817
10818				value = node.attributes.map[internalName];
10819				if (value !== undef) {
10820					// Set external name to internal value and remove internal
10821					node.attr(name, value.length > 0 ? value : null);
10822					node.attr(internalName, null);
10823				} else {
10824					// No internal attribute found then convert the value we have in the DOM
10825					value = node.attributes.map[name];
10826
10827					if (name === "style") {
10828						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10829					} else if (urlConverter) {
10830						value = urlConverter.call(urlConverterScope, value, name, node.name);
10831					}
10832
10833					node.attr(name, value.length > 0 ? value : null);
10834				}
10835			}
10836		});
10837
10838		// Remove internal classes mceItem<..> or mceSelected
10839		htmlParser.addAttributeFilter('class', function(nodes) {
10840			var i = nodes.length, node, value;
10841
10842			while (i--) {
10843				node = nodes[i];
10844				value = node.attr('class');
10845
10846				if (value) {
10847					value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
10848					node.attr('class', value.length > 0 ? value : null);
10849				}
10850			}
10851		});
10852
10853		// Remove bookmark elements
10854		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10855			var i = nodes.length, node;
10856
10857			while (i--) {
10858				node = nodes[i];
10859
10860				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
10861					node.remove();
10862				}
10863			}
10864		});
10865
10866		htmlParser.addNodeFilter('noscript', function(nodes) {
10867			var i = nodes.length, node;
10868
10869			while (i--) {
10870				node = nodes[i].firstChild;
10871
10872				if (node) {
10873					node.value = Entities.decode(node.value);
10874				}
10875			}
10876		});
10877
10878		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10879		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10880			var i = nodes.length, node, value, type;
10881
10882			function trim(value) {
10883				/*jshint maxlen:255 */
10884				/*eslint max-len:0 */
10885				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10886						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10887						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10888						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10889			}
10890
10891			while (i--) {
10892				node = nodes[i];
10893				value = node.firstChild ? node.firstChild.value : '';
10894
10895				if (name === "script") {
10896					// Remove mce- prefix from script elements and remove default type since the user specified
10897					// a script element without type attribute
10898					type = node.attr('type');
10899					if (type) {
10900						node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
10901					}
10902
10903					if (value.length > 0) {
10904						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10905					}
10906				} else {
10907					if (value.length > 0) {
10908						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10909					}
10910				}
10911			}
10912		});
10913
10914		// Convert comments to cdata and handle protected comments
10915		htmlParser.addNodeFilter('#comment', function(nodes) {
10916			var i = nodes.length, node;
10917
10918			while (i--) {
10919				node = nodes[i];
10920
10921				if (node.value.indexOf('[CDATA[') === 0) {
10922					node.name = '#cdata';
10923					node.type = 4;
10924					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10925				} else if (node.value.indexOf('mce:protected ') === 0) {
10926					node.name = "#text";
10927					node.type = 3;
10928					node.raw = true;
10929					node.value = unescape(node.value).substr(14);
10930				}
10931			}
10932		});
10933
10934		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10935			var i = nodes.length, node;
10936
10937			while (i--) {
10938				node = nodes[i];
10939				if (node.type === 7) {
10940					node.remove();
10941				} else if (node.type === 1) {
10942					if (name === "input" && !("type" in node.attributes.map)) {
10943						node.attr('type', 'text');
10944					}
10945				}
10946			}
10947		});
10948
10949		// Fix list elements, TODO: Replace this later
10950		if (settings.fix_list_elements) {
10951			htmlParser.addNodeFilter('ul,ol', function(nodes) {
10952				var i = nodes.length, node, parentNode;
10953
10954				while (i--) {
10955					node = nodes[i];
10956					parentNode = node.parent;
10957
10958					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10959						if (node.prev && node.prev.name === 'li') {
10960							node.prev.append(node);
10961						}
10962					}
10963				}
10964			});
10965		}
10966
10967		// Remove internal data attributes
10968		htmlParser.addAttributeFilter(
10969			'data-mce-src,data-mce-href,data-mce-style,' +
10970			'data-mce-selected,data-mce-expando,' +
10971			'data-mce-type,data-mce-resize',
10972
10973			function(nodes, name) {
10974				var i = nodes.length;
10975
10976				while (i--) {
10977					nodes[i].attr(name, null);
10978				}
10979			}
10980		);
10981
10982		// Return public methods
10983		return {
10984			/**
10985			 * Schema instance that was used to when the Serializer was constructed.
10986			 *
10987			 * @field {tinymce.html.Schema} schema
10988			 */
10989			schema: schema,
10990
10991			/**
10992			 * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
10993			 * and then execute the callback ones it has finished parsing the document.
10994			 *
10995			 * @example
10996			 * parser.addNodeFilter('p,h1', function(nodes, name) {
10997			 *		for (var i = 0; i < nodes.length; i++) {
10998			 *			console.log(nodes[i].name);
10999			 *		}
11000			 * });
11001			 * @method addNodeFilter
11002			 * @method {String} name Comma separated list of nodes to collect.
11003			 * @param {function} callback Callback function to execute once it has collected nodes.
11004			 */
11005			addNodeFilter: htmlParser.addNodeFilter,
11006
11007			/**
11008			 * Adds a attribute filter function to the parser used by the serializer, the parser will
11009			 * collect nodes that has the specified attributes
11010			 * and then execute the callback ones it has finished parsing the document.
11011			 *
11012			 * @example
11013			 * parser.addAttributeFilter('src,href', function(nodes, name) {
11014			 *		for (var i = 0; i < nodes.length; i++) {
11015			 *			console.log(nodes[i].name);
11016			 *		}
11017			 * });
11018			 * @method addAttributeFilter
11019			 * @method {String} name Comma separated list of nodes to collect.
11020			 * @param {function} callback Callback function to execute once it has collected nodes.
11021			 */
11022			addAttributeFilter: htmlParser.addAttributeFilter,
11023
11024			/**
11025			 * Serializes the specified browser DOM node into a HTML string.
11026			 *
11027			 * @method serialize
11028			 * @param {DOMNode} node DOM node to serialize.
11029			 * @param {Object} args Arguments option that gets passed to event handlers.
11030			 */
11031			serialize: function(node, args) {
11032				var self = this, impl, doc, oldDoc, htmlSerializer, content;
11033
11034				// Explorer won't clone contents of script and style and the
11035				// selected index of select elements are cleared on a clone operation.
11036				if (Env.ie && dom.select('script,style,select,map').length > 0) {
11037					content = node.innerHTML;
11038					node = node.cloneNode(false);
11039					dom.setHTML(node, content);
11040				} else {
11041					node = node.cloneNode(true);
11042				}
11043
11044				// Nodes needs to be attached to something in WebKit/Opera
11045				// This fix will make DOM ranges and make Sizzle happy!
11046				impl = node.ownerDocument.implementation;
11047				if (impl.createHTMLDocument) {
11048					// Create an empty HTML document
11049					doc = impl.createHTMLDocument("");
11050
11051					// Add the element or it's children if it's a body element to the new document
11052					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
11053						doc.body.appendChild(doc.importNode(node, true));
11054					});
11055
11056					// Grab first child or body element for serialization
11057					if (node.nodeName != 'BODY') {
11058						node = doc.body.firstChild;
11059					} else {
11060						node = doc.body;
11061					}
11062
11063					// set the new document in DOMUtils so createElement etc works
11064					oldDoc = dom.doc;
11065					dom.doc = doc;
11066				}
11067
11068				args = args || {};
11069				args.format = args.format || 'html';
11070
11071				// Don't wrap content if we want selected html
11072				if (args.selection) {
11073					args.forced_root_block = '';
11074				}
11075
11076				// Pre process
11077				if (!args.no_events) {
11078					args.node = node;
11079					self.onPreProcess(args);
11080				}
11081
11082				// Setup serializer
11083				htmlSerializer = new Serializer(settings, schema);
11084
11085				// Parse and serialize HTML
11086				args.content = htmlSerializer.serialize(
11087					htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
11088				);
11089
11090				// Replace all BOM characters for now until we can find a better solution
11091				if (!args.cleanup) {
11092					args.content = args.content.replace(/\uFEFF/g, '');
11093				}
11094
11095				// Post process
11096				if (!args.no_events) {
11097					self.onPostProcess(args);
11098				}
11099
11100				// Restore the old document if it was changed
11101				if (oldDoc) {
11102					dom.doc = oldDoc;
11103				}
11104
11105				args.node = null;
11106
11107				return args.content;
11108			},
11109
11110			/**
11111			 * Adds valid elements rules to the serializers schema instance this enables you to specify things
11112			 * like what elements should be outputted and what attributes specific elements might have.
11113			 * Consult the Wiki for more details on this format.
11114			 *
11115			 * @method addRules
11116			 * @param {String} rules Valid elements rules string to add to schema.
11117			 */
11118			addRules: function(rules) {
11119				schema.addValidElements(rules);
11120			},
11121
11122			/**
11123			 * Sets the valid elements rules to the serializers schema instance this enables you to specify things
11124			 * like what elements should be outputted and what attributes specific elements might have.
11125			 * Consult the Wiki for more details on this format.
11126			 *
11127			 * @method setRules
11128			 * @param {String} rules Valid elements rules string.
11129			 */
11130			setRules: function(rules) {
11131				schema.setValidElements(rules);
11132			},
11133
11134			onPreProcess: function(args) {
11135				if (editor) {
11136					editor.fire('PreProcess', args);
11137				}
11138			},
11139
11140			onPostProcess: function(args) {
11141				if (editor) {
11142					editor.fire('PostProcess', args);
11143				}
11144			}
11145		};
11146	};
11147});
11148
11149// Included from: js/tinymce/classes/dom/TridentSelection.js
11150
11151/**
11152 * TridentSelection.js
11153 *
11154 * Copyright, Moxiecode Systems AB
11155 * Released under LGPL License.
11156 *
11157 * License: http://www.tinymce.com/license
11158 * Contributing: http://www.tinymce.com/contributing
11159 */
11160
11161/**
11162 * Selection class for old explorer versions. This one fakes the
11163 * native selection object available on modern browsers.
11164 *
11165 * @class tinymce.dom.TridentSelection
11166 */
11167define("tinymce/dom/TridentSelection", [], function() {
11168	function Selection(selection) {
11169		var self = this, dom = selection.dom, FALSE = false;
11170
11171		function getPosition(rng, start) {
11172			var checkRng, startIndex = 0, endIndex, inside,
11173				children, child, offset, index, position = -1, parent;
11174
11175			// Setup test range, collapse it and get the parent
11176			checkRng = rng.duplicate();
11177			checkRng.collapse(start);
11178			parent = checkRng.parentElement();
11179
11180			// Check if the selection is within the right document
11181			if (parent.ownerDocument !== selection.dom.doc) {
11182				return;
11183			}
11184
11185			// IE will report non editable elements as it's parent so look for an editable one
11186			while (parent.contentEditable === "false") {
11187				parent = parent.parentNode;
11188			}
11189
11190			// If parent doesn't have any children then return that we are inside the element
11191			if (!parent.hasChildNodes()) {
11192				return {node: parent, inside: 1};
11193			}
11194
11195			// Setup node list and endIndex
11196			children = parent.children;
11197			endIndex = children.length - 1;
11198
11199			// Perform a binary search for the position
11200			while (startIndex <= endIndex) {
11201				index = Math.floor((startIndex + endIndex) / 2);
11202
11203				// Move selection to node and compare the ranges
11204				child = children[index];
11205				checkRng.moveToElementText(child);
11206				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
11207
11208				// Before/after or an exact match
11209				if (position > 0) {
11210					endIndex = index - 1;
11211				} else if (position < 0) {
11212					startIndex = index + 1;
11213				} else {
11214					return {node: child};
11215				}
11216			}
11217
11218			// Check if child position is before or we didn't find a position
11219			if (position < 0) {
11220				// No element child was found use the parent element and the offset inside that
11221				if (!child) {
11222					checkRng.moveToElementText(parent);
11223					checkRng.collapse(true);
11224					child = parent;
11225					inside = true;
11226				} else {
11227					checkRng.collapse(false);
11228				}
11229
11230				// Walk character by character in text node until we hit the selected range endpoint,
11231				// hit the end of document or parent isn't the right one
11232				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
11233				offset = 0;
11234				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
11235					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
11236						break;
11237					}
11238
11239					offset++;
11240				}
11241			} else {
11242				// Child position is after the selection endpoint
11243				checkRng.collapse(true);
11244
11245				// Walk character by character in text node until we hit the selected range endpoint, hit
11246				// the end of document or parent isn't the right one
11247				offset = 0;
11248				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
11249					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
11250						break;
11251					}
11252
11253					offset++;
11254				}
11255			}
11256
11257			return {node: child, position: position, offset: offset, inside: inside};
11258		}
11259
11260		// Returns a W3C DOM compatible range object by using the IE Range API
11261		function getRange() {
11262			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
11263
11264			// If selection is outside the current document just return an empty range
11265			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
11266			if (element.ownerDocument != dom.doc) {
11267				return domRange;
11268			}
11269
11270			collapsed = selection.isCollapsed();
11271
11272			// Handle control selection
11273			if (ieRange.item) {
11274				domRange.setStart(element.parentNode, dom.nodeIndex(element));
11275				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
11276
11277				return domRange;
11278			}
11279
11280			function findEndPoint(start) {
11281				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
11282
11283				container = endPoint.node;
11284				offset = endPoint.offset;
11285
11286				if (endPoint.inside && !container.hasChildNodes()) {
11287					domRange[start ? 'setStart' : 'setEnd'](container, 0);
11288					return;
11289				}
11290
11291				if (offset === undef) {
11292					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
11293					return;
11294				}
11295
11296				if (endPoint.position < 0) {
11297					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
11298
11299					if (!sibling) {
11300						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
11301						return;
11302					}
11303
11304					if (!offset) {
11305						if (sibling.nodeType == 3) {
11306							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
11307						} else {
11308							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
11309						}
11310
11311						return;
11312					}
11313
11314					// Find the text node and offset
11315					while (sibling) {
11316						if (sibling.nodeType == 3) {
11317							nodeValue = sibling.nodeValue;
11318							textNodeOffset += nodeValue.length;
11319
11320							// We are at or passed the position we where looking for
11321							if (textNodeOffset >= offset) {
11322								container = sibling;
11323								textNodeOffset -= offset;
11324								textNodeOffset = nodeValue.length - textNodeOffset;
11325								break;
11326							}
11327						}
11328
11329						sibling = sibling.nextSibling;
11330					}
11331				} else {
11332					// Find the text node and offset
11333					sibling = container.previousSibling;
11334
11335					if (!sibling) {
11336						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
11337					}
11338
11339					// If there isn't any text to loop then use the first position
11340					if (!offset) {
11341						if (container.nodeType == 3) {
11342							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
11343						} else {
11344							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
11345						}
11346
11347						return;
11348					}
11349
11350					while (sibling) {
11351						if (sibling.nodeType == 3) {
11352							textNodeOffset += sibling.nodeValue.length;
11353
11354							// We are at or passed the position we where looking for
11355							if (textNodeOffset >= offset) {
11356								container = sibling;
11357								textNodeOffset -= offset;
11358								break;
11359							}
11360						}
11361
11362						sibling = sibling.previousSibling;
11363					}
11364				}
11365
11366				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
11367			}
11368
11369			try {
11370				// Find start point
11371				findEndPoint(true);
11372
11373				// Find end point if needed
11374				if (!collapsed) {
11375					findEndPoint();
11376				}
11377			} catch (ex) {
11378				// IE has a nasty bug where text nodes might throw "invalid argument" when you
11379				// access the nodeValue or other properties of text nodes. This seems to happend when
11380				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
11381				if (ex.number == -2147024809) {
11382					// Get the current selection
11383					bookmark = self.getBookmark(2);
11384
11385					// Get start element
11386					tmpRange = ieRange.duplicate();
11387					tmpRange.collapse(true);
11388					element = tmpRange.parentElement();
11389
11390					// Get end element
11391					if (!collapsed) {
11392						tmpRange = ieRange.duplicate();
11393						tmpRange.collapse(false);
11394						element2 = tmpRange.parentElement();
11395						element2.innerHTML = element2.innerHTML;
11396					}
11397
11398					// Remove the broken elements
11399					element.innerHTML = element.innerHTML;
11400
11401					// Restore the selection
11402					self.moveToBookmark(bookmark);
11403
11404					// Since the range has moved we need to re-get it
11405					ieRange = selection.getRng();
11406
11407					// Find start point
11408					findEndPoint(true);
11409
11410					// Find end point if needed
11411					if (!collapsed) {
11412						findEndPoint();
11413					}
11414				} else {
11415					throw ex; // Throw other errors
11416				}
11417			}
11418
11419			return domRange;
11420		}
11421
11422		this.getBookmark = function(type) {
11423			var rng = selection.getRng(), bookmark = {};
11424
11425			function getIndexes(node) {
11426				var parent, root, children, i, indexes = [];
11427
11428				parent = node.parentNode;
11429				root = dom.getRoot().parentNode;
11430
11431				while (parent != root && parent.nodeType !== 9) {
11432					children = parent.children;
11433
11434					i = children.length;
11435					while (i--) {
11436						if (node === children[i]) {
11437							indexes.push(i);
11438							break;
11439						}
11440					}
11441
11442					node = parent;
11443					parent = parent.parentNode;
11444				}
11445
11446				return indexes;
11447			}
11448
11449			function getBookmarkEndPoint(start) {
11450				var position;
11451
11452				position = getPosition(rng, start);
11453				if (position) {
11454					return {
11455						position: position.position,
11456						offset: position.offset,
11457						indexes: getIndexes(position.node),
11458						inside: position.inside
11459					};
11460				}
11461			}
11462
11463			// Non ubstructive bookmark
11464			if (type === 2) {
11465				// Handle text selection
11466				if (!rng.item) {
11467					bookmark.start = getBookmarkEndPoint(true);
11468
11469					if (!selection.isCollapsed()) {
11470						bookmark.end = getBookmarkEndPoint();
11471					}
11472				} else {
11473					bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
11474				}
11475			}
11476
11477			return bookmark;
11478		};
11479
11480		this.moveToBookmark = function(bookmark) {
11481			var rng, body = dom.doc.body;
11482
11483			function resolveIndexes(indexes) {
11484				var node, i, idx, children;
11485
11486				node = dom.getRoot();
11487				for (i = indexes.length - 1; i >= 0; i--) {
11488					children = node.children;
11489					idx = indexes[i];
11490
11491					if (idx <= children.length - 1) {
11492						node = children[idx];
11493					}
11494				}
11495
11496				return node;
11497			}
11498
11499			function setBookmarkEndPoint(start) {
11500				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
11501
11502				if (endPoint) {
11503					moveLeft = endPoint.position > 0;
11504
11505					moveRng = body.createTextRange();
11506					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
11507
11508					offset = endPoint.offset;
11509					if (offset !== undef) {
11510						moveRng.collapse(endPoint.inside || moveLeft);
11511						moveRng.moveStart('character', moveLeft ? -offset : offset);
11512					} else {
11513						moveRng.collapse(start);
11514					}
11515
11516					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
11517
11518					if (start) {
11519						rng.collapse(true);
11520					}
11521				}
11522			}
11523
11524			if (bookmark.start) {
11525				if (bookmark.start.ctrl) {
11526					rng = body.createControlRange();
11527					rng.addElement(resolveIndexes(bookmark.start.indexes));
11528					rng.select();
11529				} else {
11530					rng = body.createTextRange();
11531					setBookmarkEndPoint(true);
11532					setBookmarkEndPoint();
11533					rng.select();
11534				}
11535			}
11536		};
11537
11538		this.addRange = function(rng) {
11539			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
11540				doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
11541
11542			function setEndPoint(start) {
11543				var container, offset, marker, tmpRng, nodes;
11544
11545				marker = dom.create('a');
11546				container = start ? startContainer : endContainer;
11547				offset = start ? startOffset : endOffset;
11548				tmpRng = ieRng.duplicate();
11549
11550				if (container == doc || container == doc.documentElement) {
11551					container = body;
11552					offset = 0;
11553				}
11554
11555				if (container.nodeType == 3) {
11556					container.parentNode.insertBefore(marker, container);
11557					tmpRng.moveToElementText(marker);
11558					tmpRng.moveStart('character', offset);
11559					dom.remove(marker);
11560					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
11561				} else {
11562					nodes = container.childNodes;
11563
11564					if (nodes.length) {
11565						if (offset >= nodes.length) {
11566							dom.insertAfter(marker, nodes[nodes.length - 1]);
11567						} else {
11568							container.insertBefore(marker, nodes[offset]);
11569						}
11570
11571						tmpRng.moveToElementText(marker);
11572					} else if (container.canHaveHTML) {
11573						// Empty node selection for example <div>|</div>
11574						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
11575						container.innerHTML = '<span>&#xFEFF;</span>';
11576						marker = container.firstChild;
11577						tmpRng.moveToElementText(marker);
11578						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
11579					}
11580
11581					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
11582					dom.remove(marker);
11583				}
11584			}
11585
11586			// Setup some shorter versions
11587			startContainer = rng.startContainer;
11588			startOffset = rng.startOffset;
11589			endContainer = rng.endContainer;
11590			endOffset = rng.endOffset;
11591			ieRng = body.createTextRange();
11592
11593			// If single element selection then try making a control selection out of it
11594			if (startContainer == endContainer && startContainer.nodeType == 1) {
11595				// Trick to place the caret inside an empty block element like <p></p>
11596				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
11597					if (startContainer.canHaveHTML) {
11598						// Check if previous sibling is an empty block if it is then we need to render it
11599						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
11600						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
11601						sibling = startContainer.previousSibling;
11602						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
11603							sibling.innerHTML = '&#xFEFF;';
11604						} else {
11605							sibling = null;
11606						}
11607
11608						startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
11609						ieRng.moveToElementText(startContainer.lastChild);
11610						ieRng.select();
11611						dom.doc.selection.clear();
11612						startContainer.innerHTML = '';
11613
11614						if (sibling) {
11615							sibling.innerHTML = '';
11616						}
11617						return;
11618					} else {
11619						startOffset = dom.nodeIndex(startContainer);
11620						startContainer = startContainer.parentNode;
11621					}
11622				}
11623
11624				if (startOffset == endOffset - 1) {
11625					try {
11626						ctrlElm = startContainer.childNodes[startOffset];
11627						ctrlRng = body.createControlRange();
11628						ctrlRng.addElement(ctrlElm);
11629						ctrlRng.select();
11630
11631						// Check if the range produced is on the correct element and is a control range
11632						// On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
11633						nativeRng = selection.getRng();
11634						if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
11635							return;
11636						}
11637					} catch (ex) {
11638						// Ignore
11639					}
11640				}
11641			}
11642
11643			// Set start/end point of selection
11644			setEndPoint(true);
11645			setEndPoint();
11646
11647			// Select the new range and scroll it into view
11648			ieRng.select();
11649		};
11650
11651		// Expose range method
11652		this.getRangeAt = getRange;
11653	}
11654
11655	return Selection;
11656});
11657
11658// Included from: js/tinymce/classes/util/VK.js
11659
11660/**
11661 * VK.js
11662 *
11663 * Copyright, Moxiecode Systems AB
11664 * Released under LGPL License.
11665 *
11666 * License: http://www.tinymce.com/license
11667 * Contributing: http://www.tinymce.com/contributing
11668 */
11669
11670/**
11671 * This file exposes a set of the common KeyCodes for use.  Please grow it as needed.
11672 */
11673define("tinymce/util/VK", [
11674	"tinymce/Env"
11675], function(Env) {
11676	return {
11677		BACKSPACE: 8,
11678		DELETE: 46,
11679		DOWN: 40,
11680		ENTER: 13,
11681		LEFT: 37,
11682		RIGHT: 39,
11683		SPACEBAR: 32,
11684		TAB: 9,
11685		UP: 38,
11686
11687		modifierPressed: function(e) {
11688			return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e);
11689		},
11690
11691		metaKeyPressed: function(e) {
11692			// Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
11693			return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
11694		}
11695	};
11696});
11697
11698// Included from: js/tinymce/classes/dom/ControlSelection.js
11699
11700/**
11701 * ControlSelection.js
11702 *
11703 * Copyright, Moxiecode Systems AB
11704 * Released under LGPL License.
11705 *
11706 * License: http://www.tinymce.com/license
11707 * Contributing: http://www.tinymce.com/contributing
11708 */
11709
11710/**
11711 * This class handles control selection of elements. Controls are elements
11712 * that can be resized and needs to be selected as a whole. It adds custom resize handles
11713 * to all browser engines that support properly disabling the built in resize logic.
11714 *
11715 * @class tinymce.dom.ControlSelection
11716 */
11717define("tinymce/dom/ControlSelection", [
11718	"tinymce/util/VK",
11719	"tinymce/util/Tools",
11720	"tinymce/Env"
11721], function(VK, Tools, Env) {
11722	return function(selection, editor) {
11723		var dom = editor.dom, each = Tools.each;
11724		var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
11725		var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
11726		var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
11727		var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;
11728
11729		// Details about each resize handle how to scale etc
11730		resizeHandles = {
11731			// Name: x multiplier, y multiplier, delta size x, delta size y
11732			n:  [0.5,   0,     0,   -1],
11733			e:  [1,    0.5,    1,    0],
11734			s:  [0.5,   1,     0,    1],
11735			w:  [0,    0.5,   -1,    0],
11736			nw: [0,     0,    -1,   -1],
11737			ne: [1,     0,     1,   -1],
11738			se: [1,     1,     1,    1],
11739			sw: [0,     1,    -1,    1]
11740		};
11741
11742		// Add CSS for resize handles, cloned element and selected
11743		var rootClass = '.mce-content-body';
11744		editor.contentStyles.push(
11745			rootClass + ' div.mce-resizehandle {' +
11746				'position: absolute;' +
11747				'border: 1px solid black;' +
11748				'background: #FFF;' +
11749				'width: 5px;' +
11750				'height: 5px;' +
11751				'z-index: 10000' +
11752			'}' +
11753			rootClass + ' .mce-resizehandle:hover {' +
11754				'background: #000' +
11755			'}' +
11756			rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
11757				'outline: 1px solid black;' +
11758				'resize: none' + // Have been talks about implementing this in browsers
11759			'}' +
11760			rootClass + ' .mce-clonedresizable {' +
11761				'position: absolute;' +
11762				(Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
11763				'opacity: .5;' +
11764				'filter: alpha(opacity=50);' +
11765				'z-index: 10000' +
11766			'}' +
11767			rootClass + ' .mce-resize-helper {' +
11768				'background: #555;' +
11769				'background: rgba(0,0,0,0.75);' +
11770				'border-radius: 3px;' +
11771				'border: 1px;' +
11772				'color: white;' +
11773				'display: none;' +
11774				'font-family: sans-serif;' +
11775				'font-size: 12px;' +
11776				'white-space: nowrap;' +
11777				'line-height: 14px;' +
11778				'margin: 5px 10px;' +
11779				'padding: 5px;' +
11780				'position: absolute;' +
11781				'z-index: 10001' +
11782			'}'
11783		);
11784
11785		function isResizable(elm) {
11786			var selector = editor.settings.object_resizing;
11787
11788			if (selector === false || Env.iOS) {
11789				return false;
11790			}
11791
11792			if (typeof selector != 'string') {
11793				selector = 'table,img,div';
11794			}
11795
11796			if (elm.getAttribute('data-mce-resize') === 'false') {
11797				return false;
11798			}
11799
11800			return editor.dom.is(elm, selector);
11801		}
11802
11803		function resizeGhostElement(e) {
11804			var deltaX, deltaY, proportional;
11805			var resizeHelperX, resizeHelperY;
11806
11807			// Calc new width/height
11808			deltaX = e.screenX - startX;
11809			deltaY = e.screenY - startY;
11810
11811			// Calc new size
11812			width = deltaX * selectedHandle[2] + startW;
11813			height = deltaY * selectedHandle[3] + startH;
11814
11815			// Never scale down lower than 5 pixels
11816			width = width < 5 ? 5 : width;
11817			height = height < 5 ? 5 : height;
11818
11819			if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
11820				proportional = !VK.modifierPressed(e);
11821			} else {
11822				proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
11823			}
11824
11825			// Constrain proportions
11826			if (proportional) {
11827				if (abs(deltaX) > abs(deltaY)) {
11828					height = round(width * ratio);
11829					width = round(height / ratio);
11830				} else {
11831					width = round(height / ratio);
11832					height = round(width * ratio);
11833				}
11834			}
11835
11836			// Update ghost size
11837			dom.setStyles(selectedElmGhost, {
11838				width: width,
11839				height: height
11840			});
11841
11842			// Update resize helper position
11843			resizeHelperX = selectedHandle.startPos.x + deltaX;
11844			resizeHelperY = selectedHandle.startPos.y + deltaY;
11845			resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
11846			resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
11847
11848			dom.setStyles(resizeHelper, {
11849				left: resizeHelperX,
11850				top: resizeHelperY,
11851				display: 'block'
11852			});
11853
11854			resizeHelper.innerHTML = width + ' &times; ' + height;
11855
11856			// Update ghost X position if needed
11857			if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
11858				dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
11859			}
11860
11861			// Update ghost Y position if needed
11862			if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
11863				dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
11864			}
11865
11866			// Calculate how must overflow we got
11867			deltaX = rootElement.scrollWidth - startScrollWidth;
11868			deltaY = rootElement.scrollHeight - startScrollHeight;
11869
11870			// Re-position the resize helper based on the overflow
11871			if (deltaX + deltaY !== 0) {
11872				dom.setStyles(resizeHelper, {
11873					left: resizeHelperX - deltaX,
11874					top: resizeHelperY - deltaY
11875				});
11876			}
11877
11878			if (!resizeStarted) {
11879				editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
11880				resizeStarted = true;
11881			}
11882		}
11883
11884		function endGhostResize() {
11885			resizeStarted = false;
11886
11887			function setSizeProp(name, value) {
11888				if (value) {
11889					// Resize by using style or attribute
11890					if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
11891						dom.setStyle(selectedElm, name, value);
11892					} else {
11893						dom.setAttrib(selectedElm, name, value);
11894					}
11895				}
11896			}
11897
11898			// Set width/height properties
11899			setSizeProp('width', width);
11900			setSizeProp('height', height);
11901
11902			dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
11903			dom.unbind(editableDoc, 'mouseup', endGhostResize);
11904
11905			if (rootDocument != editableDoc) {
11906				dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
11907				dom.unbind(rootDocument, 'mouseup', endGhostResize);
11908			}
11909
11910			// Remove ghost/helper and update resize handle positions
11911			dom.remove(selectedElmGhost);
11912			dom.remove(resizeHelper);
11913
11914			if (!isIE || selectedElm.nodeName == "TABLE") {
11915				showResizeRect(selectedElm);
11916			}
11917
11918			editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
11919			dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
11920			editor.nodeChanged();
11921		}
11922
11923		function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
11924			var position, targetWidth, targetHeight, e, rect;
11925
11926			unbindResizeHandleEvents();
11927
11928			// Get position and size of target
11929			position = dom.getPos(targetElm, rootElement);
11930			selectedElmX = position.x;
11931			selectedElmY = position.y;
11932			rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
11933			targetWidth = rect.width || (rect.right - rect.left);
11934			targetHeight = rect.height || (rect.bottom - rect.top);
11935
11936			// Reset width/height if user selects a new image/table
11937			if (selectedElm != targetElm) {
11938				detachResizeStartListener();
11939				selectedElm = targetElm;
11940				width = height = 0;
11941			}
11942
11943			// Makes it possible to disable resizing
11944			e = editor.fire('ObjectSelected', {target: targetElm});
11945
11946			if (isResizable(targetElm) && !e.isDefaultPrevented()) {
11947				each(resizeHandles, function(handle, name) {
11948					var handleElm, handlerContainerElm;
11949
11950					function startDrag(e) {
11951						startX = e.screenX;
11952						startY = e.screenY;
11953						startW = selectedElm.clientWidth;
11954						startH = selectedElm.clientHeight;
11955						ratio = startH / startW;
11956						selectedHandle = handle;
11957
11958						handle.startPos = {
11959							x: targetWidth * handle[0] + selectedElmX,
11960							y: targetHeight * handle[1] + selectedElmY
11961						};
11962
11963						startScrollWidth = rootElement.scrollWidth;
11964						startScrollHeight = rootElement.scrollHeight;
11965
11966						selectedElmGhost = selectedElm.cloneNode(true);
11967						dom.addClass(selectedElmGhost, 'mce-clonedresizable');
11968						dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
11969						selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
11970						selectedElmGhost.unSelectabe = true;
11971						dom.setStyles(selectedElmGhost, {
11972							left: selectedElmX,
11973							top: selectedElmY,
11974							margin: 0
11975						});
11976
11977						selectedElmGhost.removeAttribute('data-mce-selected');
11978						rootElement.appendChild(selectedElmGhost);
11979
11980						dom.bind(editableDoc, 'mousemove', resizeGhostElement);
11981						dom.bind(editableDoc, 'mouseup', endGhostResize);
11982
11983						if (rootDocument != editableDoc) {
11984							dom.bind(rootDocument, 'mousemove', resizeGhostElement);
11985							dom.bind(rootDocument, 'mouseup', endGhostResize);
11986						}
11987
11988						resizeHelper = dom.add(rootElement, 'div', {
11989							'class': 'mce-resize-helper',
11990							'data-mce-bogus': 'all'
11991						}, startW + ' &times; ' + startH);
11992					}
11993
11994					if (mouseDownHandleName) {
11995						// Drag started by IE native resizestart
11996						if (name == mouseDownHandleName) {
11997							startDrag(mouseDownEvent);
11998						}
11999
12000						return;
12001					}
12002
12003					// Get existing or render resize handle
12004					handleElm = dom.get('mceResizeHandle' + name);
12005					if (!handleElm) {
12006						handlerContainerElm = rootElement;
12007
12008						handleElm = dom.add(handlerContainerElm, 'div', {
12009							id: 'mceResizeHandle' + name,
12010							'data-mce-bogus': 'all',
12011							'class': 'mce-resizehandle',
12012							unselectable: true,
12013							style: 'cursor:' + name + '-resize; margin:0; padding:0'
12014						});
12015
12016						// Hides IE move layer cursor
12017						// If we set it on Chrome we get this wounderful bug: #6725
12018						if (Env.ie) {
12019							handleElm.contentEditable = false;
12020						}
12021					} else {
12022						dom.show(handleElm);
12023					}
12024
12025					if (!handle.elm) {
12026						dom.bind(handleElm, 'mousedown', function(e) {
12027							e.stopImmediatePropagation();
12028							e.preventDefault();
12029							startDrag(e);
12030						});
12031
12032						handle.elm = handleElm;
12033					}
12034
12035					// Position element
12036					dom.setStyles(handleElm, {
12037						left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
12038						top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
12039					});
12040				});
12041			} else {
12042				hideResizeRect();
12043			}
12044
12045			selectedElm.setAttribute('data-mce-selected', '1');
12046		}
12047
12048		function hideResizeRect() {
12049			var name, handleElm;
12050
12051			unbindResizeHandleEvents();
12052
12053			if (selectedElm) {
12054				selectedElm.removeAttribute('data-mce-selected');
12055			}
12056
12057			for (name in resizeHandles) {
12058				handleElm = dom.get('mceResizeHandle' + name);
12059				if (handleElm) {
12060					dom.unbind(handleElm);
12061					dom.remove(handleElm);
12062				}
12063			}
12064		}
12065
12066		function updateResizeRect(e) {
12067			var startElm, controlElm;
12068
12069			function isChildOrEqual(node, parent) {
12070				if (node) {
12071					do {
12072						if (node === parent) {
12073							return true;
12074						}
12075					} while ((node = node.parentNode));
12076				}
12077			}
12078
12079			// Ignore all events while resizing
12080			if (resizeStarted) {
12081				return;
12082			}
12083
12084			// Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
12085			each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
12086				img.removeAttribute('data-mce-selected');
12087			});
12088
12089			controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
12090			controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
12091
12092			if (isChildOrEqual(controlElm, rootElement)) {
12093				disableGeckoResize();
12094				startElm = selection.getStart(true);
12095
12096				if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
12097					if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) {
12098						showResizeRect(controlElm);
12099						return;
12100					}
12101				}
12102			}
12103
12104			hideResizeRect();
12105		}
12106
12107		function attachEvent(elm, name, func) {
12108			if (elm && elm.attachEvent) {
12109				elm.attachEvent('on' + name, func);
12110			}
12111		}
12112
12113		function detachEvent(elm, name, func) {
12114			if (elm && elm.detachEvent) {
12115				elm.detachEvent('on' + name, func);
12116			}
12117		}
12118
12119		function resizeNativeStart(e) {
12120			var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
12121
12122			pos = target.getBoundingClientRect();
12123			relativeX = lastMouseDownEvent.clientX - pos.left;
12124			relativeY = lastMouseDownEvent.clientY - pos.top;
12125
12126			// Figure out what corner we are draging on
12127			for (name in resizeHandles) {
12128				corner = resizeHandles[name];
12129
12130				cornerX = target.offsetWidth * corner[0];
12131				cornerY = target.offsetHeight * corner[1];
12132
12133				if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
12134					selectedHandle = corner;
12135					break;
12136				}
12137			}
12138
12139			// Remove native selection and let the magic begin
12140			resizeStarted = true;
12141			editor.fire('ObjectResizeStart', {
12142				target: selectedElm,
12143				width: selectedElm.clientWidth,
12144				height: selectedElm.clientHeight
12145			});
12146			editor.getDoc().selection.empty();
12147			showResizeRect(target, name, lastMouseDownEvent);
12148		}
12149
12150		function nativeControlSelect(e) {
12151			var target = e.srcElement;
12152
12153			if (target != selectedElm) {
12154				editor.fire('ObjectSelected', {target: target});
12155				detachResizeStartListener();
12156
12157				if (target.id.indexOf('mceResizeHandle') === 0) {
12158					e.returnValue = false;
12159					return;
12160				}
12161
12162				if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
12163					hideResizeRect();
12164					selectedElm = target;
12165					attachEvent(target, 'resizestart', resizeNativeStart);
12166				}
12167			}
12168		}
12169
12170		function detachResizeStartListener() {
12171			detachEvent(selectedElm, 'resizestart', resizeNativeStart);
12172		}
12173
12174		function unbindResizeHandleEvents() {
12175			for (var name in resizeHandles) {
12176				var handle = resizeHandles[name];
12177
12178				if (handle.elm) {
12179					dom.unbind(handle.elm);
12180					delete handle.elm;
12181				}
12182			}
12183		}
12184
12185		function disableGeckoResize() {
12186			try {
12187				// Disable object resizing on Gecko
12188				editor.getDoc().execCommand('enableObjectResizing', false, false);
12189			} catch (ex) {
12190				// Ignore
12191			}
12192		}
12193
12194		function controlSelect(elm) {
12195			var ctrlRng;
12196
12197			if (!isIE) {
12198				return;
12199			}
12200
12201			ctrlRng = editableDoc.body.createControlRange();
12202
12203			try {
12204				ctrlRng.addElement(elm);
12205				ctrlRng.select();
12206				return true;
12207			} catch (ex) {
12208				// Ignore since the element can't be control selected for example a P tag
12209			}
12210		}
12211
12212		editor.on('init', function() {
12213			if (isIE) {
12214				// Hide the resize rect on resize and reselect the image
12215				editor.on('ObjectResized', function(e) {
12216					if (e.target.nodeName != 'TABLE') {
12217						hideResizeRect();
12218						controlSelect(e.target);
12219					}
12220				});
12221
12222				attachEvent(rootElement, 'controlselect', nativeControlSelect);
12223
12224				editor.on('mousedown', function(e) {
12225					lastMouseDownEvent = e;
12226				});
12227			} else {
12228				disableGeckoResize();
12229
12230				if (Env.ie >= 11) {
12231					// TODO: Drag/drop doesn't work
12232					editor.on('mouseup', function(e) {
12233						var nodeName = e.target.nodeName;
12234
12235						if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) {
12236							editor.selection.select(e.target, nodeName == 'TABLE');
12237							editor.nodeChanged();
12238						}
12239					});
12240
12241					editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
12242						if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
12243							e.preventDefault();
12244
12245							// This moves the selection from being a control selection to a text like selection like in WebKit #6753
12246							// TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
12247							if (e.target.tagName == 'IMG') {
12248								window.setTimeout(function() {
12249									editor.selection.select(e.target);
12250								}, 0);
12251							}
12252						}
12253					});
12254				}
12255			}
12256
12257			editor.on('nodechange ResizeEditor', updateResizeRect);
12258
12259			// Update resize rect while typing in a table
12260			editor.on('keydown keyup', function(e) {
12261				if (selectedElm && selectedElm.nodeName == "TABLE") {
12262					updateResizeRect(e);
12263				}
12264			});
12265
12266			editor.on('hide', hideResizeRect);
12267
12268			// Hide rect on focusout since it would float on top of windows otherwise
12269			//editor.on('focusout', hideResizeRect);
12270		});
12271
12272		editor.on('remove', unbindResizeHandleEvents);
12273
12274		function destroy() {
12275			selectedElm = selectedElmGhost = null;
12276
12277			if (isIE) {
12278				detachResizeStartListener();
12279				detachEvent(rootElement, 'controlselect', nativeControlSelect);
12280			}
12281		}
12282
12283		return {
12284			isResizable: isResizable,
12285			showResizeRect: showResizeRect,
12286			hideResizeRect: hideResizeRect,
12287			updateResizeRect: updateResizeRect,
12288			controlSelect: controlSelect,
12289			destroy: destroy
12290		};
12291	};
12292});
12293
12294// Included from: js/tinymce/classes/dom/BookmarkManager.js
12295
12296/**
12297 * BookmarkManager.js
12298 *
12299 * Copyright, Moxiecode Systems AB
12300 * Released under LGPL License.
12301 *
12302 * License: http://www.tinymce.com/license
12303 * Contributing: http://www.tinymce.com/contributing
12304 */
12305
12306/**
12307 * This class handles selection bookmarks.
12308 *
12309 * @class tinymce.dom.BookmarkManager
12310 */
12311define("tinymce/dom/BookmarkManager", [
12312	"tinymce/Env",
12313	"tinymce/util/Tools"
12314], function(Env, Tools) {
12315	/**
12316	 * Constructs a new BookmarkManager instance for a specific selection instance.
12317	 *
12318	 * @constructor
12319	 * @method BookmarkManager
12320	 * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
12321	 */
12322	function BookmarkManager(selection) {
12323		var dom = selection.dom;
12324
12325		/**
12326		 * Returns a bookmark location for the current selection. This bookmark object
12327		 * can then be used to restore the selection after some content modification to the document.
12328		 *
12329		 * @method getBookmark
12330		 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
12331		 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
12332		 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
12333		 * @example
12334		 * // Stores a bookmark of the current selection
12335		 * var bm = tinymce.activeEditor.selection.getBookmark();
12336		 *
12337		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12338		 *
12339		 * // Restore the selection bookmark
12340		 * tinymce.activeEditor.selection.moveToBookmark(bm);
12341		 */
12342		this.getBookmark = function(type, normalized) {
12343			var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
12344
12345			function findIndex(name, element) {
12346				var index = 0;
12347
12348				Tools.each(dom.select(name), function(node, i) {
12349					if (node == element) {
12350						index = i;
12351					}
12352				});
12353
12354				return index;
12355			}
12356
12357			function normalizeTableCellSelection(rng) {
12358				function moveEndPoint(start) {
12359					var container, offset, childNodes, prefix = start ? 'start' : 'end';
12360
12361					container = rng[prefix + 'Container'];
12362					offset = rng[prefix + 'Offset'];
12363
12364					if (container.nodeType == 1 && container.nodeName == "TR") {
12365						childNodes = container.childNodes;
12366						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
12367						if (container) {
12368							offset = start ? 0 : container.childNodes.length;
12369							rng['set' + (start ? 'Start' : 'End')](container, offset);
12370						}
12371					}
12372				}
12373
12374				moveEndPoint(true);
12375				moveEndPoint();
12376
12377				return rng;
12378			}
12379
12380			function getLocation() {
12381				var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
12382
12383				function getPoint(rng, start) {
12384					var container = rng[start ? 'startContainer' : 'endContainer'],
12385						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
12386
12387					if (container.nodeType == 3) {
12388						if (normalized) {
12389							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
12390								offset += node.nodeValue.length;
12391							}
12392						}
12393
12394						point.push(offset);
12395					} else {
12396						childNodes = container.childNodes;
12397
12398						if (offset >= childNodes.length && childNodes.length) {
12399							after = 1;
12400							offset = Math.max(0, childNodes.length - 1);
12401						}
12402
12403						point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
12404					}
12405
12406					for (; container && container != root; container = container.parentNode) {
12407						point.push(dom.nodeIndex(container, normalized));
12408					}
12409
12410					return point;
12411				}
12412
12413				bookmark.start = getPoint(rng, true);
12414
12415				if (!selection.isCollapsed()) {
12416					bookmark.end = getPoint(rng);
12417				}
12418
12419				return bookmark;
12420			}
12421
12422			if (type == 2) {
12423				element = selection.getNode();
12424				name = element ? element.nodeName : null;
12425
12426				if (name == 'IMG') {
12427					return {name: name, index: findIndex(name, element)};
12428				}
12429
12430				if (selection.tridentSel) {
12431					return selection.tridentSel.getBookmark(type);
12432				}
12433
12434				return getLocation();
12435			}
12436
12437			// Handle simple range
12438			if (type) {
12439				return {rng: selection.getRng()};
12440			}
12441
12442			rng = selection.getRng();
12443			id = dom.uniqueId();
12444			collapsed = selection.isCollapsed();
12445			styles = 'overflow:hidden;line-height:0px';
12446
12447			// Explorer method
12448			if (rng.duplicate || rng.item) {
12449				// Text selection
12450				if (!rng.item) {
12451					rng2 = rng.duplicate();
12452
12453					try {
12454						// Insert start marker
12455						rng.collapse();
12456						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
12457
12458						// Insert end marker
12459						if (!collapsed) {
12460							rng2.collapse(false);
12461
12462							// Detect the empty space after block elements in IE and move the
12463							// end back one character <p></p>] becomes <p>]</p>
12464							rng.moveToElementText(rng2.parentElement());
12465							if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
12466								rng2.move('character', -1);
12467							}
12468
12469							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
12470						}
12471					} catch (ex) {
12472						// IE might throw unspecified error so lets ignore it
12473						return null;
12474					}
12475				} else {
12476					// Control selection
12477					element = rng.item(0);
12478					name = element.nodeName;
12479
12480					return {name: name, index: findIndex(name, element)};
12481				}
12482			} else {
12483				element = selection.getNode();
12484				name = element.nodeName;
12485				if (name == 'IMG') {
12486					return {name: name, index: findIndex(name, element)};
12487				}
12488
12489				// W3C method
12490				rng2 = normalizeTableCellSelection(rng.cloneRange());
12491
12492				// Insert end marker
12493				if (!collapsed) {
12494					rng2.collapse(false);
12495					rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
12496				}
12497
12498				rng = normalizeTableCellSelection(rng);
12499				rng.collapse(true);
12500				rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
12501			}
12502
12503			selection.moveToBookmark({id: id, keep: 1});
12504
12505			return {id: id};
12506		};
12507
12508		/**
12509		 * Restores the selection to the specified bookmark.
12510		 *
12511		 * @method moveToBookmark
12512		 * @param {Object} bookmark Bookmark to restore selection from.
12513		 * @return {Boolean} true/false if it was successful or not.
12514		 * @example
12515		 * // Stores a bookmark of the current selection
12516		 * var bm = tinymce.activeEditor.selection.getBookmark();
12517		 *
12518		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12519		 *
12520		 * // Restore the selection bookmark
12521		 * tinymce.activeEditor.selection.moveToBookmark(bm);
12522		 */
12523		this.moveToBookmark = function(bookmark) {
12524			var rng, root, startContainer, endContainer, startOffset, endOffset;
12525
12526			function setEndPoint(start) {
12527				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
12528
12529				if (point) {
12530					offset = point[0];
12531
12532					// Find container node
12533					for (node = root, i = point.length - 1; i >= 1; i--) {
12534						children = node.childNodes;
12535
12536						if (point[i] > children.length - 1) {
12537							return;
12538						}
12539
12540						node = children[point[i]];
12541					}
12542
12543					// Move text offset to best suitable location
12544					if (node.nodeType === 3) {
12545						offset = Math.min(point[0], node.nodeValue.length);
12546					}
12547
12548					// Move element offset to best suitable location
12549					if (node.nodeType === 1) {
12550						offset = Math.min(point[0], node.childNodes.length);
12551					}
12552
12553					// Set offset within container node
12554					if (start) {
12555						rng.setStart(node, offset);
12556					} else {
12557						rng.setEnd(node, offset);
12558					}
12559				}
12560
12561				return true;
12562			}
12563
12564			function restoreEndPoint(suffix) {
12565				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
12566
12567				if (marker) {
12568					node = marker.parentNode;
12569
12570					if (suffix == 'start') {
12571						if (!keep) {
12572							idx = dom.nodeIndex(marker);
12573						} else {
12574							node = marker.firstChild;
12575							idx = 1;
12576						}
12577
12578						startContainer = endContainer = node;
12579						startOffset = endOffset = idx;
12580					} else {
12581						if (!keep) {
12582							idx = dom.nodeIndex(marker);
12583						} else {
12584							node = marker.firstChild;
12585							idx = 1;
12586						}
12587
12588						endContainer = node;
12589						endOffset = idx;
12590					}
12591
12592					if (!keep) {
12593						prev = marker.previousSibling;
12594						next = marker.nextSibling;
12595
12596						// Remove all marker text nodes
12597						Tools.each(Tools.grep(marker.childNodes), function(node) {
12598							if (node.nodeType == 3) {
12599								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
12600							}
12601						});
12602
12603						// Remove marker but keep children if for example contents where inserted into the marker
12604						// Also remove duplicated instances of the marker for example by a
12605						// split operation or by WebKit auto split on paste feature
12606						while ((marker = dom.get(bookmark.id + '_' + suffix))) {
12607							dom.remove(marker, 1);
12608						}
12609
12610						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
12611						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
12612						// isn't worth the effort. Sorry, Opera but it's just a fact
12613						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
12614							idx = prev.nodeValue.length;
12615							prev.appendData(next.nodeValue);
12616							dom.remove(next);
12617
12618							if (suffix == 'start') {
12619								startContainer = endContainer = prev;
12620								startOffset = endOffset = idx;
12621							} else {
12622								endContainer = prev;
12623								endOffset = idx;
12624							}
12625						}
12626					}
12627				}
12628			}
12629
12630			function addBogus(node) {
12631				// Adds a bogus BR element for empty block elements
12632				if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
12633					node.innerHTML = '<br data-mce-bogus="1" />';
12634				}
12635
12636				return node;
12637			}
12638
12639			if (bookmark) {
12640				if (bookmark.start) {
12641					rng = dom.createRng();
12642					root = dom.getRoot();
12643
12644					if (selection.tridentSel) {
12645						return selection.tridentSel.moveToBookmark(bookmark);
12646					}
12647
12648					if (setEndPoint(true) && setEndPoint()) {
12649						selection.setRng(rng);
12650					}
12651				} else if (bookmark.id) {
12652					// Restore start/end points
12653					restoreEndPoint('start');
12654					restoreEndPoint('end');
12655
12656					if (startContainer) {
12657						rng = dom.createRng();
12658						rng.setStart(addBogus(startContainer), startOffset);
12659						rng.setEnd(addBogus(endContainer), endOffset);
12660						selection.setRng(rng);
12661					}
12662				} else if (bookmark.name) {
12663					selection.select(dom.select(bookmark.name)[bookmark.index]);
12664				} else if (bookmark.rng) {
12665					selection.setRng(bookmark.rng);
12666				}
12667			}
12668		};
12669	}
12670
12671	/**
12672	 * Returns true/false if the specified node is a bookmark node or not.
12673	 *
12674	 * @static
12675	 * @method isBookmarkNode
12676	 * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
12677	 * @return {Boolean} true/false if the node is a bookmark node or not.
12678	 */
12679	BookmarkManager.isBookmarkNode = function(node) {
12680		return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
12681	};
12682
12683	return BookmarkManager;
12684});
12685
12686// Included from: js/tinymce/classes/dom/Selection.js
12687
12688/**
12689 * Selection.js
12690 *
12691 * Copyright, Moxiecode Systems AB
12692 * Released under LGPL License.
12693 *
12694 * License: http://www.tinymce.com/license
12695 * Contributing: http://www.tinymce.com/contributing
12696 */
12697
12698/**
12699 * This class handles text and control selection it's an crossbrowser utility class.
12700 * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
12701 *
12702 * @class tinymce.dom.Selection
12703 * @example
12704 * // Getting the currently selected node for the active editor
12705 * alert(tinymce.activeEditor.selection.getNode().nodeName);
12706 */
12707define("tinymce/dom/Selection", [
12708	"tinymce/dom/TreeWalker",
12709	"tinymce/dom/TridentSelection",
12710	"tinymce/dom/ControlSelection",
12711	"tinymce/dom/RangeUtils",
12712	"tinymce/dom/BookmarkManager",
12713	"tinymce/Env",
12714	"tinymce/util/Tools"
12715], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) {
12716	var each = Tools.each, trim = Tools.trim;
12717	var isIE = Env.ie;
12718
12719	/**
12720	 * Constructs a new selection instance.
12721	 *
12722	 * @constructor
12723	 * @method Selection
12724	 * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
12725	 * @param {Window} win Window to bind the selection object to.
12726	 * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
12727	 */
12728	function Selection(dom, win, serializer, editor) {
12729		var self = this;
12730
12731		self.dom = dom;
12732		self.win = win;
12733		self.serializer = serializer;
12734		self.editor = editor;
12735		self.bookmarkManager = new BookmarkManager(self);
12736		self.controlSelection = new ControlSelection(self, editor);
12737
12738		// No W3C Range support
12739		if (!self.win.getSelection) {
12740			self.tridentSel = new TridentSelection(self);
12741		}
12742	}
12743
12744	Selection.prototype = {
12745		/**
12746		 * Move the selection cursor range to the specified node and offset.
12747		 * If there is no node specified it will move it to the first suitable location within the body.
12748		 *
12749		 * @method setCursorLocation
12750		 * @param {Node} node Optional node to put the cursor in.
12751		 * @param {Number} offset Optional offset from the start of the node to put the cursor at.
12752		 */
12753		setCursorLocation: function(node, offset) {
12754			var self = this, rng = self.dom.createRng();
12755
12756			if (!node) {
12757				self._moveEndPoint(rng, self.editor.getBody(), true);
12758				self.setRng(rng);
12759			} else {
12760				rng.setStart(node, offset);
12761				rng.setEnd(node, offset);
12762				self.setRng(rng);
12763				self.collapse(false);
12764			}
12765		},
12766
12767		/**
12768		 * Returns the selected contents using the DOM serializer passed in to this class.
12769		 *
12770		 * @method getContent
12771		 * @param {Object} s Optional settings class with for example output format text or html.
12772		 * @return {String} Selected contents in for example HTML format.
12773		 * @example
12774		 * // Alerts the currently selected contents
12775		 * alert(tinymce.activeEditor.selection.getContent());
12776		 *
12777		 * // Alerts the currently selected contents as plain text
12778		 * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
12779		 */
12780		getContent: function(args) {
12781			var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
12782			var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
12783
12784			args = args || {};
12785			whiteSpaceBefore = whiteSpaceAfter = '';
12786			args.get = true;
12787			args.format = args.format || 'html';
12788			args.selection = true;
12789			self.editor.fire('BeforeGetContent', args);
12790
12791			if (args.format == 'text') {
12792				return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
12793			}
12794
12795			if (rng.cloneContents) {
12796				fragment = rng.cloneContents();
12797
12798				if (fragment) {
12799					tmpElm.appendChild(fragment);
12800				}
12801			} else if (rng.item !== undefined || rng.htmlText !== undefined) {
12802				// IE will produce invalid markup if elements are present that
12803				// it doesn't understand like custom elements or HTML5 elements.
12804				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
12805				tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
12806				tmpElm.removeChild(tmpElm.firstChild);
12807			} else {
12808				tmpElm.innerHTML = rng.toString();
12809			}
12810
12811			// Keep whitespace before and after
12812			if (/^\s/.test(tmpElm.innerHTML)) {
12813				whiteSpaceBefore = ' ';
12814			}
12815
12816			if (/\s+$/.test(tmpElm.innerHTML)) {
12817				whiteSpaceAfter = ' ';
12818			}
12819
12820			args.getInner = true;
12821
12822			args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
12823			self.editor.fire('GetContent', args);
12824
12825			return args.content;
12826		},
12827
12828		/**
12829		 * Sets the current selection to the specified content. If any contents is selected it will be replaced
12830		 * with the contents passed in to this function. If there is no selection the contents will be inserted
12831		 * where the caret is placed in the editor/page.
12832		 *
12833		 * @method setContent
12834		 * @param {String} content HTML contents to set could also be other formats depending on settings.
12835		 * @param {Object} args Optional settings object with for example data format.
12836		 * @example
12837		 * // Inserts some HTML contents at the current selection
12838		 * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
12839		 */
12840		setContent: function(content, args) {
12841			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
12842
12843			args = args || {format: 'html'};
12844			args.set = true;
12845			args.selection = true;
12846			content = args.content = content;
12847
12848			// Dispatch before set content event
12849			if (!args.no_events) {
12850				self.editor.fire('BeforeSetContent', args);
12851			}
12852
12853			content = args.content;
12854
12855			if (rng.insertNode) {
12856				// Make caret marker since insertNode places the caret in the beginning of text after insert
12857				content += '<span id="__caret">_</span>';
12858
12859				// Delete and insert new node
12860				if (rng.startContainer == doc && rng.endContainer == doc) {
12861					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
12862					doc.body.innerHTML = content;
12863				} else {
12864					rng.deleteContents();
12865
12866					if (doc.body.childNodes.length === 0) {
12867						doc.body.innerHTML = content;
12868					} else {
12869						// createContextualFragment doesn't exists in IE 9 DOMRanges
12870						if (rng.createContextualFragment) {
12871							rng.insertNode(rng.createContextualFragment(content));
12872						} else {
12873							// Fake createContextualFragment call in IE 9
12874							frag = doc.createDocumentFragment();
12875							temp = doc.createElement('div');
12876
12877							frag.appendChild(temp);
12878							temp.outerHTML = content;
12879
12880							rng.insertNode(frag);
12881						}
12882					}
12883				}
12884
12885				// Move to caret marker
12886				caretNode = self.dom.get('__caret');
12887
12888				// Make sure we wrap it compleatly, Opera fails with a simple select call
12889				rng = doc.createRange();
12890				rng.setStartBefore(caretNode);
12891				rng.setEndBefore(caretNode);
12892				self.setRng(rng);
12893
12894				// Remove the caret position
12895				self.dom.remove('__caret');
12896
12897				try {
12898					self.setRng(rng);
12899				} catch (ex) {
12900					// Might fail on Opera for some odd reason
12901				}
12902			} else {
12903				if (rng.item) {
12904					// Delete content and get caret text selection
12905					doc.execCommand('Delete', false, null);
12906					rng = self.getRng();
12907				}
12908
12909				// Explorer removes spaces from the beginning of pasted contents
12910				if (/^\s+/.test(content)) {
12911					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
12912					self.dom.remove('__mce_tmp');
12913				} else {
12914					rng.pasteHTML(content);
12915				}
12916			}
12917
12918			// Dispatch set content event
12919			if (!args.no_events) {
12920				self.editor.fire('SetContent', args);
12921			}
12922		},
12923
12924		/**
12925		 * Returns the start element of a selection range. If the start is in a text
12926		 * node the parent element will be returned.
12927		 *
12928		 * @method getStart
12929		 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
12930		 * @return {Element} Start element of selection range.
12931		 */
12932		getStart: function(real) {
12933			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
12934
12935			if (rng.duplicate || rng.item) {
12936				// Control selection, return first item
12937				if (rng.item) {
12938					return rng.item(0);
12939				}
12940
12941				// Get start element
12942				checkRng = rng.duplicate();
12943				checkRng.collapse(1);
12944				startElement = checkRng.parentElement();
12945				if (startElement.ownerDocument !== self.dom.doc) {
12946					startElement = self.dom.getRoot();
12947				}
12948
12949				// Check if range parent is inside the start element, then return the inner parent element
12950				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
12951				parentElement = node = rng.parentElement();
12952				while ((node = node.parentNode)) {
12953					if (node == startElement) {
12954						startElement = parentElement;
12955						break;
12956					}
12957				}
12958
12959				return startElement;
12960			} else {
12961				startElement = rng.startContainer;
12962
12963				if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
12964					if (!real || !rng.collapsed) {
12965						startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
12966					}
12967				}
12968
12969				if (startElement && startElement.nodeType == 3) {
12970					return startElement.parentNode;
12971				}
12972
12973				return startElement;
12974			}
12975		},
12976
12977		/**
12978		 * Returns the end element of a selection range. If the end is in a text
12979		 * node the parent element will be returned.
12980		 *
12981		 * @method getEnd
12982		 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
12983		 * @return {Element} End element of selection range.
12984		 */
12985		getEnd: function(real) {
12986			var self = this, rng = self.getRng(), endElement, endOffset;
12987
12988			if (rng.duplicate || rng.item) {
12989				if (rng.item) {
12990					return rng.item(0);
12991				}
12992
12993				rng = rng.duplicate();
12994				rng.collapse(0);
12995				endElement = rng.parentElement();
12996				if (endElement.ownerDocument !== self.dom.doc) {
12997					endElement = self.dom.getRoot();
12998				}
12999
13000				if (endElement && endElement.nodeName == 'BODY') {
13001					return endElement.lastChild || endElement;
13002				}
13003
13004				return endElement;
13005			} else {
13006				endElement = rng.endContainer;
13007				endOffset = rng.endOffset;
13008
13009				if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
13010					if (!real || !rng.collapsed) {
13011						endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
13012					}
13013				}
13014
13015				if (endElement && endElement.nodeType == 3) {
13016					return endElement.parentNode;
13017				}
13018
13019				return endElement;
13020			}
13021		},
13022
13023		/**
13024		 * Returns a bookmark location for the current selection. This bookmark object
13025		 * can then be used to restore the selection after some content modification to the document.
13026		 *
13027		 * @method getBookmark
13028		 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
13029		 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
13030		 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
13031		 * @example
13032		 * // Stores a bookmark of the current selection
13033		 * var bm = tinymce.activeEditor.selection.getBookmark();
13034		 *
13035		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
13036		 *
13037		 * // Restore the selection bookmark
13038		 * tinymce.activeEditor.selection.moveToBookmark(bm);
13039		 */
13040		getBookmark: function(type, normalized) {
13041			return this.bookmarkManager.getBookmark(type, normalized);
13042		},
13043
13044		/**
13045		 * Restores the selection to the specified bookmark.
13046		 *
13047		 * @method moveToBookmark
13048		 * @param {Object} bookmark Bookmark to restore selection from.
13049		 * @return {Boolean} true/false if it was successful or not.
13050		 * @example
13051		 * // Stores a bookmark of the current selection
13052		 * var bm = tinymce.activeEditor.selection.getBookmark();
13053		 *
13054		 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
13055		 *
13056		 * // Restore the selection bookmark
13057		 * tinymce.activeEditor.selection.moveToBookmark(bm);
13058		 */
13059		moveToBookmark: function(bookmark) {
13060			return this.bookmarkManager.moveToBookmark(bookmark);
13061		},
13062
13063		/**
13064		 * Selects the specified element. This will place the start and end of the selection range around the element.
13065		 *
13066		 * @method select
13067		 * @param {Element} node HMTL DOM element to select.
13068		 * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
13069		 * @return {Element} Selected element the same element as the one that got passed in.
13070		 * @example
13071		 * // Select the first paragraph in the active editor
13072		 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
13073		 */
13074		select: function(node, content) {
13075			var self = this, dom = self.dom, rng = dom.createRng(), idx;
13076
13077			// Clear stored range set by FocusManager
13078			self.lastFocusBookmark = null;
13079
13080			if (node) {
13081				if (!content && self.controlSelection.controlSelect(node)) {
13082					return;
13083				}
13084
13085				idx = dom.nodeIndex(node);
13086				rng.setStart(node.parentNode, idx);
13087				rng.setEnd(node.parentNode, idx + 1);
13088
13089				// Find first/last text node or BR element
13090				if (content) {
13091					self._moveEndPoint(rng, node, true);
13092					self._moveEndPoint(rng, node);
13093				}
13094
13095				self.setRng(rng);
13096			}
13097
13098			return node;
13099		},
13100
13101		/**
13102		 * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
13103		 *
13104		 * @method isCollapsed
13105		 * @return {Boolean} true/false state if the selection range is collapsed or not.
13106		 * Collapsed means if it's a caret or a larger selection.
13107		 */
13108		isCollapsed: function() {
13109			var self = this, rng = self.getRng(), sel = self.getSel();
13110
13111			if (!rng || rng.item) {
13112				return false;
13113			}
13114
13115			if (rng.compareEndPoints) {
13116				return rng.compareEndPoints('StartToEnd', rng) === 0;
13117			}
13118
13119			return !sel || rng.collapsed;
13120		},
13121
13122		/**
13123		 * Collapse the selection to start or end of range.
13124		 *
13125		 * @method collapse
13126		 * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to start.
13127		 */
13128		collapse: function(toStart) {
13129			var self = this, rng = self.getRng(), node;
13130
13131			// Control range on IE
13132			if (rng.item) {
13133				node = rng.item(0);
13134				rng = self.win.document.body.createTextRange();
13135				rng.moveToElementText(node);
13136			}
13137
13138			rng.collapse(!!toStart);
13139			self.setRng(rng);
13140		},
13141
13142		/**
13143		 * Returns the browsers internal selection object.
13144		 *
13145		 * @method getSel
13146		 * @return {Selection} Internal browser selection object.
13147		 */
13148		getSel: function() {
13149			var win = this.win;
13150
13151			return win.getSelection ? win.getSelection() : win.document.selection;
13152		},
13153
13154		/**
13155		 * Returns the browsers internal range object.
13156		 *
13157		 * @method getRng
13158		 * @param {Boolean} w3c Forces a compatible W3C range on IE.
13159		 * @return {Range} Internal browser range object.
13160		 * @see http://www.quirksmode.org/dom/range_intro.html
13161		 * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
13162		 */
13163		getRng: function(w3c) {
13164			var self = this, selection, rng, elm, doc = self.win.document, ieRng;
13165
13166			function tryCompareBoundaryPoints(how, sourceRange, destinationRange) {
13167				try {
13168					return sourceRange.compareBoundaryPoints(how, destinationRange);
13169				} catch (ex) {
13170					// Gecko throws wrong document exception if the range points
13171					// to nodes that where removed from the dom #6690
13172					// Browsers should mutate existing DOMRange instances so that they always point
13173					// to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
13174					// For performance reasons just return -1
13175					return -1;
13176				}
13177			}
13178
13179			// Use last rng passed from FocusManager if it's available this enables
13180			// calls to editor.selection.getStart() to work when caret focus is lost on IE
13181			if (!w3c && self.lastFocusBookmark) {
13182				var bookmark = self.lastFocusBookmark;
13183
13184				// Convert bookmark to range IE 11 fix
13185				if (bookmark.startContainer) {
13186					rng = doc.createRange();
13187					rng.setStart(bookmark.startContainer, bookmark.startOffset);
13188					rng.setEnd(bookmark.endContainer, bookmark.endOffset);
13189				} else {
13190					rng = bookmark;
13191				}
13192
13193				return rng;
13194			}
13195
13196			// Found tridentSel object then we need to use that one
13197			if (w3c && self.tridentSel) {
13198				return self.tridentSel.getRangeAt(0);
13199			}
13200
13201			try {
13202				if ((selection = self.getSel())) {
13203					if (selection.rangeCount > 0) {
13204						rng = selection.getRangeAt(0);
13205					} else {
13206						rng = selection.createRange ? selection.createRange() : doc.createRange();
13207					}
13208				}
13209			} catch (ex) {
13210				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
13211			}
13212
13213			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
13214			// IE 11 doesn't support the selection object so we check for that as well
13215			if (isIE && rng && rng.setStart && doc.selection) {
13216				try {
13217					// IE will sometimes throw an exception here
13218					ieRng = doc.selection.createRange();
13219				} catch (ex) {
13220
13221				}
13222
13223				if (ieRng && ieRng.item) {
13224					elm = ieRng.item(0);
13225					rng = doc.createRange();
13226					rng.setStartBefore(elm);
13227					rng.setEndAfter(elm);
13228				}
13229			}
13230
13231			// No range found then create an empty one
13232			// This can occur when the editor is placed in a hidden container element on Gecko
13233			// Or on IE when there was an exception
13234			if (!rng) {
13235				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
13236			}
13237
13238			// If range is at start of document then move it to start of body
13239			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
13240				elm = self.dom.getRoot();
13241				rng.setStart(elm, 0);
13242				rng.setEnd(elm, 0);
13243			}
13244
13245			if (self.selectedRange && self.explicitRange) {
13246				if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
13247					tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
13248					// Safari, Opera and Chrome only ever select text which causes the range to change.
13249					// This lets us use the originally set range if the selection hasn't been changed by the user.
13250					rng = self.explicitRange;
13251				} else {
13252					self.selectedRange = null;
13253					self.explicitRange = null;
13254				}
13255			}
13256
13257			return rng;
13258		},
13259
13260		/**
13261		 * Changes the selection to the specified DOM range.
13262		 *
13263		 * @method setRng
13264		 * @param {Range} rng Range to select.
13265		 */
13266		setRng: function(rng, forward) {
13267			var self = this, sel;
13268
13269			if (!rng) {
13270				return;
13271			}
13272
13273			// Is IE specific range
13274			if (rng.select) {
13275				try {
13276					rng.select();
13277				} catch (ex) {
13278					// Needed for some odd IE bug #1843306
13279				}
13280
13281				return;
13282			}
13283
13284			if (!self.tridentSel) {
13285				sel = self.getSel();
13286
13287				if (sel) {
13288					self.explicitRange = rng;
13289
13290					try {
13291						sel.removeAllRanges();
13292						sel.addRange(rng);
13293					} catch (ex) {
13294						// IE might throw errors here if the editor is within a hidden container and selection is changed
13295					}
13296
13297					// Forward is set to false and we have an extend function
13298					if (forward === false && sel.extend) {
13299						sel.collapse(rng.endContainer, rng.endOffset);
13300						sel.extend(rng.startContainer, rng.startOffset);
13301					}
13302
13303					// adding range isn't always successful so we need to check range count otherwise an exception can occur
13304					self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
13305				}
13306			} else {
13307				// Is W3C Range fake range on IE
13308				if (rng.cloneRange) {
13309					try {
13310						self.tridentSel.addRange(rng);
13311						return;
13312					} catch (ex) {
13313						//IE9 throws an error here if called before selection is placed in the editor
13314					}
13315				}
13316			}
13317		},
13318
13319		/**
13320		 * Sets the current selection to the specified DOM element.
13321		 *
13322		 * @method setNode
13323		 * @param {Element} elm Element to set as the contents of the selection.
13324		 * @return {Element} Returns the element that got passed in.
13325		 * @example
13326		 * // Inserts a DOM node at current selection/caret location
13327		 * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
13328		 */
13329		setNode: function(elm) {
13330			var self = this;
13331
13332			self.setContent(self.dom.getOuterHTML(elm));
13333
13334			return elm;
13335		},
13336
13337		/**
13338		 * Returns the currently selected element or the common ancestor element for both start and end of the selection.
13339		 *
13340		 * @method getNode
13341		 * @return {Element} Currently selected element or common ancestor element.
13342		 * @example
13343		 * // Alerts the currently selected elements node name
13344		 * alert(tinymce.activeEditor.selection.getNode().nodeName);
13345		 */
13346		getNode: function() {
13347			var self = this, rng = self.getRng(), elm;
13348			var startContainer = rng.startContainer, endContainer = rng.endContainer;
13349			var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot();
13350
13351			function skipEmptyTextNodes(node, forwards) {
13352				var orig = node;
13353
13354				while (node && node.nodeType === 3 && node.length === 0) {
13355					node = forwards ? node.nextSibling : node.previousSibling;
13356				}
13357
13358				return node || orig;
13359			}
13360
13361			// Range maybe lost after the editor is made visible again
13362			if (!rng) {
13363				return root;
13364			}
13365
13366			if (rng.setStart) {
13367				elm = rng.commonAncestorContainer;
13368
13369				// Handle selection a image or other control like element such as anchors
13370				if (!rng.collapsed) {
13371					if (startContainer == endContainer) {
13372						if (endOffset - startOffset < 2) {
13373							if (startContainer.hasChildNodes()) {
13374								elm = startContainer.childNodes[startOffset];
13375							}
13376						}
13377					}
13378
13379					// If the anchor node is a element instead of a text node then return this element
13380					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
13381					//	return sel.anchorNode.childNodes[sel.anchorOffset];
13382
13383					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
13384					// This happens when you double click an underlined word in FireFox.
13385					if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
13386						if (startContainer.length === startOffset) {
13387							startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
13388						} else {
13389							startContainer = startContainer.parentNode;
13390						}
13391
13392						if (endOffset === 0) {
13393							endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
13394						} else {
13395							endContainer = endContainer.parentNode;
13396						}
13397
13398						if (startContainer && startContainer === endContainer) {
13399							return startContainer;
13400						}
13401					}
13402				}
13403
13404				if (elm && elm.nodeType == 3) {
13405					return elm.parentNode;
13406				}
13407
13408				return elm;
13409			}
13410
13411			elm = rng.item ? rng.item(0) : rng.parentElement();
13412
13413			// IE 7 might return elements outside the iframe
13414			if (elm.ownerDocument !== self.win.document) {
13415				elm = root;
13416			}
13417
13418			return elm;
13419		},
13420
13421		getSelectedBlocks: function(startElm, endElm) {
13422			var self = this, dom = self.dom, node, root, selectedBlocks = [];
13423
13424			root = dom.getRoot();
13425			startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
13426			endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
13427
13428			if (startElm && startElm != root) {
13429				selectedBlocks.push(startElm);
13430			}
13431
13432			if (startElm && endElm && startElm != endElm) {
13433				node = startElm;
13434
13435				var walker = new TreeWalker(startElm, root);
13436				while ((node = walker.next()) && node != endElm) {
13437					if (dom.isBlock(node)) {
13438						selectedBlocks.push(node);
13439					}
13440				}
13441			}
13442
13443			if (endElm && startElm != endElm && endElm != root) {
13444				selectedBlocks.push(endElm);
13445			}
13446
13447			return selectedBlocks;
13448		},
13449
13450		isForward: function() {
13451			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
13452
13453			// No support for selection direction then always return true
13454			if (!sel || !sel.anchorNode || !sel.focusNode) {
13455				return true;
13456			}
13457
13458			anchorRange = dom.createRng();
13459			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
13460			anchorRange.collapse(true);
13461
13462			focusRange = dom.createRng();
13463			focusRange.setStart(sel.focusNode, sel.focusOffset);
13464			focusRange.collapse(true);
13465
13466			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
13467		},
13468
13469		normalize: function() {
13470			var self = this, rng = self.getRng();
13471
13472			if (!isIE && new RangeUtils(self.dom).normalize(rng)) {
13473				self.setRng(rng, self.isForward());
13474			}
13475
13476			return rng;
13477		},
13478
13479		/**
13480		 * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback.
13481		 *
13482		 * @method selectorChanged
13483		 * @param {String} selector CSS selector to check for.
13484		 * @param {function} callback Callback with state and args when the selector is matches or not.
13485		 */
13486		selectorChanged: function(selector, callback) {
13487			var self = this, currentSelectors;
13488
13489			if (!self.selectorChangedData) {
13490				self.selectorChangedData = {};
13491				currentSelectors = {};
13492
13493				self.editor.on('NodeChange', function(e) {
13494					var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
13495
13496					// Check for new matching selectors
13497					each(self.selectorChangedData, function(callbacks, selector) {
13498						each(parents, function(node) {
13499							if (dom.is(node, selector)) {
13500								if (!currentSelectors[selector]) {
13501									// Execute callbacks
13502									each(callbacks, function(callback) {
13503										callback(true, {node: node, selector: selector, parents: parents});
13504									});
13505
13506									currentSelectors[selector] = callbacks;
13507								}
13508
13509								matchedSelectors[selector] = callbacks;
13510								return false;
13511							}
13512						});
13513					});
13514
13515					// Check if current selectors still match
13516					each(currentSelectors, function(callbacks, selector) {
13517						if (!matchedSelectors[selector]) {
13518							delete currentSelectors[selector];
13519
13520							each(callbacks, function(callback) {
13521								callback(false, {node: node, selector: selector, parents: parents});
13522							});
13523						}
13524					});
13525				});
13526			}
13527
13528			// Add selector listeners
13529			if (!self.selectorChangedData[selector]) {
13530				self.selectorChangedData[selector] = [];
13531			}
13532
13533			self.selectorChangedData[selector].push(callback);
13534
13535			return self;
13536		},
13537
13538		getScrollContainer: function() {
13539			var scrollContainer, node = this.dom.getRoot();
13540
13541			while (node && node.nodeName != 'BODY') {
13542				if (node.scrollHeight > node.clientHeight) {
13543					scrollContainer = node;
13544					break;
13545				}
13546
13547				node = node.parentNode;
13548			}
13549
13550			return scrollContainer;
13551		},
13552
13553		scrollIntoView: function(elm) {
13554			var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH;
13555
13556			function getPos(elm) {
13557				var x = 0, y = 0;
13558
13559				var offsetParent = elm;
13560				while (offsetParent && offsetParent.nodeType) {
13561					x += offsetParent.offsetLeft || 0;
13562					y += offsetParent.offsetTop || 0;
13563					offsetParent = offsetParent.offsetParent;
13564				}
13565
13566				return {x: x, y: y};
13567			}
13568
13569			if (root.nodeName != 'BODY') {
13570				var scrollContainer = self.getScrollContainer();
13571				if (scrollContainer) {
13572					y = getPos(elm).y - getPos(scrollContainer).y;
13573					viewPortH = scrollContainer.clientHeight;
13574					viewPortY = scrollContainer.scrollTop;
13575					if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
13576						scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
13577					}
13578
13579					return;
13580				}
13581			}
13582
13583			viewPort = dom.getViewPort(self.editor.getWin());
13584			y = dom.getPos(elm).y;
13585			viewPortY = viewPort.y;
13586			viewPortH = viewPort.h;
13587			if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
13588				self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
13589			}
13590		},
13591
13592		placeCaretAt: function(clientX, clientY) {
13593			var doc = this.editor.getDoc(), rng, point;
13594
13595			if (doc.caretPositionFromPoint) {
13596				point = doc.caretPositionFromPoint(clientX, clientY);
13597				rng = doc.createRange();
13598				rng.setStart(point.offsetNode, point.offset);
13599				rng.collapse(true);
13600			} else if (doc.caretRangeFromPoint) {
13601				rng = doc.caretRangeFromPoint(clientX, clientY);
13602			} else if (doc.body.createTextRange) {
13603				rng = doc.body.createTextRange();
13604
13605				try {
13606					rng.moveToPoint(clientX, clientY);
13607					rng.collapse(true);
13608				} catch (ex) {
13609					rng.collapse(clientY < doc.body.clientHeight);
13610				}
13611			}
13612
13613			this.setRng(rng);
13614		},
13615
13616		_moveEndPoint: function(rng, node, start) {
13617			var root = node, walker = new TreeWalker(node, root);
13618			var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
13619
13620			do {
13621				// Text node
13622				if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
13623					if (start) {
13624						rng.setStart(node, 0);
13625					} else {
13626						rng.setEnd(node, node.nodeValue.length);
13627					}
13628
13629					return;
13630				}
13631
13632				// BR/IMG/INPUT elements but not table cells
13633				if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) {
13634					if (start) {
13635						rng.setStartBefore(node);
13636					} else {
13637						if (node.nodeName == 'BR') {
13638							rng.setEndBefore(node);
13639						} else {
13640							rng.setEndAfter(node);
13641						}
13642					}
13643
13644					return;
13645				}
13646
13647				// Found empty text block old IE can place the selection inside those
13648				if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
13649					if (start) {
13650						rng.setStart(node, 0);
13651					} else {
13652						rng.setEnd(node, 0);
13653					}
13654
13655					return;
13656				}
13657			} while ((node = (start ? walker.next() : walker.prev())));
13658
13659			// Failed to find any text node or other suitable location then move to the root of body
13660			if (root.nodeName == 'BODY') {
13661				if (start) {
13662					rng.setStart(root, 0);
13663				} else {
13664					rng.setEnd(root, root.childNodes.length);
13665				}
13666			}
13667		},
13668
13669		destroy: function() {
13670			this.win = null;
13671			this.controlSelection.destroy();
13672		}
13673	};
13674
13675	return Selection;
13676});
13677
13678// Included from: js/tinymce/classes/dom/ElementUtils.js
13679
13680/**
13681 * ElementUtils.js
13682 *
13683 * Copyright, Moxiecode Systems AB
13684 * Released under LGPL License.
13685 *
13686 * License: http://www.tinymce.com/license
13687 * Contributing: http://www.tinymce.com/contributing
13688 */
13689
13690/**
13691 * Utility class for various element specific functions.
13692 *
13693 * @private
13694 */
13695define("tinymce/dom/ElementUtils", [
13696	"tinymce/dom/BookmarkManager",
13697	"tinymce/util/Tools"
13698], function(BookmarkManager, Tools) {
13699	var each = Tools.each;
13700
13701	function ElementUtils(dom) {
13702		/**
13703		 * Compares two nodes and checks if it's attributes and styles matches.
13704		 * This doesn't compare classes as items since their order is significant.
13705		 *
13706		 * @method compare
13707		 * @param {Node} node1 First node to compare with.
13708		 * @param {Node} node2 Second node to compare with.
13709		 * @return {boolean} True/false if the nodes are the same or not.
13710		 */
13711		this.compare = function(node1, node2) {
13712			// Not the same name
13713			if (node1.nodeName != node2.nodeName) {
13714				return false;
13715			}
13716
13717			/**
13718			 * Returns all the nodes attributes excluding internal ones, styles and classes.
13719			 *
13720			 * @private
13721			 * @param {Node} node Node to get attributes from.
13722			 * @return {Object} Name/value object with attributes and attribute values.
13723			 */
13724			function getAttribs(node) {
13725				var attribs = {};
13726
13727				each(dom.getAttribs(node), function(attr) {
13728					var name = attr.nodeName.toLowerCase();
13729
13730					// Don't compare internal attributes or style
13731					if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
13732						attribs[name] = dom.getAttrib(node, name);
13733					}
13734				});
13735
13736				return attribs;
13737			}
13738
13739			/**
13740			 * Compares two objects checks if it's key + value exists in the other one.
13741			 *
13742			 * @private
13743			 * @param {Object} obj1 First object to compare.
13744			 * @param {Object} obj2 Second object to compare.
13745			 * @return {boolean} True/false if the objects matches or not.
13746			 */
13747			function compareObjects(obj1, obj2) {
13748				var value, name;
13749
13750				for (name in obj1) {
13751					// Obj1 has item obj2 doesn't have
13752					if (obj1.hasOwnProperty(name)) {
13753						value = obj2[name];
13754
13755						// Obj2 doesn't have obj1 item
13756						if (typeof value == "undefined") {
13757							return false;
13758						}
13759
13760						// Obj2 item has a different value
13761						if (obj1[name] != value) {
13762							return false;
13763						}
13764
13765						// Delete similar value
13766						delete obj2[name];
13767					}
13768				}
13769
13770				// Check if obj 2 has something obj 1 doesn't have
13771				for (name in obj2) {
13772					// Obj2 has item obj1 doesn't have
13773					if (obj2.hasOwnProperty(name)) {
13774						return false;
13775					}
13776				}
13777
13778				return true;
13779			}
13780
13781			// Attribs are not the same
13782			if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
13783				return false;
13784			}
13785
13786			// Styles are not the same
13787			if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
13788				return false;
13789			}
13790
13791			return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
13792		};
13793	}
13794
13795	return ElementUtils;
13796});
13797
13798// Included from: js/tinymce/classes/fmt/Preview.js
13799
13800/**
13801 * Preview.js
13802 *
13803 * Copyright, Moxiecode Systems AB
13804 * Released under LGPL License.
13805 *
13806 * License: http://www.tinymce.com/license
13807 * Contributing: http://www.tinymce.com/contributing
13808 */
13809
13810/**
13811 * Internal class for generating previews styles for formats.
13812 *
13813 * Example:
13814 *  Preview.getCssText(editor, 'bold');
13815 *
13816 * @class tinymce.fmt.Preview
13817 * @private
13818 */
13819define("tinymce/fmt/Preview", [
13820	"tinymce/util/Tools"
13821], function(Tools) {
13822	var each = Tools.each;
13823
13824	function getCssText(editor, format) {
13825		var name, previewElm, dom = editor.dom;
13826		var previewCss = '', parentFontSize, previewStyles;
13827
13828		previewStyles = editor.settings.preview_styles;
13829
13830		// No preview forced
13831		if (previewStyles === false) {
13832			return '';
13833		}
13834
13835		// Default preview
13836		if (!previewStyles) {
13837			previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
13838				'text-transform color background-color border border-radius outline text-shadow';
13839		}
13840
13841		// Removes any variables since these can't be previewed
13842		function removeVars(val) {
13843			return val.replace(/%(\w+)/g, '');
13844		}
13845
13846		// Create block/inline element to use for preview
13847		if (typeof(format) == "string") {
13848			format = editor.formatter.get(format);
13849			if (!format) {
13850				return;
13851			}
13852
13853			format = format[0];
13854		}
13855
13856		name = format.block || format.inline || 'span';
13857		previewElm = dom.create(name);
13858
13859		// Add format styles to preview element
13860		each(format.styles, function(value, name) {
13861			value = removeVars(value);
13862
13863			if (value) {
13864				dom.setStyle(previewElm, name, value);
13865			}
13866		});
13867
13868		// Add attributes to preview element
13869		each(format.attributes, function(value, name) {
13870			value = removeVars(value);
13871
13872			if (value) {
13873				dom.setAttrib(previewElm, name, value);
13874			}
13875		});
13876
13877		// Add classes to preview element
13878		each(format.classes, function(value) {
13879			value = removeVars(value);
13880
13881			if (!dom.hasClass(previewElm, value)) {
13882				dom.addClass(previewElm, value);
13883			}
13884		});
13885
13886		editor.fire('PreviewFormats');
13887
13888		// Add the previewElm outside the visual area
13889		dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
13890		editor.getBody().appendChild(previewElm);
13891
13892		// Get parent container font size so we can compute px values out of em/% for older IE:s
13893		parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
13894		parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
13895
13896		each(previewStyles.split(' '), function(name) {
13897			var value = dom.getStyle(previewElm, name, true);
13898
13899			// If background is transparent then check if the body has a background color we can use
13900			if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
13901				value = dom.getStyle(editor.getBody(), name, true);
13902
13903				// Ignore white since it's the default color, not the nicest fix
13904				// TODO: Fix this by detecting runtime style
13905				if (dom.toHex(value).toLowerCase() == '#ffffff') {
13906					return;
13907				}
13908			}
13909
13910			if (name == 'color') {
13911				// Ignore black since it's the default color, not the nicest fix
13912				// TODO: Fix this by detecting runtime style
13913				if (dom.toHex(value).toLowerCase() == '#000000') {
13914					return;
13915				}
13916			}
13917
13918			// Old IE won't calculate the font size so we need to do that manually
13919			if (name == 'font-size') {
13920				if (/em|%$/.test(value)) {
13921					if (parentFontSize === 0) {
13922						return;
13923					}
13924
13925					// Convert font size from em/% to px
13926					value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
13927					value = (value * parentFontSize) + 'px';
13928				}
13929			}
13930
13931			if (name == "border" && value) {
13932				previewCss += 'padding:0 2px;';
13933			}
13934
13935			previewCss += name + ':' + value + ';';
13936		});
13937
13938		editor.fire('AfterPreviewFormats');
13939
13940		//previewCss += 'line-height:normal';
13941
13942		dom.remove(previewElm);
13943
13944		return previewCss;
13945	}
13946
13947	return {
13948		getCssText: getCssText
13949	};
13950});
13951
13952// Included from: js/tinymce/classes/Formatter.js
13953
13954/**
13955 * Formatter.js
13956 *
13957 * Copyright, Moxiecode Systems AB
13958 * Released under LGPL License.
13959 *
13960 * License: http://www.tinymce.com/license
13961 * Contributing: http://www.tinymce.com/contributing
13962 */
13963
13964/**
13965 * Text formatter engine class. This class is used to apply formats like bold, italic, font size
13966 * etc to the current selection or specific nodes. This engine was build to replace the browsers
13967 * default formatting logic for execCommand due to it's inconsistent and buggy behavior.
13968 *
13969 * @class tinymce.Formatter
13970 * @example
13971 *  tinymce.activeEditor.formatter.register('mycustomformat', {
13972 *    inline: 'span',
13973 *    styles: {color: '#ff0000'}
13974 *  });
13975 *
13976 *  tinymce.activeEditor.formatter.apply('mycustomformat');
13977 */
13978define("tinymce/Formatter", [
13979	"tinymce/dom/TreeWalker",
13980	"tinymce/dom/RangeUtils",
13981	"tinymce/dom/BookmarkManager",
13982	"tinymce/dom/ElementUtils",
13983	"tinymce/util/Tools",
13984	"tinymce/fmt/Preview"
13985], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) {
13986	/**
13987	 * Constructs a new formatter instance.
13988	 *
13989	 * @constructor Formatter
13990	 * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
13991	 */
13992	return function(ed) {
13993		var formats = {},
13994			dom = ed.dom,
13995			selection = ed.selection,
13996			rangeUtils = new RangeUtils(dom),
13997			isValid = ed.schema.isValidChild,
13998			isBlock = dom.isBlock,
13999			forcedRootBlock = ed.settings.forced_root_block,
14000			nodeIndex = dom.nodeIndex,
14001			INVISIBLE_CHAR = '\uFEFF',
14002			MCE_ATTR_RE = /^(src|href|style)$/,
14003			FALSE = false,
14004			TRUE = true,
14005			formatChangeData,
14006			undef,
14007			getContentEditable = dom.getContentEditable,
14008			disableCaretContainer,
14009			markCaretContainersBogus,
14010			isBookmarkNode = BookmarkManager.isBookmarkNode;
14011
14012		var each = Tools.each,
14013			grep = Tools.grep,
14014			walk = Tools.walk,
14015			extend = Tools.extend;
14016
14017		function isTextBlock(name) {
14018			if (name.nodeType) {
14019				name = name.nodeName;
14020			}
14021
14022			return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
14023		}
14024
14025		function getParents(node, selector) {
14026			return dom.getParents(node, selector, dom.getRoot());
14027		}
14028
14029		function isCaretNode(node) {
14030			return node.nodeType === 1 && node.id === '_mce_caret';
14031		}
14032
14033		function defaultFormats() {
14034			register({
14035				valigntop: [
14036					{selector: 'td,th', styles: {'verticalAlign': 'top'}}
14037				],
14038
14039				valignmiddle: [
14040					{selector: 'td,th', styles: {'verticalAlign': 'middle'}}
14041				],
14042
14043				valignbottom: [
14044					{selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
14045				],
14046
14047				alignleft: [
14048					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
14049					{selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
14050				],
14051
14052				aligncenter: [
14053					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
14054					{selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
14055					{selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
14056				],
14057
14058				alignright: [
14059					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
14060					{selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
14061				],
14062
14063				alignjustify: [
14064					{selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
14065				],
14066
14067				bold: [
14068					{inline: 'strong', remove: 'all'},
14069					{inline: 'span', styles: {fontWeight: 'bold'}},
14070					{inline: 'b', remove: 'all'}
14071				],
14072
14073				italic: [
14074					{inline: 'em', remove: 'all'},
14075					{inline: 'span', styles: {fontStyle: 'italic'}},
14076					{inline: 'i', remove: 'all'}
14077				],
14078
14079				underline: [
14080					{inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
14081					{inline: 'u', remove: 'all'}
14082				],
14083
14084				strikethrough: [
14085					{inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
14086					{inline: 'strike', remove: 'all'}
14087				],
14088
14089				forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true},
14090				hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true},
14091				fontname: {inline: 'span', styles: {fontFamily: '%value'}},
14092				fontsize: {inline: 'span', styles: {fontSize: '%value'}},
14093				fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
14094				blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
14095				tabledataresponsive: {block: 'div', wrapper: 1, classes: 'tabledataresponsive'},
14096				subscript: {inline: 'sub'},
14097				superscript: {inline: 'sup'},
14098				code: {inline: 'code'},
14099
14100				link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
14101					onmatch: function() {
14102						return true;
14103					},
14104
14105					onformat: function(elm, fmt, vars) {
14106						each(vars, function(value, key) {
14107							dom.setAttrib(elm, key, value);
14108						});
14109					}
14110				},
14111
14112				removeformat: [
14113					{
14114						selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q',
14115						remove: 'all',
14116						split: true,
14117						expand: false,
14118						block_expand: true,
14119						deep: true
14120					},
14121					{selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
14122					{selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
14123				]
14124			});
14125
14126			// Register default block formats
14127			each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
14128				register(name, {block: name, remove: 'all'});
14129			});
14130
14131			// Register user defined formats
14132			register(ed.settings.formats);
14133		}
14134
14135		function addKeyboardShortcuts() {
14136			// Add some inline shortcuts
14137			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
14138			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
14139			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
14140
14141			// BlockFormat shortcuts keys
14142			for (var i = 1; i <= 6; i++) {
14143				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
14144			}
14145
14146			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
14147			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
14148			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
14149		}
14150
14151		// Public functions
14152
14153		/**
14154		 * Returns the format by name or all formats if no name is specified.
14155		 *
14156		 * @method get
14157		 * @param {String} name Optional name to retrive by.
14158		 * @return {Array/Object} Array/Object with all registred formats or a specific format.
14159		 */
14160		function get(name) {
14161			return name ? formats[name] : formats;
14162		}
14163
14164		/**
14165		 * Registers a specific format by name.
14166		 *
14167		 * @method register
14168		 * @param {Object/String} name Name of the format for example "bold".
14169		 * @param {Object/Array} format Optional format object or array of format variants
14170		 * can only be omitted if the first arg is an object.
14171		 */
14172		function register(name, format) {
14173			if (name) {
14174				if (typeof(name) !== 'string') {
14175					each(name, function(format, name) {
14176						register(name, format);
14177					});
14178				} else {
14179					// Force format into array and add it to internal collection
14180					format = format.length ? format : [format];
14181
14182					each(format, function(format) {
14183						// Set deep to false by default on selector formats this to avoid removing
14184						// alignment on images inside paragraphs when alignment is changed on paragraphs
14185						if (format.deep === undef) {
14186							format.deep = !format.selector;
14187						}
14188
14189						// Default to true
14190						if (format.split === undef) {
14191							format.split = !format.selector || format.inline;
14192						}
14193
14194						// Default to true
14195						if (format.remove === undef && format.selector && !format.inline) {
14196							format.remove = 'none';
14197						}
14198
14199						// Mark format as a mixed format inline + block level
14200						if (format.selector && format.inline) {
14201							format.mixed = true;
14202							format.block_expand = true;
14203						}
14204
14205						// Split classes if needed
14206						if (typeof(format.classes) === 'string') {
14207							format.classes = format.classes.split(/\s+/);
14208						}
14209					});
14210
14211					formats[name] = format;
14212				}
14213			}
14214		}
14215
14216		/**
14217		 * Unregister a specific format by name.
14218		 *
14219		 * @method unregister
14220		 * @param {String} name Name of the format for example "bold".
14221		 */
14222		function unregister(name) {
14223			if (name && formats[name]) {
14224				delete formats[name];
14225			}
14226
14227			return formats;
14228		}
14229
14230		function getTextDecoration(node) {
14231			var decoration;
14232
14233			ed.dom.getParent(node, function(n) {
14234				decoration = ed.dom.getStyle(n, 'text-decoration');
14235				return decoration && decoration !== 'none';
14236			});
14237
14238			return decoration;
14239		}
14240
14241		function processUnderlineAndColor(node) {
14242			var textDecoration;
14243			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14244				textDecoration = getTextDecoration(node.parentNode);
14245				if (ed.dom.getStyle(node, 'color') && textDecoration) {
14246					ed.dom.setStyle(node, 'text-decoration', textDecoration);
14247				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14248					ed.dom.setStyle(node, 'text-decoration', null);
14249				}
14250			}
14251		}
14252
14253		/**
14254		 * Applies the specified format to the current selection or specified node.
14255		 *
14256		 * @method apply
14257		 * @param {String} name Name of format to apply.
14258		 * @param {Object} vars Optional list of variables to replace within format before applying it.
14259		 * @param {Node} node Optional node to apply the format to defaults to current selection.
14260		 */
14261		function apply(name, vars, node) {
14262			var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
14263
14264			function setElementFormat(elm, fmt) {
14265				fmt = fmt || format;
14266
14267				if (elm) {
14268					if (fmt.onformat) {
14269						fmt.onformat(elm, fmt, vars, node);
14270					}
14271
14272					each(fmt.styles, function(value, name) {
14273						dom.setStyle(elm, name, replaceVars(value, vars));
14274					});
14275
14276					// Needed for the WebKit span spam bug
14277					// TODO: Remove this once WebKit/Blink fixes this
14278					if (fmt.styles) {
14279						var styleVal = dom.getAttrib(elm, 'style');
14280
14281						if (styleVal) {
14282							elm.setAttribute('data-mce-style', styleVal);
14283						}
14284					}
14285
14286					each(fmt.attributes, function(value, name) {
14287						dom.setAttrib(elm, name, replaceVars(value, vars));
14288					});
14289
14290					each(fmt.classes, function(value) {
14291						value = replaceVars(value, vars);
14292
14293						if (!dom.hasClass(elm, value)) {
14294							dom.addClass(elm, value);
14295						}
14296					});
14297				}
14298			}
14299
14300			function adjustSelectionToVisibleSelection() {
14301				function findSelectionEnd(start, end) {
14302					var walker = new TreeWalker(end);
14303					for (node = walker.current(); node; node = walker.prev()) {
14304						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
14305							return node;
14306						}
14307					}
14308				}
14309
14310				// Adjust selection so that a end container with a end offset of zero is not included in the selection
14311				// as this isn't visible to the user.
14312				var rng = ed.selection.getRng();
14313				var start = rng.startContainer;
14314				var end = rng.endContainer;
14315
14316				if (start != end && rng.endOffset === 0) {
14317					var newEnd = findSelectionEnd(start, end);
14318					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
14319
14320					rng.setEnd(newEnd, endOffset);
14321				}
14322
14323				return rng;
14324			}
14325
14326			function applyRngStyle(rng, bookmark, node_specific) {
14327				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
14328
14329				// Setup wrapper element
14330				wrapName = format.inline || format.block;
14331				wrapElm = dom.create(wrapName);
14332				setElementFormat(wrapElm);
14333
14334				rangeUtils.walk(rng, function(nodes) {
14335					var currentWrapElm;
14336
14337					/**
14338					 * Process a list of nodes wrap them.
14339					 */
14340					function process(node) {
14341						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
14342
14343						lastContentEditable = contentEditable;
14344						nodeName = node.nodeName.toLowerCase();
14345						parentName = node.parentNode.nodeName.toLowerCase();
14346
14347						// Node has a contentEditable value
14348						if (node.nodeType === 1 && getContentEditable(node)) {
14349							lastContentEditable = contentEditable;
14350							contentEditable = getContentEditable(node) === "true";
14351							hasContentEditableState = true; // We don't want to wrap the container only it's children
14352						}
14353
14354						// Stop wrapping on br elements
14355						if (isEq(nodeName, 'br')) {
14356							currentWrapElm = 0;
14357
14358							// Remove any br elements when we wrap things
14359							if (format.block) {
14360								dom.remove(node);
14361							}
14362
14363							return;
14364						}
14365
14366						// If node is wrapper type
14367						if (format.wrapper && matchNode(node, name, vars)) {
14368							currentWrapElm = 0;
14369							return;
14370						}
14371
14372						// Can we rename the block
14373						// TODO: Break this if up, too complex
14374						if (contentEditable && !hasContentEditableState && format.block &&
14375							!format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
14376							node = dom.rename(node, wrapName);
14377							setElementFormat(node);
14378							newWrappers.push(node);
14379							currentWrapElm = 0;
14380							return;
14381						}
14382
14383						// Handle selector patterns
14384						if (format.selector) {
14385							// Look for matching formats
14386							each(formatList, function(format) {
14387								// Check collapsed state if it exists
14388								if ('collapsed' in format && format.collapsed !== isCollapsed) {
14389									return;
14390								}
14391
14392								if (dom.is(node, format.selector) && !isCaretNode(node)) {
14393									setElementFormat(node, format);
14394									found = true;
14395								}
14396							});
14397
14398							// Continue processing if a selector match wasn't found and a inline element is defined
14399							if (!format.inline || found) {
14400								currentWrapElm = 0;
14401								return;
14402							}
14403						}
14404
14405						// Is it valid to wrap this item
14406						// TODO: Break this if up, too complex
14407						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
14408								!(!node_specific && node.nodeType === 3 &&
14409								node.nodeValue.length === 1 &&
14410								node.nodeValue.charCodeAt(0) === 65279) &&
14411								!isCaretNode(node) &&
14412								(!format.inline || !isBlock(node))) {
14413							// Start wrapping
14414							if (!currentWrapElm) {
14415								// Wrap the node
14416								currentWrapElm = dom.clone(wrapElm, FALSE);
14417								node.parentNode.insertBefore(currentWrapElm, node);
14418								newWrappers.push(currentWrapElm);
14419							}
14420
14421							currentWrapElm.appendChild(node);
14422						} else {
14423							// Start a new wrapper for possible children
14424							currentWrapElm = 0;
14425
14426							each(grep(node.childNodes), process);
14427
14428							if (hasContentEditableState) {
14429								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
14430							}
14431
14432							// End the last wrapper
14433							currentWrapElm = 0;
14434						}
14435					}
14436
14437					// Process siblings from range
14438					each(nodes, process);
14439				});
14440
14441				// Apply formats to links as well to get the color of the underline to change as well
14442				if (format.links === true) {
14443					each(newWrappers, function(node) {
14444						function process(node) {
14445							if (node.nodeName === 'A') {
14446								setElementFormat(node, format);
14447							}
14448
14449							each(grep(node.childNodes), process);
14450						}
14451
14452						process(node);
14453					});
14454				}
14455
14456				// Cleanup
14457				each(newWrappers, function(node) {
14458					var childCount;
14459
14460					function getChildCount(node) {
14461						var count = 0;
14462
14463						each(node.childNodes, function(node) {
14464							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
14465								count++;
14466							}
14467						});
14468
14469						return count;
14470					}
14471
14472					function mergeStyles(node) {
14473						var child, clone;
14474
14475						each(node.childNodes, function(node) {
14476							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
14477								child = node;
14478								return FALSE; // break loop
14479							}
14480						});
14481
14482						// If child was found and of the same type as the current node
14483						if (child && !isBookmarkNode(child) && matchName(child, format)) {
14484							clone = dom.clone(child, FALSE);
14485							setElementFormat(clone);
14486
14487							dom.replace(clone, node, TRUE);
14488							dom.remove(child, 1);
14489						}
14490
14491						return clone || node;
14492					}
14493
14494					childCount = getChildCount(node);
14495
14496					// Remove empty nodes but only if there is multiple wrappers and they are not block
14497					// elements so never remove single <h1></h1> since that would remove the
14498					// currrent empty block element where the caret is at
14499					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
14500						dom.remove(node, 1);
14501						return;
14502					}
14503
14504					if (format.inline || format.wrapper) {
14505						// Merges the current node with it's children of similar type to reduce the number of elements
14506						if (!format.exact && childCount === 1) {
14507							node = mergeStyles(node);
14508						}
14509
14510						// Remove/merge children
14511						each(formatList, function(format) {
14512							// Merge all children of similar type will move styles from child to parent
14513							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
14514							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
14515							each(dom.select(format.inline, node), function(child) {
14516								if (isBookmarkNode(child)) {
14517									return;
14518								}
14519
14520								removeFormat(format, vars, child, format.exact ? child : null);
14521							});
14522						});
14523
14524						// Remove child if direct parent is of same type
14525						if (matchNode(node.parentNode, name, vars)) {
14526							dom.remove(node, 1);
14527							node = 0;
14528							return TRUE;
14529						}
14530
14531						// Look for parent with similar style format
14532						if (format.merge_with_parents) {
14533							dom.getParent(node.parentNode, function(parent) {
14534								if (matchNode(parent, name, vars)) {
14535									dom.remove(node, 1);
14536									node = 0;
14537									return TRUE;
14538								}
14539							});
14540						}
14541
14542						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
14543						if (node && format.merge_siblings !== false) {
14544							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
14545							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
14546						}
14547					}
14548				});
14549			}
14550
14551			if (format) {
14552				if (node) {
14553					if (node.nodeType) {
14554						rng = dom.createRng();
14555						rng.setStartBefore(node);
14556						rng.setEndAfter(node);
14557						applyRngStyle(expandRng(rng, formatList), null, true);
14558					} else {
14559						applyRngStyle(node, null, true);
14560					}
14561				} else {
14562					if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
14563						// Obtain selection node before selection is unselected by applyRngStyle()
14564						var curSelNode = ed.selection.getNode();
14565
14566						// If the formats have a default block and we can't find a parent block then
14567						// start wrapping it with a DIV this is for forced_root_blocks: false
14568						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
14569						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
14570							apply(formatList[0].defaultBlock);
14571						}
14572
14573						// Apply formatting to selection
14574						ed.selection.setRng(adjustSelectionToVisibleSelection());
14575						bookmark = selection.getBookmark();
14576						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
14577
14578						// Colored nodes should be underlined so that the color of the underline matches the text color.
14579						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
14580							walk(curSelNode, processUnderlineAndColor, 'childNodes');
14581							processUnderlineAndColor(curSelNode);
14582						}
14583
14584						selection.moveToBookmark(bookmark);
14585						moveStart(selection.getRng(TRUE));
14586						ed.nodeChanged();
14587					} else {
14588						performCaretAction('apply', name, vars);
14589					}
14590				}
14591			}
14592		}
14593
14594		/**
14595		 * Removes the specified format from the current selection or specified node.
14596		 *
14597		 * @method remove
14598		 * @param {String} name Name of format to remove.
14599		 * @param {Object} vars Optional list of variables to replace within format before removing it.
14600		 * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
14601		 */
14602		function remove(name, vars, node, similar) {
14603			var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
14604
14605			// Merges the styles for each node
14606			function process(node) {
14607				var children, i, l, lastContentEditable, hasContentEditableState;
14608
14609				// Node has a contentEditable value
14610				if (node.nodeType === 1 && getContentEditable(node)) {
14611					lastContentEditable = contentEditable;
14612					contentEditable = getContentEditable(node) === "true";
14613					hasContentEditableState = true; // We don't want to wrap the container only it's children
14614				}
14615
14616				// Grab the children first since the nodelist might be changed
14617				children = grep(node.childNodes);
14618
14619				// Process current node
14620				if (contentEditable && !hasContentEditableState) {
14621					for (i = 0, l = formatList.length; i < l; i++) {
14622						if (removeFormat(formatList[i], vars, node, node)) {
14623							break;
14624						}
14625					}
14626				}
14627
14628				// Process the children
14629				if (format.deep) {
14630					if (children.length) {
14631						for (i = 0, l = children.length; i < l; i++) {
14632							process(children[i]);
14633						}
14634
14635						if (hasContentEditableState) {
14636							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
14637						}
14638					}
14639				}
14640			}
14641
14642			function findFormatRoot(container) {
14643				var formatRoot;
14644
14645				// Find format root
14646				each(getParents(container.parentNode).reverse(), function(parent) {
14647					var format;
14648
14649					// Find format root element
14650					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
14651						// Is the node matching the format we are looking for
14652						format = matchNode(parent, name, vars, similar);
14653						if (format && format.split !== false) {
14654							formatRoot = parent;
14655						}
14656					}
14657				});
14658
14659				return formatRoot;
14660			}
14661
14662			function wrapAndSplit(formatRoot, container, target, split) {
14663				var parent, clone, lastClone, firstClone, i, formatRootParent;
14664
14665				// Format root found then clone formats and split it
14666				if (formatRoot) {
14667					formatRootParent = formatRoot.parentNode;
14668
14669					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14670						clone = dom.clone(parent, FALSE);
14671
14672						for (i = 0; i < formatList.length; i++) {
14673							if (removeFormat(formatList[i], vars, clone, clone)) {
14674								clone = 0;
14675								break;
14676							}
14677						}
14678
14679						// Build wrapper node
14680						if (clone) {
14681							if (lastClone) {
14682								clone.appendChild(lastClone);
14683							}
14684
14685							if (!firstClone) {
14686								firstClone = clone;
14687							}
14688
14689							lastClone = clone;
14690						}
14691					}
14692
14693					// Never split block elements if the format is mixed
14694					if (split && (!format.mixed || !isBlock(formatRoot))) {
14695						container = dom.split(formatRoot, container);
14696					}
14697
14698					// Wrap container in cloned formats
14699					if (lastClone) {
14700						target.parentNode.insertBefore(lastClone, target);
14701						firstClone.appendChild(target);
14702					}
14703				}
14704
14705				return container;
14706			}
14707
14708			function splitToFormatRoot(container) {
14709				return wrapAndSplit(findFormatRoot(container), container, container, true);
14710			}
14711
14712			function unwrap(start) {
14713				var node = dom.get(start ? '_start' : '_end'),
14714					out = node[start ? 'firstChild' : 'lastChild'];
14715
14716				// If the end is placed within the start the result will be removed
14717				// So this checks if the out node is a bookmark node if it is it
14718				// checks for another more suitable node
14719				if (isBookmarkNode(out)) {
14720					out = out[start ? 'firstChild' : 'lastChild'];
14721				}
14722
14723				dom.remove(node, true);
14724
14725				return out;
14726			}
14727
14728			function removeRngStyle(rng) {
14729				var startContainer, endContainer;
14730				var commonAncestorContainer = rng.commonAncestorContainer;
14731
14732				rng = expandRng(rng, formatList, TRUE);
14733
14734				if (format.split) {
14735					startContainer = getContainer(rng, TRUE);
14736					endContainer = getContainer(rng);
14737
14738					if (startContainer != endContainer) {
14739						// WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
14740						// so let's see if we can use the first child instead
14741						// This will happen if you triple click a table cell and use remove formatting
14742						if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
14743							if (startContainer.nodeName == "TR") {
14744								startContainer = startContainer.firstChild.firstChild || startContainer;
14745							} else {
14746								startContainer = startContainer.firstChild || startContainer;
14747							}
14748						}
14749
14750						// Try to adjust endContainer as well if cells on the same row were selected - bug #6410
14751						if (commonAncestorContainer &&
14752							/^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
14753							/^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) {
14754							endContainer = endContainer.firstChild || endContainer;
14755						}
14756
14757						// Wrap start/end nodes in span element since these might be cloned/moved
14758						startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
14759						endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
14760
14761						// Split start/end
14762						splitToFormatRoot(startContainer);
14763						splitToFormatRoot(endContainer);
14764
14765						// Unwrap start/end to get real elements again
14766						startContainer = unwrap(TRUE);
14767						endContainer = unwrap();
14768					} else {
14769						startContainer = endContainer = splitToFormatRoot(startContainer);
14770					}
14771
14772					// Update range positions since they might have changed after the split operations
14773					rng.startContainer = startContainer.parentNode;
14774					rng.startOffset = nodeIndex(startContainer);
14775					rng.endContainer = endContainer.parentNode;
14776					rng.endOffset = nodeIndex(endContainer) + 1;
14777				}
14778
14779				// Remove items between start/end
14780				rangeUtils.walk(rng, function(nodes) {
14781					each(nodes, function(node) {
14782						process(node);
14783
14784						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14785						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
14786							node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14787							removeFormat({
14788								'deep': false,
14789								'exact': true,
14790								'inline': 'span',
14791								'styles': {
14792									'textDecoration': 'underline'
14793								}
14794							}, null, node);
14795						}
14796					});
14797				});
14798			}
14799
14800			// Handle node
14801			if (node) {
14802				if (node.nodeType) {
14803					rng = dom.createRng();
14804					rng.setStartBefore(node);
14805					rng.setEndAfter(node);
14806					removeRngStyle(rng);
14807				} else {
14808					removeRngStyle(node);
14809				}
14810
14811				return;
14812			}
14813
14814			if (!selection.isCollapsed() || !format.inline || dom.select('td.mce
14814-item-selected,th.mce-item-selected').length) {
14815				bookmark = selection.getBookmark();
14816				removeRngStyle(selection.getRng(TRUE));
14817				selection.moveToBookmark(bookmark);
14818
14819				// Check if start element still has formatting then we are at: "<b>text|</b>text"
14820				// and need to move the start into the next text node
14821				if (format.inline && match(name, vars, selection.getStart())) {
14822					moveStart(selection.getRng(true));
14823				}
14824
14825				ed.nodeChanged();
14826			} else {
14827				performCaretAction('remove', name, vars, similar);
14828			}
14829		}
14830
14831		/**
14832		 * Toggles the specified format on/off.
14833		 *
14834		 * @method toggle
14835		 * @param {String} name Name of format to apply/remove.
14836		 * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
14837		 * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
14838		 */
14839		function toggle(name, vars, node) {
14840			var fmt = get(name);
14841
14842			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
14843				remove(name, vars, node);
14844			} else {
14845				apply(name, vars, node);
14846			}
14847		}
14848
14849		/**
14850		 * Return true/false if the specified node has the specified format.
14851		 *
14852		 * @method matchNode
14853		 * @param {Node} node Node to check the format on.
14854		 * @param {String} name Format name to check.
14855		 * @param {Object} vars Optional list of variables to replace before checking it.
14856		 * @param {Boolean} similar Match format that has similar properties.
14857		 * @return {Object} Returns the format object it matches or undefined if it doesn't match.
14858		 */
14859		function matchNode(node, name, vars, similar) {
14860			var formatList = get(name), format, i, classes;
14861
14862			function matchItems(node, format, item_name) {
14863				var key, value, items = format[item_name], i;
14864
14865				// Custom match
14866				if (format.onmatch) {
14867					return format.onmatch(node, format, item_name);
14868				}
14869
14870				// Check all items
14871				if (items) {
14872					// Non indexed object
14873					if (items.length === undef) {
14874						for (key in items) {
14875							if (items.hasOwnProperty(key)) {
14876								if (item_name === 'attributes') {
14877									value = dom.getAttrib(node, key);
14878								} else {
14879									value = getStyle(node, key);
14880								}
14881
14882								if (similar && !value && !format.exact) {
14883									return;
14884								}
14885
14886								if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
14887									return;
14888								}
14889							}
14890						}
14891					} else {
14892						// Only one match needed for indexed arrays
14893						for (i = 0; i < items.length; i++) {
14894							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
14895								return format;
14896							}
14897						}
14898					}
14899				}
14900
14901				return format;
14902			}
14903
14904			if (formatList && node) {
14905				// Check each format in list
14906				for (i = 0; i < formatList.length; i++) {
14907					format = formatList[i];
14908
14909					// Name name, attributes, styles and classes
14910					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14911						// Match classes
14912						if ((classes = format.classes)) {
14913							for (i = 0; i < classes.length; i++) {
14914								if (!dom.hasClass(node, classes[i])) {
14915									return;
14916								}
14917							}
14918						}
14919
14920						return format;
14921					}
14922				}
14923			}
14924		}
14925
14926		/**
14927		 * Matches the current selection or specified node against the specified format name.
14928		 *
14929		 * @method match
14930		 * @param {String} name Name of format to match.
14931		 * @param {Object} vars Optional list of variables to replace before checking it.
14932		 * @param {Node} node Optional node to check.
14933		 * @return {boolean} true/false if the specified selection/node matches the format.
14934		 */
14935		function match(name, vars, node) {
14936			var startNode;
14937
14938			function matchParents(node) {
14939				var root = dom.getRoot();
14940
14941				if (node === root) {
14942					return false;
14943				}
14944
14945				// Find first node with similar format settings
14946				node = dom.getParent(node, function(node) {
14947					return node.parentNode === root || !!matchNode(node, name, vars, true);
14948				});
14949
14950				// Do an exact check on the similar format element
14951				return matchNode(node, name, vars);
14952			}
14953
14954			// Check specified node
14955			if (node) {
14956				return matchParents(node);
14957			}
14958
14959			// Check selected node
14960			node = selection.getNode();
14961			if (matchParents(node)) {
14962				return TRUE;
14963			}
14964
14965			// Check start node if it's different
14966			startNode = selection.getStart();
14967			if (startNode != node) {
14968				if (matchParents(startNode)) {
14969					return TRUE;
14970				}
14971			}
14972
14973			return FALSE;
14974		}
14975
14976		/**
14977		 * Matches the current selection against the array of formats and returns a new array with matching formats.
14978		 *
14979		 * @method matchAll
14980		 * @param {Array} names Name of format to match.
14981		 * @param {Object} vars Optional list of variables to replace before checking it.
14982		 * @return {Array} Array with matched formats.
14983		 */
14984		function matchAll(names, vars) {
14985			var startElement, matchedFormatNames = [], checkedMap = {};
14986
14987			// Check start of selection for formats
14988			startElement = selection.getStart();
14989			dom.getParent(startElement, function(node) {
14990				var i, name;
14991
14992				for (i = 0; i < names.length; i++) {
14993					name = names[i];
14994
14995					if (!checkedMap[name] && matchNode(node, name, vars)) {
14996						checkedMap[name] = true;
14997						matchedFormatNames.push(name);
14998					}
14999				}
15000			}, dom.getRoot());
15001
15002			return matchedFormatNames;
15003		}
15004
15005		/**
15006		 * Returns true/false if the specified format can be applied to the current selection or not. It
15007		 * will currently only check the state for selector formats, it returns true on all other format types.
15008		 *
15009		 * @method canApply
15010		 * @param {String} name Name of format to check.
15011		 * @return {boolean} true/false if the specified format can be applied to the current selection/node.
15012		 */
15013		function canApply(name) {
15014			var formatList = get(name), startNode, parents, i, x, selector;
15015
15016			if (formatList) {
15017				startNode = selection.getStart();
15018				parents = getParents(startNode);
15019
15020				for (x = formatList.length - 1; x >= 0; x--) {
15021					selector = formatList[x].selector;
15022
15023					// Format is not selector based then always return TRUE
15024					// Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
15025					if (!selector || formatList[x].defaultBlock) {
15026						return TRUE;
15027					}
15028
15029					for (i = parents.length - 1; i >= 0; i--) {
15030						if (dom.is(parents[i], selector)) {
15031							return TRUE;
15032						}
15033					}
15034				}
15035			}
15036
15037			return FALSE;
15038		}
15039
15040		/**
15041		 * Executes the specified callback when the current selection matches the formats or not.
15042		 *
15043		 * @method formatChanged
15044		 * @param {String} formats Comma separated list of formats to check for.
15045		 * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
15046		 * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
15047		 */
15048		function formatChanged(formats, callback, similar) {
15049			var currentFormats;
15050
15051			// Setup format node change logic
15052			if (!formatChangeData) {
15053				formatChangeData = {};
15054				currentFormats = {};
15055
15056				ed.on('NodeChange', function(e) {
15057					var parents = getParents(e.element), matchedFormats = {};
15058
15059					// Ignore bogus nodes like the <a> tag created by moveStart()
15060					parents = Tools.grep(parents, function(node) {
15061						return node.nodeType == 1 && !node.getAttribute('data-mce-bogus');
15062					});
15063
15064					// Check for new formats
15065					each(formatChangeData, function(callbacks, format) {
15066						each(parents, function(node) {
15067							if (matchNode(node, format, {}, callbacks.similar)) {
15068								if (!currentFormats[format]) {
15069									// Execute callbacks
15070									each(callbacks, function(callback) {
15071										callback(true, {node: node, format: format, parents: parents});
15072									});
15073
15074									currentFormats[format] = callbacks;
15075								}
15076
15077								matchedFormats[format] = callbacks;
15078								return false;
15079							}
15080						});
15081					});
15082
15083					// Check if current formats still match
15084					each(currentFormats, function(callbacks, format) {
15085						if (!matchedFormats[format]) {
15086							delete currentFormats[format];
15087
15088							each(callbacks, function(callback) {
15089								callback(false, {node: e.element, format: format, parents: parents});
15090							});
15091						}
15092					});
15093				});
15094			}
15095
15096			// Add format listeners
15097			each(formats.split(','), function(format) {
15098				if (!formatChangeData[format]) {
15099					formatChangeData[format] = [];
15100					formatChangeData[format].similar = similar;
15101				}
15102
15103				formatChangeData[format].push(callback);
15104			});
15105
15106			return this;
15107		}
15108
15109		/**
15110		 * Returns a preview css text for the specified format.
15111		 *
15112		 * @method getCssText
15113		 * @param {String/Object} format Format to generate preview css text for.
15114		 * @return {String} Css text for the specified format.
15115		 * @example
15116		 * var cssText1 = editor.formatter.getCssText('bold');
15117		 * var cssText2 = editor.formatter.getCssText({inline: 'b'});
15118		 */
15119		function getCssText(format) {
15120			return Preview.getCssText(ed, format);
15121		}
15122
15123		// Expose to public
15124		extend(this, {
15125			get: get,
15126			register: register,
15127			unregister: unregister,
15128			apply: apply,
15129			remove: remove,
15130			toggle: toggle,
15131			match: match,
15132			matchAll: matchAll,
15133			matchNode: matchNode,
15134			canApply: canApply,
15135			formatChanged: formatChanged,
15136			getCssText: getCssText
15137		});
15138
15139		// Initialize
15140		defaultFormats();
15141		addKeyboardShortcuts();
15142		ed.on('BeforeGetContent', function(e) {
15143			if (markCaretContainersBogus && e.format != 'raw') {
15144				markCaretContainersBogus();
15145			}
15146		});
15147		ed.on('mouseup keydown', function(e) {
15148			if (disableCaretContainer) {
15149				disableCaretContainer(e);
15150			}
15151		});
15152
15153		// Private functions
15154
15155		/**
15156		 * Checks if the specified nodes name matches the format inline/block or selector.
15157		 *
15158		 * @private
15159		 * @param {Node} node Node to match against the specified format.
15160		 * @param {Object} format Format object o match with.
15161		 * @return {boolean} true/false if the format matches.
15162		 */
15163		function matchName(node, format) {
15164			// Check for inline match
15165			if (isEq(node, format.inline)) {
15166				return TRUE;
15167			}
15168
15169			// Check for block match
15170			if (isEq(node, format.block)) {
15171				return TRUE;
15172			}
15173
15174			// Check for selector match
15175			if (format.selector) {
15176				return node.nodeType == 1 && dom.is(node, format.selector);
15177			}
15178		}
15179
15180		/**
15181		 * Compares two string/nodes regardless of their case.
15182		 *
15183		 * @private
15184		 * @param {String/Node} Node or string to compare.
15185		 * @param {String/Node} Node or string to compare.
15186		 * @return {boolean} True/false if they match.
15187		 */
15188		function isEq(str1, str2) {
15189			str1 = str1 || '';
15190			str2 = str2 || '';
15191
15192			str1 = '' + (str1.nodeName || str1);
15193			str2 = '' + (str2.nodeName || str2);
15194
15195			return str1.toLowerCase() == str2.toLowerCase();
15196		}
15197
15198		/**
15199		 * Returns the style by name on the specified node. This method modifies the style
15200		 * contents to make it more easy to match. This will resolve a few browser issues.
15201		 *
15202		 * @private
15203		 * @param {Node} node to get style from.
15204		 * @param {String} name Style name to get.
15205		 * @return {String} Style item value.
15206		 */
15207		function getStyle(node, name) {
15208			return normalizeStyleValue(dom.getStyle(node, name), name);
15209		}
15210
15211		/**
15212		 * Normalize style value by name. This method modifies the style contents
15213		 * to make it more easy to match. This will resolve a few browser issues.
15214		 *
15215		 * @private
15216		 * @param {Node} node to get style from.
15217		 * @param {String} name Style name to get.
15218		 * @return {String} Style item value.
15219		 */
15220		function normalizeStyleValue(value, name) {
15221			// Force the format to hex
15222			if (name == 'color' || name == 'backgroundColor') {
15223				value = dom.toHex(value);
15224			}
15225
15226			// Opera will return bold as 700
15227			if (name == 'fontWeight' && value == 700) {
15228				value = 'bold';
15229			}
15230
15231			// Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
15232			if (name == 'fontFamily') {
15233				value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
15234			}
15235
15236			return '' + value;
15237		}
15238
15239		/**
15240		 * Replaces variables in the value. The variable format is %var.
15241		 *
15242		 * @private
15243		 * @param {String} value Value to replace variables in.
15244		 * @param {Object} vars Name/value array with variables to replace.
15245		 * @return {String} New value with replaced variables.
15246		 */
15247		function replaceVars(value, vars) {
15248			if (typeof(value) != "string") {
15249				value = value(vars);
15250			} else if (vars) {
15251				value = value.replace(/%(\w+)/g, function(str, name) {
15252					return vars[name] || str;
15253				});
15254			}
15255
15256			return value;
15257		}
15258
15259		function isWhiteSpaceNode(node) {
15260			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
15261		}
15262
15263		function wrap(node, name, attrs) {
15264			var wrapper = dom.create(name, attrs);
15265
15266			node.parentNode.insertBefore(wrapper, node);
15267			wrapper.appendChild(node);
15268
15269			return wrapper;
15270		}
15271
15272		/**
15273		 * Expands the specified range like object to depending on format.
15274		 *
15275		 * For example on block formats it will move the start/end position
15276		 * to the beginning of the current block.
15277		 *
15278		 * @private
15279		 * @param {Object} rng Range like object.
15280		 * @param {Array} formats Array with formats to expand by.
15281		 * @return {Object} Expanded range like object.
15282		 */
15283		function expandRng(rng, format, remove) {
15284			var lastIdx, leaf, endPoint,
15285				startContainer = rng.startContainer,
15286				startOffset = rng.startOffset,
15287				endContainer = rng.endContainer,
15288				endOffset = rng.endOffset;
15289
15290			// This function walks up the tree if there is no siblings before/after the node
15291			function findParentContainer(start) {
15292				var container, parent, sibling, siblingName, root;
15293
15294				container = parent = start ? startContainer : endContainer;
15295				siblingName = start ? 'previousSibling' : 'nextSibling';
15296				root = dom.getRoot();
15297
15298				function isBogusBr(node) {
15299					return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
15300				}
15301
15302				// If it's a text node and the offset is inside the text
15303				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
15304					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
15305						return container;
15306					}
15307				}
15308
15309				/*eslint no-constant-condition:0 */
15310				while (true) {
15311					// Stop expanding on block elements
15312					if (!format[0].block_expand && isBlock(parent)) {
15313						return parent;
15314					}
15315
15316					// Walk left/right
15317					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
15318						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
15319							return parent;
15320						}
15321					}
15322
15323					// Check if we can move up are we at root level or body level
15324					if (parent.parentNode == root) {
15325						container = parent;
15326						break;
15327					}
15328
15329					parent = parent.parentNode;
15330				}
15331
15332				return container;
15333			}
15334
15335			// This function walks down the tree to find the leaf at the selection.
15336			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15337			function findLeaf(node, offset) {
15338				if (offset === undef) {
15339					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15340				}
15341
15342				while (node && node.hasChildNodes()) {
15343					node = node.childNodes[offset];
15344					if (node) {
15345						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15346					}
15347				}
15348				return {node: node, offset: offset};
15349			}
15350
15351			// If index based start position then resolve it
15352			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15353				lastIdx = startContainer.childNodes.length - 1;
15354				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15355
15356				if (startContainer.nodeType == 3) {
15357					startOffset = 0;
15358				}
15359			}
15360
15361			// If index based end position then resolve it
15362			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15363				lastIdx = endContainer.childNodes.length - 1;
15364				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15365
15366				if (endContainer.nodeType == 3) {
15367					endOffset = endContainer.nodeValue.length;
15368				}
15369			}
15370
15371			// Expands the node to the closes contentEditable false element if it exists
15372			function findParentContentEditable(node) {
15373				var parent = node;
15374
15375				while (parent) {
15376					if (parent.nodeType === 1 && getContentEditable(parent)) {
15377						return getContentEditable(parent) === "false" ? parent : node;
15378					}
15379
15380					parent = parent.parentNode;
15381				}
15382
15383				return node;
15384			}
15385
15386			function findWordEndPoint(container, offset, start) {
15387				var walker, node, pos, lastTextNode;
15388
15389				function findSpace(node, offset) {
15390					var pos, pos2, str = node.nodeValue;
15391
15392					if (typeof(offset) == "undefined") {
15393						offset = start ? str.length : 0;
15394					}
15395
15396					if (start) {
15397						pos = str.lastIndexOf(' ', offset);
15398						pos2 = str.lastIndexOf('\u00a0', offset);
15399						pos = pos > pos2 ? pos : pos2;
15400
15401						// Include the space on remove to avoid tag soup
15402						if (pos !== -1 && !remove) {
15403							pos++;
15404						}
15405					} else {
15406						pos = str.indexOf(' ', offset);
15407						pos2 = str.indexOf('\u00a0', offset);
15408						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
15409					}
15410
15411					return pos;
15412				}
15413
15414				if (container.nodeType === 3) {
15415					pos = findSpace(container, offset);
15416
15417					if (pos !== -1) {
15418						return {container: container, offset: pos};
15419					}
15420
15421					lastTextNode = container;
15422				}
15423
15424				// Walk the nodes inside the block
15425				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
15426				while ((node = walker[start ? 'prev' : 'next']())) {
15427					if (node.nodeType === 3) {
15428						lastTextNode = node;
15429						pos = findSpace(node);
15430
15431						if (pos !== -1) {
15432							return {container: node, offset: pos};
15433						}
15434					} else if (isBlock(node)) {
15435						break;
15436					}
15437				}
15438
15439				if (lastTextNode) {
15440					if (start) {
15441						offset = 0;
15442					} else {
15443						offset = lastTextNode.length;
15444					}
15445
15446					return {container: lastTextNode, offset: offset};
15447				}
15448			}
15449
15450			function findSelectorEndPoint(container, sibling_name) {
15451				var parents, i, y, curFormat;
15452
15453				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
15454					container = container[sibling_name];
15455				}
15456
15457				parents = getParents(container);
15458				for (i = 0; i < parents.length; i++) {
15459					for (y = 0; y < format.length; y++) {
15460						curFormat = format[y];
15461
15462						// If collapsed state is set then skip formats that doesn't match that
15463						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
15464							continue;
15465						}
15466
15467						if (dom.is(parents[i], curFormat.selector)) {
15468							return parents[i];
15469						}
15470					}
15471				}
15472
15473				return container;
15474			}
15475
15476			function findBlockEndPoint(container, sibling_name) {
15477				var node, root = dom.getRoot();
15478
15479				// Expand to block of similar type
15480				if (!format[0].wrapper) {
15481					node = dom.getParent(container, format[0].block, root);
15482				}
15483
15484				// Expand to first wrappable block element or any block element
15485				if (!node) {
15486					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
15487						// Fixes #6183 where it would expand to editable parent element in inline mode
15488						return node != root && isTextBlock(node);
15489					});
15490				}
15491
15492				// Exclude inner lists from wrapping
15493				if (node && format[0].wrapper) {
15494					node = getParents(node, 'ul,ol').reverse()[0] || node;
15495				}
15496
15497				// Didn't find a block element look for first/last wrappable element
15498				if (!node) {
15499					node = container;
15500
15501					while (node[sibling_name] && !isBlock(node[sibling_name])) {
15502						node = node[sibling_name];
15503
15504						// Break on BR but include it will be removed later on
15505						// we can't remove it now since we need to check if it can be wrapped
15506						if (isEq(node, 'br')) {
15507							break;
15508						}
15509					}
15510				}
15511
15512				return node || container;
15513			}
15514
15515			// Expand to closest contentEditable element
15516			startContainer = findParentContentEditable(startContainer);
15517			endContainer = findParentContentEditable(endContainer);
15518
15519			// Exclude bookmark nodes if possible
15520			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
15521				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
15522				startContainer = startContainer.nextSibling || startContainer;
15523
15524				if (startContainer.nodeType == 3) {
15525					startOffset = 0;
15526				}
15527			}
15528
15529			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
15530				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
15531				endContainer = endContainer.previousSibling || endContainer;
15532
15533				if (endContainer.nodeType == 3) {
15534					endOffset = endContainer.length;
15535				}
15536			}
15537
15538			if (format[0].inline) {
15539				if (rng.collapsed) {
15540					// Expand left to closest word boundary
15541					endPoint = findWordEndPoint(startContainer, startOffset, true);
15542					if (endPoint) {
15543						startContainer = endPoint.container;
15544						startOffset = endPoint.offset;
15545					}
15546
15547					// Expand right to closest word boundary
15548					endPoint = findWordEndPoint(endContainer, endOffset);
15549					if (endPoint) {
15550						endContainer = endPoint.container;
15551						endOffset = endPoint.offset;
15552					}
15553				}
15554
15555				// Avoid applying formatting to a trailing space.
15556				leaf = findLeaf(endContainer, endOffset);
15557				if (leaf.node) {
15558					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
15559						leaf = findLeaf(leaf.node.previousSibling);
15560					}
15561
15562					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15563							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15564
15565						if (leaf.offset > 1) {
15566							endContainer = leaf.node;
15567							endContainer.splitText(leaf.offset - 1);
15568						}
15569					}
15570				}
15571			}
15572
15573			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
15574			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15575			// This will reduce the number of wrapper elements that needs to be created
15576			// Move start point up the tree
15577			if (format[0].inline || format[0].block_expand) {
15578				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
15579					startContainer = findParentContainer(true);
15580				}
15581
15582				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
15583					endContainer = findParentContainer();
15584				}
15585			}
15586
15587			// Expand start/end container to matching selector
15588			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15589				// Find new startContainer/endContainer if there is better one
15590				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15591				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15592			}
15593
15594			// Expand start/end container to matching block element or text node
15595			if (format[0].block || format[0].selector) {
15596				// Find new startContainer/endContainer if there is better one
15597				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15598				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15599
15600				// Non block element then try to expand up the leaf
15601				if (format[0].block) {
15602					if (!isBlock(startContainer)) {
15603						startContainer = findParentContainer(true);
15604					}
15605
15606					if (!isBlock(endContainer)) {
15607						endContainer = findParentContainer();
15608					}
15609				}
15610			}
15611
15612			// Setup index for startContainer
15613			if (startContainer.nodeType == 1) {
15614				startOffset = nodeIndex(startContainer);
15615				startContainer = startContainer.parentNode;
15616			}
15617
15618			// Setup index for endContainer
15619			if (endContainer.nodeType == 1) {
15620				endOffset = nodeIndex(endContainer) + 1;
15621				endContainer = endContainer.parentNode;
15622			}
15623
15624			// Return new range like object
15625			return {
15626				startContainer: startContainer,
15627				startOffset: startOffset,
15628				endContainer: endContainer,
15629				endOffset: endOffset
15630			};
15631		}
15632
15633		function isColorFormatAndAnchor(node, format) {
15634			return format.links && node.tagName == 'A';
15635		}
15636
15637		/**
15638		 * Removes the specified format for the specified node. It will also remove the node if it doesn't have
15639		 * any attributes if the format specifies it to do so.
15640		 *
15641		 * @private
15642		 * @param {Object} format Format object with items to remove from node.
15643		 * @param {Object} vars Name/value object with variables to apply to format.
15644		 * @param {Node} node Node to remove the format styles on.
15645		 * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
15646		 * @return {Boolean} True/false if the node was removed or not.
15647		 */
15648		function removeFormat(format, vars, node, compare_node) {
15649			var i, attrs, stylesModified;
15650
15651			// Check if node matches format
15652			if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) {
15653				return FALSE;
15654			}
15655
15656			// Should we compare with format attribs and styles
15657			if (format.remove != 'all') {
15658				// Remove styles
15659				each(format.styles, function(value, name) {
15660					value = normalizeStyleValue(replaceVars(value, vars), name);
15661
15662					// Indexed array
15663					if (typeof(name) === 'number') {
15664						name = value;
15665						compare_node = 0;
15666					}
15667
15668					if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
15669						dom.setStyle(node, name, '');
15670					}
15671
15672					stylesModified = 1;
15673				});
15674
15675				// Remove style attribute if it's empty
15676				if (stylesModified && dom.getAttrib(node, 'style') === '') {
15677					node.removeAttribute('style');
15678					node.removeAttribute('data-mce-style');
15679				}
15680
15681				// Remove attributes
15682				each(format.attributes, function(value, name) {
15683					var valueOut;
15684
15685					value = replaceVars(value, vars);
15686
15687					// Indexed array
15688					if (typeof(name) === 'number') {
15689						name = value;
15690						compare_node = 0;
15691					}
15692
15693					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15694						// Keep internal classes
15695						if (name == 'class') {
15696							value = dom.getAttrib(node, name);
15697							if (value) {
15698								// Build new class value where everything is removed except the internal prefixed classes
15699								valueOut = '';
15700								each(value.split(/\s+/), function(cls) {
15701									if (/mce\w+/.test(cls)) {
15702										valueOut += (valueOut ? ' ' : '') + cls;
15703									}
15704								});
15705
15706								// We got some internal classes left
15707								if (valueOut) {
15708									dom.setAttrib(node, name, valueOut);
15709									return;
15710								}
15711							}
15712						}
15713
15714						// IE6 has a bug where the attribute doesn't get removed correctly
15715						if (name == "class") {
15716							node.removeAttribute('className');
15717						}
15718
15719						// Remove mce prefixed attributes
15720						if (MCE_ATTR_RE.test(name)) {
15721							node.removeAttribute('data-mce-' + name);
15722						}
15723
15724						node.removeAttribute(name);
15725					}
15726				});
15727
15728				// Remove classes
15729				each(format.classes, function(value) {
15730					value = replaceVars(value, vars);
15731
15732					if (!compare_node || dom.hasClass(compare_node, value)) {
15733						dom.removeClass(node, value);
15734					}
15735				});
15736
15737				// Check for non internal attributes
15738				attrs = dom.getAttribs(node);
15739				for (i = 0; i < attrs.length; i++) {
15740					if (attrs[i].nodeName.indexOf('_') !== 0) {
15741						return FALSE;
15742					}
15743				}
15744			}
15745
15746			// Remove the inline child if it's empty for example <b> or <span>
15747			if (format.remove != 'none') {
15748				removeNode(node, format);
15749				return TRUE;
15750			}
15751		}
15752
15753		/**
15754		 * Removes the node and wrap it's children in paragraphs before doing so or
15755		 * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
15756		 *
15757		 * If the div in the node below gets removed:
15758		 *  text<div>text</div>text
15759		 *
15760		 * Output becomes:
15761		 *  text<div><br />text<br /></div>text
15762		 *
15763		 * So when the div is removed the result is:
15764		 *  text<br />text<br />text
15765		 *
15766		 * @private
15767		 * @param {Node} node Node to remove + apply BR/P elements to.
15768		 * @param {Object} format Format rule.
15769		 * @return {Node} Input node.
15770		 */
15771		function removeNode(node, format) {
15772			var parentNode = node.parentNode, rootBlockElm;
15773
15774			function find(node, next, inc) {
15775				node = getNonWhiteSpaceSibling(node, next, inc);
15776
15777				return !node || (node.nodeName == 'BR' || isBlock(node));
15778			}
15779
15780			if (format.block) {
15781				if (!forcedRootBlock) {
15782					// Append BR elements if needed before we remove the block
15783					if (isBlock(node) && !isBlock(parentNode)) {
15784						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
15785							node.insertBefore(dom.create('br'), node.firstChild);
15786						}
15787
15788						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
15789							node.appendChild(dom.create('br'));
15790						}
15791					}
15792				} else {
15793					// Wrap the block in a forcedRootBlock if we are at the root of document
15794					if (parentNode == dom.getRoot()) {
15795						if (!format.list_block || !isEq(node, format.list_block)) {
15796							each(grep(node.childNodes), function(node) {
15797								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15798									if (!rootBlockElm) {
15799										rootBlockElm = wrap(node, forcedRootBlock);
15800										dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
15801									} else {
15802										rootBlockElm.appendChild(node);
15803									}
15804								} else {
15805									rootBlockElm = 0;
15806								}
15807							});
15808						}
15809					}
15810				}
15811			}
15812
15813			// Never remove nodes that isn't the specified inline element if a selector is specified too
15814			if (format.selector && format.inline && !isEq(format.inline, node)) {
15815				return;
15816			}
15817
15818			dom.remove(node, 1);
15819		}
15820
15821		/**
15822		 * Returns the next/previous non whitespace node.
15823		 *
15824		 * @private
15825		 * @param {Node} node Node to start at.
15826		 * @param {boolean} next (Optional) Include next or previous node defaults to previous.
15827		 * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
15828		 * @return {Node} Next or previous node or undefined if it wasn't found.
15829		 */
15830		function getNonWhiteSpaceSibling(node, next, inc) {
15831			if (node) {
15832				next = next ? 'nextSibling' : 'previousSibling';
15833
15834				for (node = inc ? node : node[next]; node; node = node[next]) {
15835					if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
15836						return node;
15837					}
15838				}
15839			}
15840		}
15841
15842		/**
15843		 * Merges the next/previous sibling element if they match.
15844		 *
15845		 * @private
15846		 * @param {Node} prev Previous node to compare/merge.
15847		 * @param {Node} next Next node to compare/merge.
15848		 * @return {Node} Next node if we didn't merge and prev node if we did.
15849		 */
15850		function mergeSiblings(prev, next) {
15851			var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
15852
15853			function findElementSibling(node, sibling_name) {
15854				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
15855					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
15856						return node;
15857					}
15858
15859					if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
15860						return sibling;
15861					}
15862				}
15863
15864				return node;
15865			}
15866
15867			// Check if next/prev exists and that they are elements
15868			if (prev && next) {
15869				// If previous sibling is empty then jump over it
15870				prev = findElementSibling(prev, 'previousSibling');
15871				next = findElementSibling(next, 'nextSibling');
15872
15873				// Compare next and previous nodes
15874				if (elementUtils.compare(prev, next)) {
15875					// Append nodes between
15876					for (sibling = prev.nextSibling; sibling && sibling != next;) {
15877						tmpSibling = sibling;
15878						sibling = sibling.nextSibling;
15879						prev.appendChild(tmpSibling);
15880					}
15881
15882					// Remove next node
15883					dom.remove(next);
15884
15885					// Move children into prev node
15886					each(grep(next.childNodes), function(node) {
15887						prev.appendChild(node);
15888					});
15889
15890					return prev;
15891				}
15892			}
15893
15894			return next;
15895		}
15896
15897		function getContainer(rng, start) {
15898			var container, offset, lastIdx;
15899
15900			container = rng[start ? 'startContainer' : 'endContainer'];
15901			offset = rng[start ? 'startOffset' : 'endOffset'];
15902
15903			if (container.nodeType == 1) {
15904				lastIdx = container.childNodes.length - 1;
15905
15906				if (!start && offset) {
15907					offset--;
15908				}
15909
15910				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
15911			}
15912
15913			// If start text node is excluded then walk to the next node
15914			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
15915				container = new TreeWalker(container, ed.getBody()).next() || container;
15916			}
15917
15918			// If end text node is excluded then walk to the previous node
15919			if (container.nodeType === 3 && !start && offset === 0) {
15920				container = new TreeWalker(container, ed.getBody()).prev() || container;
15921			}
15922
15923			return container;
15924		}
15925
15926		function performCaretAction(type, name, vars, similar) {
15927			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
15928
15929			// Creates a caret container bogus element
15930			function createCaretContainer(fill) {
15931				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
15932
15933				if (fill) {
15934					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
15935				}
15936
15937				return caretContainer;
15938			}
15939
15940			function isCaretContainerEmpty(node, nodes) {
15941				while (node) {
15942					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
15943						return false;
15944					}
15945
15946					// Collect nodes
15947					if (nodes && node.nodeType === 1) {
15948						nodes.push(node);
15949					}
15950
15951					node = node.firstChild;
15952				}
15953
15954				return true;
15955			}
15956
15957			// Returns any parent caret container element
15958			function getParentCaretContainer(node) {
15959				while (node) {
15960					if (node.id === caretContainerId) {
15961						return node;
15962					}
15963
15964					node = node.parentNode;
15965				}
15966			}
15967
15968			// Finds the first text node in the specified node
15969			function findFirstTextNode(node) {
15970				var walker;
15971
15972				if (node) {
15973					walker = new TreeWalker(node, node);
15974
15975					for (node = walker.current(); node; node = walker.next()) {
15976						if (node.nodeType === 3) {
15977							return node;
15978						}
15979					}
15980				}
15981			}
15982
15983			// Removes the caret container for the specified node or all on the current document
15984			function removeCaretContainer(node, move_caret) {
15985				var child, rng;
15986
15987				if (!node) {
15988					node = getParentCaretContainer(selection.getStart());
15989
15990					if (!node) {
15991						while ((node = dom.get(caretContainerId))) {
15992							removeCaretContainer(node, false);
15993						}
15994					}
15995				} else {
15996					rng = selection.getRng(true);
15997
15998					if (isCaretContainerEmpty(node)) {
15999						if (move_caret !== false) {
16000							rng.setStartBefore(node);
16001							rng.setEndBefore(node);
16002						}
16003
16004						dom.remove(node);
16005					} else {
16006						child = findFirstTextNode(node);
16007
16008						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
16009							child.deleteData(0, 1);
16010
16011							// Fix for bug #6976
16012							if (rng.startContainer == child && rng.startOffset > 0) {
16013								rng.setStart(child, rng.startOffset - 1);
16014							}
16015
16016							if (rng.endContainer == child && rng.endOffset > 0) {
16017								rng.setEnd(child, rng.endOffset - 1);
16018							}
16019						}
16020
16021						dom.remove(node, 1);
16022					}
16023
16024					selection.setRng(rng);
16025				}
16026			}
16027
16028			// Applies formatting to the caret postion
16029			function applyCaretFormat() {
16030				var rng, caretContainer, textNode, offset, bookmark, container, text;
16031
16032				rng = selection.getRng(true);
16033				offset = rng.startOffset;
16034				container = rng.startContainer;
16035				text = container.nodeValue;
16036
16037				caretContainer = getParentCaretContainer(selection.getStart());
16038				if (caretContainer) {
16039					textNode = findFirstTextNode(caretContainer);
16040				}
16041
16042				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
16043				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
16044					// Get bookmark of caret position
16045					bookmark = selection.getBookmark();
16046
16047					// Collapse bookmark range (WebKit)
16048					rng.collapse(true);
16049
16050					// Expand the range to the closest word and split it at those points
16051					rng = expandRng(rng, get(name));
16052					rng = rangeUtils.split(rng);
16053
16054					// Apply the format to the range
16055					apply(name, vars, rng);
16056
16057					// Move selection back to caret position
16058					selection.moveToBookmark(bookmark);
16059				} else {
16060					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
16061						caretContainer = createCaretContainer(true);
16062						textNode = caretContainer.firstChild;
16063
16064						rng.insertNode(caretContainer);
16065						offset = 1;
16066
16067						apply(name, vars, caretContainer);
16068					} else {
16069						apply(name, vars, caretContainer);
16070					}
16071
16072					// Move selection to text node
16073					selection.setCursorLocation(textNode, offset);
16074				}
16075			}
16076
16077			function removeCaretFormat() {
16078				var rng = selection.getRng(true), container, offset, bookmark,
16079					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
16080
16081				container = rng.startContainer;
16082				offset = rng.startOffset;
16083				node = container;
16084
16085				if (container.nodeType == 3) {
16086					if (offset != container.nodeValue.length) {
16087						hasContentAfter = true;
16088					}
16089
16090					node = node.parentNode;
16091				}
16092
16093				while (node) {
16094					if (matchNode(node, name, vars, similar)) {
16095						formatNode = node;
16096						break;
16097					}
16098
16099					if (node.nextSibling) {
16100						hasContentAfter = true;
16101					}
16102
16103					parents.push(node);
16104					node = node.parentNode;
16105				}
16106
16107				// Node doesn't have the specified format
16108				if (!formatNode) {
16109					return;
16110				}
16111
16112				// Is there contents after the caret then remove the format on the element
16113				if (hasContentAfter) {
16114					// Get bookmark of caret position
16115					bookmark = selection.getBookmark();
16116
16117					// Collapse bookmark range (WebKit)
16118					rng.collapse(true);
16119
16120					// Expand the range to the closest word and split it at those points
16121					rng = expandRng(rng, get(name), true);
16122					rng = rangeUtils.split(rng);
16123
16124					// Remove the format from the range
16125					remove(name, vars, rng);
16126
16127					// Move selection back to caret position
16128					selection.moveToBookmark(bookmark);
16129				} else {
16130					caretContainer = createCaretContainer();
16131
16132					node = caretContainer;
16133					for (i = parents.length - 1; i >= 0; i--) {
16134						node.appendChild(dom.clone(parents[i], false));
16135						node = node.firstChild;
16136					}
16137
16138					// Insert invisible character into inner most format element
16139					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
16140					node = node.firstChild;
16141
16142					var block = dom.getParent(formatNode, isTextBlock);
16143
16144					if (block && dom.isEmpty(block)) {
16145						// Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
16146						formatNode.parentNode.replaceChild(caretContainer, formatNode);
16147					} else {
16148						// Insert caret container after the formated node
16149						dom.insertAfter(caretContainer, formatNode);
16150					}
16151
16152					// Move selection to text node
16153					selection.setCursorLocation(node, 1);
16154
16155					// If the formatNode is empty, we can remove it safely.
16156					if (dom.isEmpty(formatNode)) {
16157						dom.remove(formatNode);
16158					}
16159				}
16160			}
16161
16162			// Checks if the parent caret container node isn't empty if that is the case it
16163			// will remove the bogus state on all children that isn't empty
16164			function unmarkBogusCaretParents() {
16165				var caretContainer;
16166
16167				caretContainer = getParentCaretContainer(selection.getStart());
16168				if (caretContainer && !dom.isEmpty(caretContainer)) {
16169					walk(caretContainer, function(node) {
16170						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
16171							dom.setAttrib(node, 'data-mce-bogus', null);
16172						}
16173					}, 'childNodes');
16174				}
16175			}
16176
16177			// Only bind the caret events once
16178			if (!ed._hasCaretEvents) {
16179				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
16180				markCaretContainersBogus = function() {
16181					var nodes = [], i;
16182
16183					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
16184						// Mark children
16185						i = nodes.length;
16186						while (i--) {
16187							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
16188						}
16189					}
16190				};
16191
16192				disableCaretContainer = function(e) {
16193					var keyCode = e.keyCode;
16194
16195					removeCaretContainer();
16196
16197					// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
16198					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
16199						removeCaretContainer(getParentCaretContainer(selection.getStart()));
16200					}
16201
16202					unmarkBogusCaretParents();
16203				};
16204
16205				// Remove bogus state if they got filled by contents using editor.selection.setContent
16206				ed.on('SetContent', function(e) {
16207					if (e.selection) {
16208						unmarkBogusCaretParents();
16209					}
16210				});
16211				ed._hasCaretEvents = true;
16212			}
16213
16214			// Do apply or remove caret format
16215			if (type == "apply") {
16216				applyCaretFormat();
16217			} else {
16218				removeCaretFormat();
16219			}
16220		}
16221
16222		/**
16223		 * Moves the start to the first suitable text node.
16224		 */
16225		function moveStart(rng) {
16226			var container = rng.startContainer,
16227					offset = rng.startOffset, isAtEndOfText,
16228					walker, node, nodes, tmpNode;
16229
16230			// Convert text node into index if possible
16231			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
16232				// Get the parent container location and walk from there
16233				offset = nodeIndex(container);
16234				container = container.parentNode;
16235				isAtEndOfText = true;
16236			}
16237
16238			// Move startContainer/startOffset in to a suitable node
16239			if (container.nodeType == 1) {
16240				nodes = container.childNodes;
16241				container = nodes[Math.min(offset, nodes.length - 1)];
16242				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
16243
16244				// If offset is at end of the parent node walk to the next one
16245				if (offset >
16245 nodes.length - 1 || isAtEndOfText) {
16246					walker.next();
16247				}
16248
16249				for (node = walker.current(); node; node = walker.next()) {
16250					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
16251						// IE has a "neat" feature where it moves the start node into the closest element
16252						// we can avoid this by inserting an element before it and then remove it after we set the selection
16253						tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
16254						node.parentNode.insertBefore(tmpNode, node);
16255
16256						// Set selection and remove tmpNode
16257						rng.setStart(node, 0);
16258						selection.setRng(rng);
16259						dom.remove(tmpNode);
16260
16261						return;
16262					}
16263				}
16264			}
16265		}
16266	};
16267});
16268
16269// Included from: js/tinymce/classes/UndoManager.js
16270
16271/**
16272 * UndoManager.js
16273 *
16274 * Copyright, Moxiecode Systems AB
16275 * Released under LGPL License.
16276 *
16277 * License: http://www.tinymce.com/license
16278 * Contributing: http://www.tinymce.com/contributing
16279 */
16280
16281/**
16282 * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed.
16283 *
16284 * @class tinymce.UndoManager
16285 */
16286define("tinymce/UndoManager", [
16287	"tinymce/util/VK",
16288	"tinymce/Env",
16289	"tinymce/util/Tools",
16290	"tinymce/html/SaxParser"
16291], function(VK, Env, Tools, SaxParser) {
16292	var trim = Tools.trim, trimContentRegExp;
16293
16294	trimContentRegExp = new RegExp([
16295		'<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
16296		'\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
16297	].join('|'), 'gi');
16298
16299	return function(editor) {
16300		var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
16301
16302		/**
16303		 * Returns a trimmed version of the editor contents to be used for the undo level. This
16304		 * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also
16305		 * remove the data-mce-selected attributes used for selection of objects and caret containers.
16306		 * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will
16307		 * be removed by the serialization logic when you save.
16308		 *
16309		 * @private
16310		 * @return {String} HTML contents of the editor excluding some internal bogus elements.
16311		 */
16312		function getContent() {
16313			var content = editor.getContent({format: 'raw', no_events: 1});
16314			var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
16315			var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;
16316
16317			content = content.replace(trimContentRegExp, '');
16318			shortEndedElements = schema.getShortEndedElements();
16319
16320			// Remove all bogus elements marked with "all"
16321			while ((matches = bogusAllRegExp.exec(content))) {
16322				index = bogusAllRegExp.lastIndex;
16323				matchLength = matches[0].length;
16324
16325				if (shortEndedElements[matches[1]]) {
16326					endTagIndex = index;
16327				} else {
16328					endTagIndex = SaxParser.findEndTag(schema, content, index);
16329				}
16330
16331				content = content.substring(0, index - matchLength) + content.substring(endTagIndex);
16332				bogusAllRegExp.lastIndex = index - matchLength;
16333			}
16334
16335			return trim(content);
16336		}
16337
16338		function addNonTypingUndoLevel(e) {
16339			self.typing = false;
16340			self.add({}, e);
16341		}
16342
16343		// Add initial undo level when the editor is initialized
16344		editor.on('init', function() {
16345			self.add();
16346		});
16347
16348		// Get position before an execCommand is processed
16349		editor.on('BeforeExecCommand', function(e) {
16350			var cmd = e.command;
16351
16352			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
16353				self.beforeChange();
16354			}
16355		});
16356
16357		// Add undo level after an execCommand call was made
16358		editor.on('ExecCommand', function(e) {
16359			var cmd = e.command;
16360
16361			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
16362				addNonTypingUndoLevel(e);
16363			}
16364		});
16365
16366		editor.on('ObjectResizeStart', function() {
16367			self.beforeChange();
16368		});
16369
16370		editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
16371		editor.on('DragEnd', addNonTypingUndoLevel);
16372
16373		editor.on('KeyUp', function(e) {
16374			var keyCode = e.keyCode;
16375
16376			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
16377				addNonTypingUndoLevel();
16378				editor.nodeChanged();
16379			}
16380
16381			if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
16382				editor.nodeChanged();
16383			}
16384
16385			// Fire a TypingUndo event on the first character entered
16386			if (isFirstTypedCharacter && self.typing) {
16387				// Make the it dirty if the content was changed after typing the first character
16388				if (!editor.isDirty()) {
16389					editor.isNotDirty = !data[0] || getContent() == data[0].content;
16390
16391					// Fire initial change event
16392					if (!editor.isNotDirty) {
16393						editor.fire('change', {level: data[0], lastLevel: null});
16394					}
16395				}
16396
16397				editor.fire('TypingUndo');
16398				isFirstTypedCharacter = false;
16399				editor.nodeChanged();
16400			}
16401		});
16402
16403		editor.on('KeyDown', function(e) {
16404			var keyCode = e.keyCode;
16405
16406			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
16407			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
16408				if (self.typing) {
16409					addNonTypingUndoLevel(e);
16410				}
16411
16412				return;
16413			}
16414
16415			// If key isn't shift,ctrl,alt,capslock,metakey
16416			var modKey = VK.modifierPressed(e);
16417			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) {
16418				self.beforeChange();
16419				self.typing = true;
16420				self.add({}, e);
16421				isFirstTypedCharacter = true;
16422			}
16423		});
16424
16425		editor.on('MouseDown', function(e) {
16426			if (self.typing) {
16427				addNonTypingUndoLevel(e);
16428			}
16429		});
16430
16431		// Add keyboard shortcuts for undo/redo keys
16432		editor.addShortcut('ctrl+z', '', 'Undo');
16433		editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo');
16434
16435		editor.on('AddUndo Undo Redo ClearUndos', function(e) {
16436			if (!e.isDefaultPrevented()) {
16437				editor.nodeChanged();
16438			}
16439		});
16440
16441		self = {
16442			// Explose for debugging reasons
16443			data: data,
16444
16445			/**
16446			 * State if the user is currently typing or not. This will add a typing operation into one undo
16447			 * level instead of one new level for each keystroke.
16448			 *
16449			 * @field {Boolean} typing
16450			 */
16451			typing: false,
16452
16453			/**
16454			 * Stores away a bookmark to be used when performing an undo action so that the selection is before
16455			 * the change has been made.
16456			 *
16457			 * @method beforeChange
16458			 */
16459			beforeChange: function() {
16460				if (!locks) {
16461					beforeBookmark = editor.selection.getBookmark(2, true);
16462				}
16463			},
16464
16465			/**
16466			 * Adds a new undo level/snapshot to the undo list.
16467			 *
16468			 * @method add
16469			 * @param {Object} level Optional undo level object to add.
16470			 * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
16471			 * @return {Object} Undo level that got added or null it a level wasn't needed.
16472			 */
16473			add: function(level, event) {
16474				var i, settings = editor.settings, lastLevel;
16475
16476				level = level || {};
16477				level.content = getContent();
16478
16479				if (locks || editor.removed) {
16480					return null;
16481				}
16482
16483				lastLevel = data[index];
16484				if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
16485					return null;
16486				}
16487
16488				// Add undo level if needed
16489				if (lastLevel && lastLevel.content == level.content) {
16490					return null;
16491				}
16492
16493				// Set before bookmark on previous level
16494				if (data[index]) {
16495					data[index].beforeBookmark = beforeBookmark;
16496				}
16497
16498				// Time to compress
16499				if (settings.custom_undo_redo_levels) {
16500					if (data.length > settings.custom_undo_redo_levels) {
16501						for (i = 0; i < data.length - 1; i++) {
16502							data[i] = data[i + 1];
16503						}
16504
16505						data.length--;
16506						index = data.length;
16507					}
16508				}
16509
16510				// Get a non intrusive normalized bookmark
16511				level.bookmark = editor.selection.getBookmark(2, true);
16512
16513				// Crop array if needed
16514				if (index < data.length - 1) {
16515					data.length = index + 1;
16516				}
16517
16518				data.push(level);
16519				index = data.length - 1;
16520
16521				var args = {level: level, lastLevel: lastLevel, originalEvent: event};
16522
16523				editor.fire('AddUndo', args);
16524
16525				if (index > 0) {
16526					editor.isNotDirty = false;
16527					editor.fire('change', args);
16528				}
16529
16530				return level;
16531			},
16532
16533			/**
16534			 * Undoes the last action.
16535			 *
16536			 * @method undo
16537			 * @return {Object} Undo level or null if no undo was performed.
16538			 */
16539			undo: function() {
16540				var level;
16541
16542				if (self.typing) {
16543					self.add();
16544					self.typing = false;
16545				}
16546
16547				if (index > 0) {
16548					level = data[--index];
16549
16550					// Undo to first index then set dirty state to false
16551					if (index === 0) {
16552						editor.isNotDirty = true;
16553					}
16554
16555					editor.setContent(level.content, {format: 'raw'});
16556					editor.selection.moveToBookmark(level.beforeBookmark);
16557
16558					editor.fire('undo', {level: level});
16559				}
16560
16561				return level;
16562			},
16563
16564			/**
16565			 * Redoes the last action.
16566			 *
16567			 * @method redo
16568			 * @return {Object} Redo level or null if no redo was performed.
16569			 */
16570			redo: function() {
16571				var level;
16572
16573				if (index < data.length - 1) {
16574					level = data[++index];
16575
16576					editor.setContent(level.content, {format: 'raw'});
16577					editor.selection.moveToBookmark(level.bookmark);
16578
16579					editor.fire('redo', {level: level});
16580				}
16581
16582				return level;
16583			},
16584
16585			/**
16586			 * Removes all undo levels.
16587			 *
16588			 * @method clear
16589			 */
16590			clear: function() {
16591				data = [];
16592				index = 0;
16593				self.typing = false;
16594				editor.fire('ClearUndos');
16595			},
16596
16597			/**
16598			 * Returns true/false if the undo manager has any undo levels.
16599			 *
16600			 * @method hasUndo
16601			 * @return {Boolean} true/false if the undo manager has any undo levels.
16602			 */
16603			hasUndo: function() {
16604				// Has undo levels or typing and content isn't the same as the initial level
16605				return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
16606			},
16607
16608			/**
16609			 * Returns true/false if the undo manager has any redo levels.
16610			 *
16611			 * @method hasRedo
16612			 * @return {Boolean} true/false if the undo manager has any redo levels.
16613			 */
16614			hasRedo: function() {
16615				return index < data.length - 1 && !this.typing;
16616			},
16617
16618			/**
16619			 * Executes the specified function in an undo transation. The selection
16620			 * before the modification will be stored to the undo stack and if the DOM changes
16621			 * it will add a new undo level. Any methods within the transation that adds undo levels will
16622			 * be ignored. So a transation can include calls to execCommand or editor.insertContent.
16623			 *
16624			 * @method transact
16625			 * @param {function} callback Function to execute dom manipulation logic in.
16626			 */
16627			transact: function(callback) {
16628				self.beforeChange();
16629
16630				try {
16631					locks++;
16632					callback();
16633				} finally {
16634					locks--;
16635				}
16636
16637				self.add();
16638			}
16639		};
16640
16641		return self;
16642	};
16643});
16644
16645// Included from: js/tinymce/classes/EnterKey.js
16646
16647/**
16648 * EnterKey.js
16649 *
16650 * Copyright, Moxiecode Systems AB
16651 * Released under LGPL License.
16652 *
16653 * License: http://www.tinymce.com/license
16654 * Contributing: http://www.tinymce.com/contributing
16655 */
16656
16657/**
16658 * Contains logic for handling the enter key to split/generate block elements.
16659 */
16660define("tinymce/EnterKey", [
16661	"tinymce/dom/TreeWalker",
16662	"tinymce/dom/RangeUtils",
16663	"tinymce/Env"
16664], function(TreeWalker, RangeUtils, Env) {
16665	var isIE = Env.ie && Env.ie < 11;
16666
16667	return function(editor) {
16668		var dom = editor.dom, selection = editor.selection, settings = editor.settings;
16669		var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
16670
16671		function handleEnterKey(evt) {
16672			var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
16673				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
16674
16675			// Returns true if the block can be split into two blocks or not
16676			function canSplitBlock(node) {
16677				return node &&
16678					dom.isBlock(node) &&
16679					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
16680					!/^(fixed|absolute)/i.test(node.style.position) &&
16681					dom.getContentEditable(node) !== "true";
16682			}
16683
16684			// Renders empty block on IE
16685			function renderBlockOnIE(block) {
16686				var oldRng;
16687
16688				if (dom.isBlock(block)) {
16689					oldRng = selection.getRng();
16690					block.appendChild(dom.create('span', null, '\u00a0'));
16691					selection.select(block);
16692					block.lastChild.outerHTML = '';
16693					selection.setRng(oldRng);
16694				}
16695			}
16696
16697			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>
16697x</p>
16698			function trimInlineElementsOnLeftSideOfBlock(block) {
16699				var node = block, firstChilds = [], i;
16700
16701				if (!node) {
16702					return;
16703				}
16704
16705				// Find inner most first child ex: <p><i><b>*</b></i></p>
16706				while ((node = node.firstChild)) {
16707					if (dom.isBlock(node)) {
16708						return;
16709					}
16710
16711					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16712						firstChilds.push(node);
16713					}
16714				}
16715
16716				i = firstChilds.length;
16717				while (i--) {
16718					node = firstChilds[i];
16719					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
16720						dom.remove(node);
16721					} else {
16722						// Remove <a> </a> see #5381
16723						if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
16724							dom.remove(node);
16725						}
16726					}
16727				}
16728			}
16729
16730			// Moves the caret to a suitable position within the root for example in the first non
16731			// pure whitespace text node or before an image
16732			function moveToCaretPosition(root) {
16733				var walker, node, rng, lastNode = root, tempElm;
16734
16735				function firstNonWhiteSpaceNodeSibling(node) {
16736					while (node) {
16737						if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
16738							return node;
16739						}
16740
16741						node = node.nextSibling;
16742					}
16743				}
16744
16745				if (!root) {
16746					return;
16747				}
16748
16749				// Old IE versions doesn't properly render blocks with br elements in them
16750				// For example <p><br></p> wont be rendered correctly in a contentEditable area
16751				// until you remove the br producing <p></p>
16752				if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
16753					if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
16754						dom.remove(parentBlock.firstChild);
16755					}
16756				}
16757
16758				if (/^(LI|DT|DD)$/.test(root.nodeName)) {
16759					var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
16760
16761					if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
16762						root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
16763					}
16764				}
16765
16766				rng = dom.createRng();
16767
16768				// Normalize whitespace to remove empty text nodes. Fix for: #6904
16769				// Gecko will be able to place the caret in empty text nodes but it won't render propery
16770				// Older IE versions will sometimes crash so for now ignore all IE versions
16771				if (!Env.ie) {
16772					root.normalize();
16773				}
16774
16775				if (root.hasChildNodes()) {
16776					walker = new TreeWalker(root, root);
16777
16778					while ((node = walker.current())) {
16779						if (node.nodeType == 3) {
16780							rng.setStart(node, 0);
16781							rng.setEnd(node, 0);
16782							break;
16783						}
16784
16785						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16786							rng.setStartBefore(node);
16787							rng.setEndBefore(node);
16788							break;
16789						}
16790
16791						lastNode = node;
16792						node = walker.next();
16793					}
16794
16795					if (!node) {
16796						rng.setStart(lastNode, 0);
16797						rng.setEnd(lastNode, 0);
16798					}
16799				} else {
16800					if (root.nodeName == 'BR') {
16801						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
16802							// Trick on older IE versions to render the caret before the BR between two lists
16803							if (!documentMode || documentMode < 9) {
16804								tempElm = dom.create('br');
16805								root.parentNode.insertBefore(tempElm, root);
16806							}
16807
16808							rng.setStartBefore(root);
16809							rng.setEndBefore(root);
16810						} else {
16811							rng.setStartAfter(root);
16812							rng.setEndAfter(root);
16813						}
16814					} else {
16815						rng.setStart(root, 0);
16816						rng.setEnd(root, 0);
16817					}
16818				}
16819
16820				selection.setRng(rng);
16821
16822				// Remove tempElm created for old IE:s
16823				dom.remove(tempElm);
16824				selection.scrollIntoView(root);
16825			}
16826
16827			function setForcedBlockAttrs(node) {
16828				var forcedRootBlockName = settings.forced_root_block;
16829
16830				if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
16831					dom.setAttribs(node, settings.forced_root_block_attrs);
16832				}
16833			}
16834
16835			// Creates a new block element by cloning the current one or creating a new one if the name is specified
16836			// This function will also copy any text formatting from the parent block and add it to the new one
16837			function createNewBlock(name) {
16838				var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
16839
16840				if (name || parentBlockName == "TABLE") {
16841					block = dom.create(name || newBlockName);
16842					setForcedBlockAttrs(block);
16843				} else {
16844					block = parentBlock.cloneNode(false);
16845				}
16846
16847				caretNode = block;
16848
16849				// Clone any parent styles
16850				if (settings.keep_styles !== false) {
16851					do {
16852						if (textInlineElements[node.nodeName]) {
16853							// Never clone a caret containers
16854							if (node.id == '_mce_caret') {
16855								continue;
16856							}
16857
16858							clonedNode = node.cloneNode(false);
16859							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
16860
16861							if (block.hasChildNodes()) {
16862								clonedNode.appendChild(block.firstChild);
16863								block.appendChild(clonedNode);
16864							} else {
16865								caretNode = clonedNode;
16866								block.appendChild(clonedNode);
16867							}
16868						}
16869					} while ((node = node.parentNode));
16870				}
16871
16872				// BR is needed in empty blocks on non IE browsers
16873				if (!isIE) {
16874					caretNode.innerHTML = '<br data-mce-bogus="1">';
16875				}
16876
16877				return block;
16878			}
16879
16880			// Returns true/false if the caret is at the start/end of the parent block element
16881			function isCaretAtStartOrEndOfBlock(start) {
16882				var walker, node, name;
16883
16884				// Caret is in the middle of a text node like "a|b"
16885				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
16886					return false;
16887				}
16888
16889				// If after the last element in block node edge case for #5091
16890				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
16891					return true;
16892				}
16893
16894				// If the caret if before the first element in parentBlock
16895				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
16896					return true;
16897				}
16898
16899				// Caret can be before/after a table
16900				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
16901					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
16902				}
16903
16904				// Walk the DOM and look for text nodes or non empty elements
16905				walker = new TreeWalker(container, parentBlock);
16906
16907				// If caret is in beginning or end of a text block then jump to the next/previous node
16908				if (container.nodeType == 3) {
16909					if (start && offset === 0) {
16910						walker.prev();
16911					} else if (!start && offset == container.nodeValue.length) {
16912						walker.next();
16913					}
16914				}
16915
16916				while ((node = walker.current())) {
16917					if (node.nodeType === 1) {
16918						// Ignore bogus elements
16919						if (!node.getAttribute('data-mce-bogus')) {
16920							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
16921							name = node.nodeName.toLowerCase();
16922							if (nonEmptyElementsMap[name] && name !== 'br') {
16923								return false;
16924							}
16925						}
16926					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
16927						return false;
16928					}
16929
16930					if (start) {
16931						walker.prev();
16932					} else {
16933						walker.next();
16934					}
16935				}
16936
16937				return true;
16938			}
16939
16940			// Wraps any text nodes or inline elements in the specified forced root block name
16941			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
16942				var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
16943
16944				// Not in a block element or in a table cell or caption
16945				parentBlock = dom.getParent(container, dom.isBlock);
16946				rootBlockName = editor.getBody().nodeName.toLowerCase();
16947				if (!parentBlock || !canSplitBlock(parentBlock)) {
16948					parentBlock = parentBlock || editableRoot;
16949
16950					if (!parentBlock.hasChildNodes()) {
16951						newBlock = dom.create(blockName);
16952						setForcedBlockAttrs(newBlock);
16953						parentBlock.appendChild(newBlock);
16954						rng.setStart(newBlock, 0);
16955						rng.setEnd(newBlock, 0);
16956						return newBlock;
16957					}
16958
16959					// Find parent that is the first child of parentBlock
16960					node = container;
16961					while (node.parentNode != parentBlock) {
16962						node = node.parentNode;
16963					}
16964
16965					// Loop left to find start node start wrapping at
16966					while (node && !dom.isBlock(node)) {
16967						startNode = node;
16968						node = node.previousSibling;
16969					}
16970
16971					if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
16972						newBlock = dom.create(blockName);
16973						setForcedBlockAttrs(newBlock);
16974						startNode.parentNode.insertBefore(newBlock, startNode);
16975
16976						// Start wrapping until we hit a block
16977						node = startNode;
16978						while (node && !dom.isBlock(node)) {
16979							next = node.nextSibling;
16980							newBlock.appendChild(node);
16981							node = next;
16982						}
16983
16984						// Restore range to it's past location
16985						rng.setStart(container, offset);
16986						rng.setEnd(container, offset);
16987					}
16988				}
16989
16990				return container;
16991			}
16992
16993			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
16994			function handleEmptyListItem() {
16995				function isFirstOrLastLi(first) {
16996					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
16997
16998					// Find first/last element since there might be whitespace there
16999					while (node) {
17000						if (node.nodeType == 1) {
17001							break;
17002						}
17003
17004						node = node[first ? 'nextSibling' : 'previousSibling'];
17005					}
17006
17007					return node === parentBlock;
17008				}
17009
17010				function getContainerBlock() {
17011					var containerBlockParent = containerBlock.parentNode;
17012
17013					if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
17014						return containerBlockParent;
17015					}
17016
17017					return containerBlock;
17018				}
17019
17020				// Check if we are in an nested list
17021				var containerBlockParentName = containerBlock.parentNode.nodeName;
17022				if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
17023					newBlockName = 'LI';
17024				}
17025
17026				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
17027
17028				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
17029					if (containerBlockParentName == 'LI') {
17030						// Nested list is inside a LI
17031						dom.insertAfter(newBlock, getContainerBlock());
17032					} else {
17033						// Is first and last list item then replace the OL/UL with a text block
17034						dom.replace(newBlock, containerBlock);
17035					}
17036				} else if (isFirstOrLastLi(true)) {
17037					if (containerBlockParentName == 'LI') {
17038						// List nested in an LI then move the list to a new sibling LI
17039						dom.insertAfter(newBlock, getContainerBlock());
17040						newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
17041						newBlock.appendChild(containerBlock);
17042					} else {
17043						// First LI in list then remove LI and add text block before list
17044						containerBlock.parentNode.insertBefore(newBlock, containerBlock);
17045					}
17046				} else if (isFirstOrLastLi()) {
17047					// Last LI in list then remove LI and add text block after list
17048					dom.insertAfter(newBlock, getContainerBlock());
17049					renderBlockOnIE(newBlock);
17050				} else {
17051					// Middle LI in list the split the list and insert a text block in the middle
17052					// Extract after fragment and insert it after the current block
17053					containerBlock = getContainerBlock();
17054					tmpRng = rng.cloneRange();
17055					tmpRng.setStartAfter(parentBlock);
17056					tmpRng.setEndAfter(containerBlock);
17057					fragment = tmpRng.extractContents();
17058
17059					if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
17060						newBlock = fragment.firstChild;
17061						dom.insertAfter(fragment, containerBlock);
17062					} else {
17063						dom.insertAfter(fragment, containerBlock);
17064						dom.insertAfter(newBlock, containerBlock);
17065					}
17066				}
17067
17068				dom.remove(parentBlock);
17069				moveToCaretPosition(newBlock);
17070				undoManager.add();
17071			}
17072
17073			// Inserts a BR element if the forced_root_block option is set to false or empty string
17074			function insertBr() {
17075				editor.execCommand("InsertLineBreak", false, evt);
17076			}
17077
17078			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
17079			function trimLeadingLineBreaks(node) {
17080				do {
17081					if (node.nodeType === 3) {
17082						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
17083					}
17084
17085					node = node.firstChild;
17086				} while (node);
17087			}
17088
17089			function getEditableRoot(node) {
17090				var root = dom.getRoot(), parent, editableRoot;
17091
17092				// Get all parents until we hit a non editable parent or the root
17093				parent = node;
17094				while (parent !== root && dom.getContentEditable(parent) !== "false") {
17095					if (dom.getContentEditable(parent) === "true") {
17096						editableRoot = parent;
17097					}
17098
17099					parent = parent.parentNode;
17100				}
17101
17102				return parent !== root ? editableRoot : root;
17103			}
17104
17105			// Adds a BR at the end of blocks that only contains an IMG or INPUT since
17106			// these might be floated and then they won't expand the block
17107			function addBrToBlockIfNeeded(block) {
17108				var lastChild;
17109
17110				// IE will render the blocks correctly other browsers needs a BR
17111				if (!isIE) {
17112					block.normalize(); // Remove empty text nodes that got left behind by the extract
17113
17114					// Check if the block is empty or contains a floated last child
17115					lastChild = block.lastChild;
17116					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
17117						dom.add(block, 'br');
17118					}
17119				}
17120			}
17121
17122			rng = selection.getRng(true);
17123
17124			// Event is blocked by some other handler for example the lists plugin
17125			if (evt.isDefaultPrevented()) {
17126				return;
17127			}
17128
17129			// Delete any selected contents
17130			if (!rng.collapsed) {
17131				editor.execCommand('Delete');
17132				return;
17133			}
17134
17135			// Setup range items and newBlockName
17136			new RangeUtils(dom).normalize(rng);
17137			container = rng.startContainer;
17138			offset = rng.startOffset;
17139			newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
17140			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
17141			documentMode = dom.doc.documentMode;
17142			shiftKey = evt.shiftKey;
17143
17144			// Resolve node index
17145			if (container.nodeType == 1 && container.hasChildNodes()) {
17146				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
17147
17148				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
17149				if (isAfterLastNodeInContainer && container.nodeType == 3) {
17150					offset = container.nodeValue.length;
17151				} else {
17152					offset = 0;
17153				}
17154			}
17155
17156			// Get editable root node normaly the body element but sometimes a div or span
17157			editableRoot = getEditableRoot(container);
17158
17159			// If there is no editable root then enter is done inside a contentEditable false element
17160			if (!editableRoot) {
17161				return;
17162			}
17163
17164			undoManager.beforeChange();
17165
17166			// If editable root isn't block nor the root of the editor
17167			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
17168				if (!newBlockName || shiftKey) {
17169					insertBr();
17170				}
17171
17172				return;
17173			}
17174
17175			// Wrap the current node and it's sibling in a default block if it's needed.
17176			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
17177			// This won't happen if root blocks are disabled or the shiftKey is pressed
17178			if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
17179				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
17180			}
17181
17182			// Find parent block and setup empty block paddings
17183			parentBlock = dom.getParent(container, dom.isBlock);
17184			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
17185
17186			// Setup block names
17187			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17188			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17189
17190			// Enter inside block contained within a LI then split or insert before/after LI
17191			if (containerBlockName == 'LI' && !evt.ctrlKey) {
17192				parentBlock = containerBlock;
17193				parentBlockName = containerBlockName;
17194			}
17195
17196			// Handle enter in list item
17197			if (/^(LI|DT|DD)$/.test(parentBlockName)) {
17198				if (!newBlockName && shiftKey) {
17199					insertBr();
17200					return;
17201				}
17202
17203				// Handle enter inside an empty list item
17204				if (dom.isEmpty(parentBlock)) {
17205					handleEmptyListItem();
17206					return;
17207				}
17208			}
17209
17210			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
17211			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
17212				if (!shiftKey) {
17213					insertBr();
17214					return;
17215				}
17216			} else {
17217				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
17218				if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
17219					insertBr();
17220					return;
17221				}
17222			}
17223
17224			// If parent block is root then never insert new blocks
17225			if (newBlockName && parentBlock === editor.getBody()) {
17226				return;
17227			}
17228
17229			// Default block name if it's not configured
17230			newBlockName = newBlockName || 'P';
17231
17232			// Insert new block before/after the parent block depending on caret location
17233			if (isCaretAtStartOrEndOfBlock()) {
17234				// 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
17235				if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
17236					newBlock = createNewBlock(newBlockName);
17237				} else {
17238					newBlock = createNewBlock();
17239				}
17240
17241				// Split the current container block element if enter is pressed inside an empty inner block element
17242				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
17243					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
17244					newBlock = dom.split(containerBlock, parentBlock);
17245				} else {
17246					dom.insertAfter(newBlock, parentBlock);
17247				}
17248
17249				moveToCaretPosition(newBlock);
17250			} else if (isCaretAtStartOrEndOfBlock(true)) {
17251				// Insert new block before
17252				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
17253				renderBlockOnIE(newBlock);
17254				moveToCaretPosition(parentBlock);
17255			} else {
17256				// Extract after fragment and insert it after the current block
17257				tmpRng = rng.cloneRange();
17258				tmpRng.setEndAfter(parentBlock);
17259				fragment = tmpRng.extractContents();
17260				trimLeadingLineBreaks(fragment);
17261				newBlock = fragment.firstChild;
17262				dom.insertAfter(fragment, parentBlock);
17263				trimInlineElementsOnLeftSideOfBlock(newBlock);
17264				addBrToBlockIfNeeded(parentBlock);
17265				moveToCaretPosition(newBlock);
17266			}
17267
17268			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
17269
17270			// Allow custom handling of new blocks
17271			editor.fire('NewBlock', {newBlock: newBlock});
17272
17273			undoManager.add();
17274		}
17275
17276		editor.on('keydown', function(evt) {
17277			if (evt.keyCode == 13) {
17278				if (handleEnterKey(evt) !== false) {
17279					evt.preventDefault();
17280				}
17281			}
17282		});
17283	};
17284});
17285
17286// Included from: js/tinymce/classes/ForceBlocks.js
17287
17288/**
17289 * ForceBlocks.js
17290 *
17291 * Copyright, Moxiecode Systems AB
17292 * Released under LGPL License.
17293 *
17294 * License: http://www.tinymce.com/license
17295 * Contributing: http://www.tinymce.com/contributing
17296 */
17297
17298define("tinymce/ForceBlocks", [], function() {
17299	return function(editor) {
17300		var settings = editor.settings, dom = editor.dom, selection = editor.selection;
17301		var schema = editor.schema, blockElements = schema.getBlockElements();
17302
17303		function addRootBlocks() {
17304			var node = selection.getStart(), rootNode = editor.getBody(), rng;
17305			var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
17306			var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
17307			var tmpRng, rootNodeName, forcedRootBlock;
17308
17309			forcedRootBlock = settings.forced_root_block;
17310
17311			if (!node || node.nodeType !== 1 || !forcedRootBlock) {
17312				return;
17313			}
17314
17315			// Check if node is wrapped in block
17316			while (node && node != rootNode) {
17317				if (blockElements[node.nodeName]) {
17318					return;
17319				}
17320
17321				node = node.parentNode;
17322			}
17323
17324			// Get current selection
17325			rng = selection.getRng();
17326			if (rng.setStart) {
17327				startContainer = rng.startContainer;
17328				startOffset = rng.startOffset;
17329				endContainer = rng.endContainer;
17330				endOffset = rng.endOffset;
17331
17332				try {
17333					restoreSelection = editor.getDoc().activeElement === rootNode;
17334				} catch (ex) {
17335					// IE throws unspecified error here sometimes
17336				}
17337			} else {
17338				// Force control range into text range
17339				if (rng.item) {
17340					node = rng.item(0);
17341					rng = editor.getDoc().body.createTextRange();
17342					rng.moveToElementText(node);
17343				}
17344
17345				restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
17346				tmpRng = rng.duplicate();
17347				tmpRng.collapse(true);
17348				startOffset = tmpRng.move('character', offset) * -1;
17349
17350				if (!tmpRng.collapsed) {
17351					tmpRng = rng.duplicate();
17352					tmpRng.collapse(false);
17353					endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
17354				}
17355			}
17356
17357			// Wrap non block elements and text nodes
17358			node = rootNode.firstChild;
17359			rootNodeName = rootNode.nodeName.toLowerCase();
17360			while (node) {
17361				// TODO: Break this up, too complex
17362				if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
17363					schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
17364					// Remove empty text nodes
17365					if (node.nodeType === 3 && node.nodeValue.length === 0) {
17366						tempNode = node;
17367						node = node.nextSibling;
17368						dom.remove(tempNode);
17369						continue;
17370					}
17371
17372					if (!rootBlockNode) {
17373						rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
17374						node.parentNode.insertBefore(rootBlockNode, node);
17375						wrapped = true;
17376					}
17377
17378					tempNode = node;
17379					node = node.nextSibling;
17380					rootBlockNode.appendChild(tempNode);
17381				} else {
17382					rootBlockNode = null;
17383					node = node.nextSibling;
17384				}
17385			}
17386
17387			if (wrapped && restoreSelection) {
17388				if (rng.setStart) {
17389					rng.setStart(startContainer, startOffset);
17390					rng.setEnd(endContainer, endOffset);
17391					selection.setRng(rng);
17392				} else {
17393					// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
17394					try {
17395						rng = editor.getDoc().body.createTextRange();
17396						rng.moveToElementText(rootNode);
17397						rng.collapse(true);
17398						rng.moveStart('character', startOffset);
17399
17400						if (endOffset > 0) {
17401							rng.moveEnd('character', endOffset);
17402						}
17403
17404						rng.select();
17405					} catch (ex) {
17406						// Ignore
17407					}
17408				}
17409
17410				editor.nodeChanged();
17411			}
17412		}
17413
17414		// Force root blocks
17415		if (settings.forced_root_block) {
17416			editor.on('NodeChange', addRootBlocks);
17417		}
17418	};
17419});
17420
17421// Included from: js/tinymce/classes/EditorCommands.js
17422
17423/**
17424 * EditorCommands.js
17425 *
17426 * Copyright, Moxiecode Systems AB
17427 * Released under LGPL License.
17428 *
17429 * License: http://www.tinymce.com/license
17430 * Contributing: http://www.tinymce.com/contributing
17431 */
17432
17433/**
17434 * This class enables you to add custom editor commands and it contains
17435 * overrides for native browser commands to address various bugs and issues.
17436 *
17437 * @class tinymce.EditorCommands
17438 */
17439define("tinymce/EditorCommands", [
17440	"tinymce/html/Serializer",
17441	"tinymce/Env",
17442	"tinymce/util/Tools",
17443	"tinymce/dom/ElementUtils",
17444	"tinymce/dom/RangeUtils",
17445	"tinymce/dom/TreeWalker"
17446], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
17447	// Added for compression purposes
17448	var each = Tools.each, extend = Tools.extend;
17449	var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
17450	var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
17451	var TRUE = true, FALSE = false;
17452
17453	return function(editor) {
17454		var dom = editor.dom,
17455			selection = editor.selection,
17456			commands = {state: {}, exec: {}, value: {}},
17457			settings = editor.settings,
17458			formatter = editor.formatter,
17459			bookmark;
17460
17461		/**
17462		 * Executes the specified command.
17463		 *
17464		 * @method execCommand
17465		 * @param {String} command Command to execute.
17466		 * @param {Boolean} ui Optional user interface state.
17467		 * @param {Object} value Optional value for command.
17468		 * @return {Boolean} true/false if the command was found or not.
17469		 */
17470		function execCommand(command, ui, value) {
17471			var func;
17472
17473			command = command.toLowerCase();
17474			if ((func = commands.exec[command])) {
17475				func(command, ui, value);
17476				return TRUE;
17477			}
17478
17479			return FALSE;
17480		}
17481
17482		/**
17483		 * Queries the current state for a command for example if the current selection is "bold".
17484		 *
17485		 * @method queryCommandState
17486		 * @param {String} command Command to check the state of.
17487		 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
17488		 */
17489		function queryCommandState(command) {
17490			var func;
17491
17492			command = command.toLowerCase();
17493			if ((func = commands.state[command])) {
17494				return func(command);
17495			}
17496
17497			return -1;
17498		}
17499
17500		/**
17501		 * Queries the command value for example the current fontsize.
17502		 *
17503		 * @method queryCommandValue
17504		 * @param {String} command Command to check the value of.
17505		 * @return {Object} Command value of false if it's not found.
17506		 */
17507		function queryCommandValue(command) {
17508			var func;
17509
17510			command = command.toLowerCase();
17511			if ((func = commands.value[command])) {
17512				return func(command);
17513			}
17514
17515			return FALSE;
17516		}
17517
17518		/**
17519		 * Adds commands to the command collection.
17520		 *
17521		 * @method addCommands
17522		 * @param {Object} command_list Name/value collection with commands to add, the names can also be c
17522omma separated.
17523		 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
17524		 */
17525		function addCommands(command_list, type) {
17526			type = type || 'exec';
17527
17528			each(command_list, function(callback, command) {
17529				each(command.toLowerCase().split(','), function(command) {
17530					commands[type][command] = callback;
17531				});
17532			});
17533		}
17534
17535		// Expose public methods
17536		extend(this, {
17537			execCommand: execCommand,
17538			queryCommandState: queryCommandState,
17539			queryCommandValue: queryCommandValue,
17540			addCommands: addCommands
17541		});
17542
17543		// Private methods
17544
17545		function execNativeCommand(command, ui, value) {
17546			if (ui === undefined) {
17547				ui = FALSE;
17548			}
17549
17550			if (value === undefined) {
17551				value = null;
17552			}
17553
17554			return editor.getDoc().execCommand(command, ui, value);
17555		}
17556
17557		function isFormatMatch(name) {
17558			return formatter.match(name);
17559		}
17560
17561		function toggleFormat(name, value) {
17562			formatter.toggle(name, value ? {value: value} : undefined);
17563			editor.nodeChanged();
17564		}
17565
17566		function storeSelection(type) {
17567			bookmark = selection.getBookmark(type);
17568		}
17569
17570		function restoreSelection() {
17571			selection.moveToBookmark(bookmark);
17572		}
17573
17574		// Add execCommand overrides
17575		addCommands({
17576			// Ignore these, added for compatibility
17577			'mceResetDesignMode,mceBeginUndoLevel': function() {},
17578
17579			// Add undo manager logic
17580			'mceEndUndoLevel,mceAddUndoLevel': function() {
17581				editor.undoManager.add();
17582			},
17583
17584			'Cut,Copy,Paste': function(command) {
17585				var doc = editor.getDoc(), failed;
17586
17587				// Try executing the native command
17588				try {
17589					execNativeCommand(command);
17590				} catch (ex) {
17591					// Command failed
17592					failed = TRUE;
17593				}
17594
17595				// Present alert message about clipboard access not being available
17596				if (failed || !doc.queryCommandSupported(command)) {
17597					var msg = editor.translate(
17598						"Your browser doesn't support direct access to the clipboard. " +
17599						"Please use the Ctrl+X/C/V keyboard shortcuts instead."
17600					);
17601
17602					if (Env.mac) {
17603						msg = msg.replace(/Ctrl\+/g, '\u2318+');
17604					}
17605
17606					editor.windowManager.alert(msg);
17607				}
17608			},
17609
17610			// Override unlink command
17611			unlink: function() {
17612				if (selection.isCollapsed()) {
17613					var elm = selection.getNode();
17614					if (elm.tagName == 'A') {
17615						editor.dom.remove(elm, true);
17616					}
17617
17618					return;
17619				}
17620
17621				formatter.remove("link");
17622			},
17623
17624			// Override justify commands to use the text formatter engine
17625			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
17626				var align = command.substring(7);
17627
17628				if (align == 'full') {
17629					align = 'justify';
17630				}
17631
17632				// Remove all other alignments first
17633				each('left,center,right,justify'.split(','), function(name) {
17634					if (align != name) {
17635						formatter.remove('align' + name);
17636					}
17637				});
17638
17639				toggleFormat('align' + align);
17640				execCommand('mceRepaint');
17641			},
17642
17643			// Override list commands to fix WebKit bug
17644			'InsertUnorderedList,InsertOrderedList': function(command) {
17645				var listElm, listParent;
17646
17647				execNativeCommand(command);
17648
17649				// WebKit produces lists within block elements so we need to split them
17650				// we will replace the native list creation logic to custom logic later on
17651				// TODO: Remove this when the list creation logic is removed
17652				listElm = dom.getParent(selection.getNode(), 'ol,ul');
17653				if (listElm) {
17654					listParent = listElm.parentNode;
17655
17656					// If list is within a text block then split that block
17657					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
17658						storeSelection();
17659						dom.split(listParent, listElm);
17660						restoreSelection();
17661					}
17662				}
17663			},
17664
17665			// Override commands to use the text formatter engine
17666			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
17667				toggleFormat(command);
17668			},
17669
17670			// Override commands to use the text formatter engine
17671			'ForeColor,HiliteColor,FontName': function(command, ui, value) {
17672				toggleFormat(command, value);
17673			},
17674
17675			FontSize: function(command, ui, value) {
17676				var fontClasses, fontSizes;
17677
17678				// Convert font size 1-7 to styles
17679				if (value >= 1 && value <= 7) {
17680					fontSizes = explode(settings.font_size_style_values);
17681					fontClasses = explode(settings.font_size_classes);
17682
17683					if (fontClasses) {
17684						value = fontClasses[value - 1] || value;
17685					} else {
17686						value = fontSizes[value - 1] || value;
17687					}
17688				}
17689
17690				toggleFormat(command, value);
17691			},
17692
17693			RemoveFormat: function(command) {
17694				formatter.remove(command);
17695			},
17696
17697			mceBlockQuote: function() {
17698				toggleFormat('blockquote');
17699			},
17700
17701			FormatBlock: function(command, ui, value) {
17702				return toggleFormat(value || 'p');
17703			},
17704
17705			mceCleanup: function() {
17706				var bookmark = selection.getBookmark();
17707
17708				editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
17709
17710				selection.moveToBookmark(bookmark);
17711			},
17712
17713			mceRemoveNode: function(command, ui, value) {
17714				var node = value || selection.getNode();
17715
17716				// Make sure that the body node isn't removed
17717				if (node != editor.getBody()) {
17718					storeSelection();
17719					editor.dom.remove(node, TRUE);
17720					restoreSelection();
17721				}
17722			},
17723
17724			mceSelectNodeDepth: function(command, ui, value) {
17725				var counter = 0;
17726
17727				dom.getParent(selection.getNode(), function(node) {
17728					if (node.nodeType == 1 && counter++ == value) {
17729						selection.select(node);
17730						return FALSE;
17731					}
17732				}, editor.getBody());
17733			},
17734
17735			mceSelectNode: function(command, ui, value) {
17736				selection.select(value);
17737			},
17738
17739			mceInsertContent: function(command, ui, value) {
17740				var parser, serializer, parentNode, rootNode, fragment, args;
17741				var marker, rng, node, node2, bookmarkHtml, merge;
17742				var textInlineElements = editor.schema.getTextInlineElements();
17743
17744				function trimOrPaddLeftRight(html) {
17745					var rng, container, offset;
17746
17747					rng = selection.getRng(true);
17748					container = rng.startContainer;
17749					offset = rng.startOffset;
17750
17751					function hasSiblingText(siblingName) {
17752						return container[siblingName] && container[siblingName].nodeType == 3;
17753					}
17754
17755					if (container.nodeType == 3) {
17756						if (offset > 0) {
17757							html = html.replace(/^&nbsp;/, ' ');
17758						} else if (!hasSiblingText('previousSibling')) {
17759							html = html.replace(/^ /, '&nbsp;');
17760						}
17761
17762						if (offset < container.length) {
17763							html = html.replace(/&nbsp;(<br>|)$/, ' ');
17764						} else if (!hasSiblingText('nextSibling')) {
17765							html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
17766						}
17767					}
17768
17769					return html;
17770				}
17771
17772				function markInlineFormatElements(fragment) {
17773					if (merge) {
17774						for (node = fragment.firstChild; node; node = node.walk(true)) {
17775							if (textInlineElements[node.name]) {
17776								node.attr('data-mce-new', "true");
17777							}
17778						}
17779					}
17780				}
17781
17782				function reduceInlineTextElements() {
17783					if (merge) {
17784						var root = editor.getBody(), elementUtils = new ElementUtils(dom);
17785
17786						each(dom.select('*[data-mce-new]'), function(node) {
17787							node.removeAttribute('data-mce-new');
17788
17789							for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
17790								if (elementUtils.compare(testNode, node)) {
17791									dom.remove(node, true);
17792								}
17793							}
17794						});
17795					}
17796				}
17797
17798				if (typeof(value) != 'string') {
17799					merge = value.merge;
17800					value = value.content;
17801				}
17802
17803				// Check for whitespace before/after value
17804				if (/^ | $/.test(value)) {
17805					value = trimOrPaddLeftRight(value);
17806				}
17807
17808				// Setup parser and serializer
17809				parser = editor.parser;
17810				serializer = new Serializer({}, editor.schema);
17811				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">
17811&#xFEFF;&#x200B;</span>';
17812
17813				// Run beforeSetContent handlers on the HTML to be inserted
17814				args = {content: value, format: 'html', selection: true};
17815				editor.fire('BeforeSetContent', args);
17816				value = args.content;
17817
17818				// Add caret at end of contents if it's missing
17819				if (value.indexOf('{$caret}') == -1) {
17820					value += '{$caret}';
17821				}
17822
17823				// Replace the caret marker with a span bookmark element
17824				value = value.replace(/\{\$caret\}/, bookmarkHtml);
17825
17826				// If selection is at <body>|<p></p> then move it into <body><p>|</p>
17827				rng = selection.getRng();
17828				var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
17829				var body = editor.getBody();
17830				if (caretElement === body && selection.isCollapsed()) {
17831					if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
17832						rng = dom.createRng();
17833						rng.setStart(body.firstChild, 0);
17834						rng.setEnd(body.firstChild, 0);
17835						selection.setRng(rng);
17836					}
17837				}
17838
17839				// Insert node maker where we will insert the new HTML and get it's parent
17840				if (!selection.isCollapsed()) {
17841					editor.getDoc().execCommand('Delete', false, null);
17842				}
17843
17844				parentNode = selection.getNode();
17845
17846				// Parse the fragment within the context of the parent node
17847				var parserArgs = {context: parentNode.nodeName.toLowerCase()};
17848				fragment = parser.parse(value, parserArgs);
17849
17850				markInlineFormatElements(fragment);
17851
17852				// Move the caret to a more suitable location
17853				node = fragment.lastChild;
17854				if (node.attr('id') == 'mce_marker') {
17855					marker = node;
17856
17857					for (node = node.prev; node; node = node.walk(true)) {
17858						if (node.type == 3 || !dom.isBlock(node.name)) {
17859							if (editor.schema.isValidChild(node.parent.name, 'span')) {
17860								node.parent.insert(marker, node, node.name === 'br');
17861							}
17862							break;
17863						}
17864					}
17865				}
17866
17867				// If parser says valid we can insert the contents into that parent
17868				if (!parserArgs.invalid) {
17869					value = serializer.serialize(fragment);
17870
17871					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
17872					node = parentNode.firstChild;
17873					node2 = parentNode.lastChild;
17874					if (!node || (node === node2 && node.nodeName === 'BR')) {
17875						dom.setHTML(parentNode, value);
17876					} else {
17877						selection.setContent(value);
17878					}
17879				} else {
17880					// If the fragment was invalid within that context then we need
17881					// to parse and process the parent it's inserted into
17882
17883					// Insert bookmark node and get the parent
17884					selection.setContent(bookmarkHtml);
17885					parentNode = selection.getNode();
17886					rootNode = editor.getBody();
17887
17888					// Opera will return the document node when selection is in root
17889					if (parentNode.nodeType == 9) {
17890						parentNode = node = rootNode;
17891					} else {
17892						node = parentNode;
17893					}
17894
17895					// Find the ancestor just before the root element
17896					while (node !== rootNode) {
17897						parentNode = node;
17898						node = node.parentNode;
17899					}
17900
17901					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
17902					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
17903					value = serializer.serialize(
17904						parser.parse(
17905							// Need to replace by using a function since $ in the contents would otherwise be a problem
17906							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
17907								return serializer.serialize(fragment);
17908							})
17909						)
17910					);
17911
17912					// Set the inner/outer HTML depending on if we are in the root or not
17913					if (parentNode == rootNode) {
17914						dom.setHTML(rootNode, value);
17915					} else {
17916						dom.setOuterHTML(parentNode, value);
17917					}
17918				}
17919
17920				reduceInlineTextElements();
17921
17922				marker = dom.get('mce_marker');
17923				selection.scrollIntoView(marker);
17924
17925				// Move selection before marker and remove it
17926				rng = dom.createRng();
17927
17928				// If previous sibling is a text node set the selection to the end of that node
17929				node = marker.previousSibling;
17930				if (node && node.nodeType == 3) {
17931					rng.setStart(node, node.nodeValue.length);
17932
17933					// TODO: Why can't we normalize on IE
17934					if (!isIE) {
17935						node2 = marker.nextSibling;
17936						if (node2 && node2.nodeType == 3) {
17937							node.appendData(node2.data);
17938							node2.parentNode.removeChild(node2);
17939						}
17940					}
17941				} else {
17942					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
17943					rng.setStartBefore(marker);
17944					rng.setEndBefore(marker);
17945				}
17946
17947				// Remove the marker node and set the new range
17948				dom.remove(marker);
17949				selection.setRng(rng);
17950
17951				// Dispatch after event and add any visual elements needed
17952				editor.fire('SetContent', args);
17953				editor.addVisual();
17954			},
17955
17956			mceInsertRawHTML: function(command, ui, value) {
17957				selection.setContent('tiny_mce_marker');
17958				editor.setContent(
17959					editor.getContent().replace(/tiny_mce_marker/g, function() {
17960						return value;
17961					})
17962				);
17963			},
17964
17965			mceToggleFormat: function(command, ui, value) {
17966				toggleFormat(value);
17967			},
17968
17969			mceSetContent: function(command, ui, value) {
17970				editor.setContent(value);
17971			},
17972
17973			'Indent,Outdent': function(command) {
17974				var intentValue, indentUnit, value;
17975
17976				// Setup indent level
17977				intentValue = settings.indentation;
17978				indentUnit = /[a-z%]+$/i.exec(intentValue);
17979				intentValue = parseInt(intentValue, 10);
17980
17981				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
17982					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
17983					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
17984						formatter.apply('div');
17985					}
17986
17987					each(selection.getSelectedBlocks(), function(element) {
17988						if (element.nodeName != "LI") {
17989							var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
17990
17991							indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
17992
17993							if (command == 'outdent') {
17994								value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
17995								dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
17996							} else {
17997								value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
17998								dom.setStyle(element, indentStyleName, value);
17999							}
18000						}
18001					});
18002				} else {
18003					execNativeCommand(command);
18004				}
18005			},
18006
18007			mceRepaint: function() {
18008				if (isGecko) {
18009					try {
18010						storeSelection(TRUE);
18011
18012						if (selection.getSel()) {
18013							selection.getSel().selectAllChildren(editor.getBody());
18014						}
18015
18016						selection.collapse(TRUE);
18017						restoreSelection();
18018					} catch (ex) {
18019						// Ignore
18020					}
18021				}
18022			},
18023
18024			InsertHorizontalRule: function() {
18025				editor.execCommand('mceInsertContent', false, '<hr />');
18026			},
18027
18028			mceToggleVisualAid: function() {
18029				editor.hasVisual = !editor.hasVisual;
18030				editor.addVisual();
18031			},
18032
18033			mceReplaceContent: function(command, ui, value) {
18034				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
18035			},
18036
18037			mceInsertLink: function(command, ui, value) {
18038				var anchor;
18039
18040				if (typeof(value) == 'string') {
18041					value = {href: value};
18042				}
18043
18044				anchor = dom.getParent(selection.getNode(), 'a');
18045
18046				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
18047				value.href = value.href.replace(' ', '%20');
18048
18049				// Remove existing links if there could be child links or that the href isn't specified
18050				if (!anchor || !value.href) {
18051					formatter.remove('link');
18052				}
18053
18054				// Apply new link to selection
18055				if (value.href) {
18056					formatter.apply('link', value, anchor);
18057				}
18058			},
18059
18060			selectAll: function() {
18061				var root = dom.getRoot(), rng;
18062
18063				if (selection.getRng().setStart) {
18064					rng = dom.createRng();
18065					rng.setStart(root, 0);
18066					rng.setEnd(root, root.childNodes.length);
18067					selection.setRng(rng);
18068				} else {
18069					// IE will render it's own root level block elements and sometimes
18070					// even put font elements in them when the user starts typing. So we need to
18071					// move the selection to a more suitable element from this:
18072					// <body>|<p></p></body> to this: <body><p>|</p></body>
18073					rng = selection.getRng();
18074					if (!rng.item) {
18075						rng.moveToElementText(root);
18076						rng.select();
18077					}
18078				}
18079			},
18080
18081			"delete": function() {
18082				execNativeCommand("Delete");
18083
18084				// Check if body is empty after the delete call if so then set the contents
18085				// to an empty string and move the caret to any block produced by that operation
18086				// this fixes the issue with root blocks not being properly produced after a delete call on IE
18087				var body = editor.getBody();
18088
18089				if (dom.isEmpty(body)) {
18090					editor.setContent('');
18091
18092					if (body.firstChild && dom.isBlock(body.firstChild)) {
18093						editor.selection.setCursorLocation(body.firstChild, 0);
18094					} else {
18095						editor.selection.setCursorLocation(body, 0);
18096					}
18097				}
18098			},
18099
18100			mceNewDocument: function() {
18101				editor.setContent('');
18102			},
18103
18104			InsertLineBreak: function(command, ui, value) {
18105				// We load the current event in from EnterKey.js when appropriate to heed
18106				// certain event-specific variations such as ctrl-enter in a list
18107				var evt = value;
18108				var brElm, extraBr, marker;
18109				var rng = selection.getRng(true);
18110				new RangeUtils(dom).normalize(rng);
18111
18112				var offset = rng.startOffset;
18113				var container = rng.startContainer;
18114
18115				// Resolve node index
18116				if (container.nodeType == 1 && container.hasChildNodes()) {
18117					var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18118
18119					container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18120					if (isAfterLastNodeInContainer && container.nodeType == 3) {
18121						offset = container.nodeValue.length;
18122					} else {
18123						offset = 0;
18124					}
18125				}
18126
18127				var parentBlock = dom.getParent(container, dom.isBlock);
18128				var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18129				var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18130				var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18131
18132				// Enter inside block contained within a LI then split or insert before/after LI
18133				var isControlKey = evt && evt.ctrlKey;
18134				if (containerBlockName == 'LI' && !isControlKey) {
18135					parentBlock = containerBlock;
18136					parentBlockName = containerBlockName;
18137				}
18138
18139				// Walks the parent block to the right and look for BR elements
18140				function hasRightSideContent() {
18141					var walker = new TreeWalker(container, parentBlock), node;
18142					var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18143
18144					while ((node = walker.next())) {
18145						if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
18146							return true;
18147						}
18148					}
18149				}
18150
18151				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18152					// Insert extra BR element at the end block elements
18153					if (!isOldIE && !hasRightSideContent()) {
18154						brElm = dom.create('br');
18155						rng.insertNode(brElm);
18156						rng.setStartAfter(brElm);
18157						rng.setEndAfter(brElm);
18158						extraBr = true;
18159					}
18160				}
18161
18162				brElm = dom.create('br');
18163				rng.insertNode(brElm);
18164
18165				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18166				var documentMode = dom.doc.documentMode;
18167				if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18168					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18169				}
18170
18171				// Insert temp marker and scroll to that
18172				marker = dom.create('span', {}, '&nbsp;');
18173				brElm.parentNode.insertBefore(marker, brElm);
18174				selection.scrollIntoView(marker);
18175				dom.remove(marker);
18176
18177				if (!extraBr) {
18178					rng.setStartAfter(brElm);
18179					rng.setEndAfter(brElm);
18180				} else {
18181					rng.setStartBefore(brElm);
18182					rng.setEndBefore(brElm);
18183				}
18184
18185				selection.setRng(rng);
18186				editor.undoManager.add();
18187
18188				return TRUE;
18189			}
18190		});
18191
18192		// Add queryCommandState overrides
18193		addCommands({
18194			// Override justify commands
18195			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
18196				var name = 'align' + command.substring(7);
18197				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
18198				var matches = map(nodes, function(node) {
18199					return !!formatter.matchNode(node, name);
18200				});
18201				return inArray(matches, TRUE) !== -1;
18202			},
18203
18204			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
18205				return isFormatMatch(command);
18206			},
18207
18208			mceBlockQuote: function() {
18209				return isFormatMatch('blockquote');
18210			},
18211
18212			Outdent: function() {
18213				var node;
18214
18215				if (settings.inline_styles) {
18216					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
18217						return TRUE;
18218					}
18219
18220					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
18221						return TRUE;
18222					}
18223				}
18224
18225				return (
18226					queryCommandState('InsertUnorderedList') ||
18227					queryCommandState('InsertOrderedList') ||
18228					(!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
18229				);
18230			},
18231
18232			'InsertUnorderedList,InsertOrderedList': function(command) {
18233				var list = dom.getParent(selection.getNode(), 'ul,ol');
18234
18235				return list &&
18236					(
18237						command === 'insertunorderedlist' && list.tagName === 'UL' ||
18238						command === 'insertorderedlist' && list.tagName === 'OL'
18239					);
18240			}
18241		}, 'state');
18242
18243		// Add queryCommandValue overrides
18244		addCommands({
18245			'FontSize,FontName': function(command) {
18246				var value = 0, parent;
18247
18248				if ((parent = dom.getParent(selection.getNode(), 'span'))) {
18249					if (command == 'fontsize') {
18250						value = parent.style.fontSize;
18251					} else {
18252						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
18253					}
18254				}
18255
18256				return value;
18257			}
18258		}, 'value');
18259
18260		// Add undo manager logic
18261		addCommands({
18262			Undo: function() {
18263				editor.undoManager.undo();
18264			},
18265
18266			Redo: function() {
18267				editor.undoManager.redo();
18268			}
18269		});
18270	};
18271});
18272
18273// Included from: js/tinymce/classes/util/URI.js
18274
18275/**
18276 * URI.js
18277 *
18278 * Copyright, Moxiecode Systems AB
18279 * Released under LGPL License.
18280 *
18281 * License: http://www.tinymce.com/license
18282 * Contributing: http://www.tinymce.com/contributing
18283 */
18284
18285/**
18286 * This class handles parsing, modification and serialization of URI/URL strings.
18287 * @class tinymce.util.URI
18288 */
18289define("tinymce/util/URI", [
18290	"tinymce/util/Tools"
18291], function(Tools) {
18292	var each = Tools.each, trim = Tools.trim;
18293	var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' ');
18294	var DEFAULT_PORTS = {
18295		'ftp': 21,
18296		'http': 80,
18297		'https': 443,
18298		'mailto': 25
18299	};
18300
18301	/**
18302	 * Constructs a new URI instance.
18303	 *
18304	 * @constructor
18305	 * @method URI
18306	 * @param {String} url URI string to parse.
18307	 * @param {Object} settings Optional settings object.
18308	 */
18309	function URI(url, settings) {
18310		var self = this, baseUri, base_url;
18311
18312		url = trim(url);
18313		settings = self.settings = settings || {};
18314		baseUri = settings.base_uri;
18315
18316		// Strange app protocol that isn't http/https or local anchor
18317		// For example: mailto,skype,tel etc.
18318		if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
18319			self.source = url;
18320			return;
18321		}
18322
18323		var isProtocolRelative = url.indexOf('//') === 0;
18324
18325		// Absolute path with no host, fake host and protocol
18326		if (url.indexOf('/') === 0 && !isProtocolRelative) {
18327			url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
18328		}
18329
18330		// Relative path http:// or protocol relative //path
18331		if (!/^[\w\-]*:?\/\//.test(url)) {
18332			base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
18333			if (settings.base_uri.protocol === "") {
18334				url = '//mce_host' + self.toAbsPath(base_url, url);
18335			} else {
18336				url = /([^#?]*)([#?]?.*)/.exec(url);
18337				url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
18338			}
18339		}
18340
18341		// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
18342		url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
18343
18344		/*jshint maxlen: 255 */
18345		/*eslint max-len: 0 */
18346		url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
18347
18348		each(queryParts, function(v, i) {
18349			var part = url[i];
18350
18351			// Zope 3 workaround, they use @@something
18352			if (part) {
18353				part = part.replace(/\(mce_at\)/g, '@@');
18354			}
18355
18356			self[v] = part;
18357		});
18358
18359		if (baseUri) {
18360			if (!self.protocol) {
18361				self.protocol = baseUri.protocol;
18362			}
18363
18364			if (!self.userInfo) {
18365				self.userInfo = baseUri.userInfo;
18366			}
18367
18368			if (!self.port && self.host === 'mce_host') {
18369				self.port = baseUri.port;
18370			}
18371
18372			if (!self.host || self.host === 'mce_host') {
18373				self.host = baseUri.host;
18374			}
18375
18376			self.source = '';
18377		}
18378
18379		if (isProtocolRelative) {
18380			self.protocol = '';
18381		}
18382
18383		//t.path = t.path || '/';
18384	}
18385
18386	URI.prototype = {
18387		/**
18388		 * Sets the internal path part of the URI.
18389		 *
18390		 * @method setPath
18391		 * @param {string} path Path string to set.
18392		 */
18393		setPath: function(path) {
18394			var self = this;
18395
18396			path = /^(.*?)\/?(\w+)?$/.exec(path);
18397
18398			// Update path parts
18399			self.path = path[0];
18400			self.directory = path[1];
18401			self.file = path[2];
18402
18403			// Rebuild source
18404			self.source = '';
18405			self.getURI();
18406		},
18407
18408		/**
18409		 * Converts the specified URI into a relative URI based on the current URI instance location.
18410		 *
18411		 * @method toRelative
18412		 * @param {String} uri URI to convert into a relative path/URI.
18413		 * @return {String} Relative URI from the point specified in the current URI instance.
18414		 * @example
18415		 * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
18416		 * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
18417		 */
18418		toRelative: function(uri) {
18419			var self = this, output;
18420
18421			if (uri === "./") {
18422				return uri;
18423			}
18424
18425			uri = new URI(uri, {base_uri: self});
18426
18427			// Not on same domain/port or protocol
18428			if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
18429				(self.protocol != uri.protocol && uri.protocol !== "")) {
18430				return uri.getURI();
18431			}
18432
18433			var tu = self.getURI(), uu = uri.getURI();
18434
18435			// Allow usage of the base_uri when relative_urls = true
18436			if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
18437				return tu;
18438			}
18439
18440			output = self.toRelPath(self.path, uri.path);
18441
18442			// Add query
18443			if (uri.query) {
18444				output += '?' + uri.query;
18445			}
18446
18447			// Add anchor
18448			if (uri.anchor) {
18449				output += '#' + uri.anchor;
18450			}
18451
18452			return output;
18453		},
18454
18455		/**
18456		 * Converts the specified URI into a absolute URI based on the current URI instance location.
18457		 *
18458		 * @method toAbsolute
18459		 * @param {String} uri URI to convert into a relative path/URI.
18460		 * @param {Boolean} noHost No host and protocol prefix.
18461		 * @return {String} Absolute URI from the point specified in the current URI instance.
18462		 * @example
18463		 * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
18464		 * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
18465		 */
18466		toAbsolute: function(uri, noHost) {
18467			uri = new URI(uri, {base_uri: this});
18468
18469			return uri.getURI(noHost && this.isSameOrigin(uri));
18470		},
18471
18472		/**
18473		 * Determine whether the given URI has the same origin as this URI.  Based on RFC-6454.
18474		 * Supports default ports for protocols listed in DEFAULT_PORTS.  Unsupported protocols will fail safe: they
18475		 * won't match, if the port specifications differ.
18476		 *
18477		 * @method isSameOrigin
18478		 * @param {tinymce.util.URI} uri Uri instance to compare.
18479		 * @returns {Boolean} True if the origins are the same.
18480		 */
18481		isSameOrigin: function(uri) {
18482			if (this.host == uri.host && this.protocol == uri.protocol) {
18483				if (this.port == uri.port) {
18484					return true;
18485				}
18486
18487				var defaultPort = DEFAULT_PORTS[this.protocol];
18488				if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
18489					return true;
18490				}
18491			}
18492
18493			return false;
18494		},
18495
18496		/**
18497		 * Converts a absolute path into a relative path.
18498		 *
18499		 * @method toRelPath
18500		 * @param {String} base Base point to convert the path from.
18501		 * @param {String} path Absolute path to convert into a relative path.
18502		 */
18503		toRelPath: function(base, path) {
18504			var items, breakPoint = 0, out = '', i, l;
18505
18506			// Split the paths
18507			base = base.substring(0, base.lastIndexOf('/'));
18508			base = base.split('/');
18509			items = path.split('/');
18510
18511			if (base.length >= items.length) {
18512				for (i = 0, l = base.length; i < l; i++) {
18513					if (i >= items.length || base[i] != items[i]) {
18514						breakPoint = i + 1;
18515						break;
18516					}
18517				}
18518			}
18519
18520			if (base.length < items.length) {
18521				for (i = 0, l = items.length; i < l; i++) {
18522					if (i >= base.length || base[i] != items[i]) {
18523						breakPoint = i + 1;
18524						break;
18525					}
18526				}
18527			}
18528
18529			if (breakPoint === 1) {
18530				return path;
18531			}
18532
18533			for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
18534				out += "../";
18535			}
18536
18537			for (i = breakPoint - 1, l = items.length; i < l; i++) {
18538				if (i != breakPoint - 1) {
18539					out += "/" + items[i];
18540				} else {
18541					out += items[i];
18542				}
18543			}
18544
18545			return out;
18546		},
18547
18548		/**
18549		 * Converts a relative path into a absolute path.
18550		 *
18551		 * @method toAbsPath
18552		 * @param {String} base Base point to convert the path from.
18553		 * @param {String} path Relative path to convert into an absolute path.
18554		 */
18555		toAbsPath: function(base, path) {
18556			var i, nb = 0, o = [], tr, outPath;
18557
18558			// Split paths
18559			tr = /\/$/.test(path) ? '/' : '';
18560			base = base.split('/');
18561			path = path.split('/');
18562
18563			// Remove empty chunks
18564			each(base, function(k) {
18565				if (k) {
18566					o.push(k);
18567				}
18568			});
18569
18570			base = o;
18571
18572			// Merge relURLParts chunks
18573			for (i = path.length - 1, o = []; i >= 0; i--) {
18574				// Ignore empty or .
18575				if (path[i].length === 0 || path[i] === ".") {
18576					continue;
18577				}
18578
18579				// Is parent
18580				if (path[i] === '..') {
18581					nb++;
18582					continue;
18583				}
18584
18585				// Move up
18586				if (nb > 0) {
18587					nb--;
18588					continue;
18589				}
18590
18591				o.push(path[i]);
18592			}
18593
18594			i = base.length - nb;
18595
18596			// If /a/b/c or /
18597			if (i <= 0) {
18598				outPath = o.reverse().join('/');
18599			} else {
18600				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
18601			}
18602
18603			// Add front / if it's needed
18604			if (outPath.indexOf('/') !== 0) {
18605				outPath = '/' + outPath;
18606			}
18607
18608			// Add traling / if it's needed
18609			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
18610				outPath += tr;
18611			}
18612
18613			return outPath;
18614		},
18615
18616		/**
18617		 * Returns the full URI of the internal structure.
18618		 *
18619		 * @method getURI
18620		 * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
18621		 */
18622		getURI: function(noProtoHost) {
18623			var s, self = this;
18624
18625			// Rebuild source
18626			if (!self.source || noProtoHost) {
18627				s = '';
18628
18629				if (!noProtoHost) {
18630					if (self.protocol) {
18631						s += self.protocol + '://';
18632					} else {
18633						s += '//';
18634					}
18635
18636					if (self.userInfo) {
18637						s += self.userInfo + '@';
18638					}
18639
18640					if (self.host) {
18641						s += self.host;
18642					}
18643
18644					if (self.port) {
18645						s += ':' + self.port;
18646					}
18647				}
18648
18649				if (self.path) {
18650					s += self.path;
18651				}
18652
18653				if (self.query) {
18654					s += '?' + self.query;
18655				}
18656
18657				if (self.anchor) {
18658					s += '#' + self.anchor;
18659				}
18660
18661				self.source = s;
18662			}
18663
18664			return self.source;
18665		}
18666	};
18667
18668	return URI;
18669});
18670
18671// Included from: js/tinymce/classes/util/Class.js
18672
18673/**
18674 * Class.js
18675 *
18676 * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved.
18677 */
18678
18679/**
18680 * This utilitiy class is used for easier inheritage.
18681 *
18682 * Features:
18683 * * Exposed super functions: this._super();
18684 * * Mixins
18685 * * Dummy functions
18686 * * Property functions: var value = object.value(); and object.value(newValue);
18687 * * Static functions
18688 * * Defaults settings
18689 */
18690define("tinymce/util/Class", [
18691	"tinymce/util/Tools"
18692], function(Tools) {
18693	var each = Tools.each, extend = Tools.extend;
18694
18695	var extendClass, initializing;
18696
18697	function Class() {
18698	}
18699
18700	// Provides classical inheritance, based on code made by John Resig
18701	Class.extend = extendClass = function(prop) {
18702		var self = this, _super = self.prototype, prototype, name, member;
18703
18704		// The dummy class constructor
18705		function Class() {
18706			var i, mixins, mixin, self = this;
18707
18708			// All construction is actually done in the init method
18709			if (!initializing) {
18710				// Run class constuctor
18711				if (self.init) {
18712					self.init.apply(self, arguments);
18713				}
18714
18715				// Run mixin constructors
18716				mixins = self.Mixins;
18717				if (mixins) {
18718					i = mixins.length;
18719					while (i--) {
18720						mixin = mixins[i];
18721						if (mixin.init) {
18722							mixin.init.apply(self, arguments);
18723						}
18724					}
18725				}
18726			}
18727		}
18728
18729		// Dummy function, needs to be extended in order to provide functionality
18730		function dummy() {
18731			return this;
18732		}
18733
18734		// Creates a overloaded method for the class
18735		// this enables you to use this._super(); to call the super function
18736		function createMethod(name, fn) {
18737			return function() {
18738				var self = this, tmp = self._super, ret;
18739
18740				self._super = _super[name];
18741				ret = fn.apply(self, arguments);
18742				self._super = tmp;
18743
18744				return ret;
18745			};
18746		}
18747
18748		// Instantiate a base class (but only create the instance,
18749		// don't run the init constructor)
18750		initializing = true;
18751
18752		/*eslint new-cap:0 */
18753		prototype = new self();
18754		initializing = false;
18755
18756		// Add mixins
18757		if (prop.Mixins) {
18758			each(prop.Mixins, function(mixin) {
18759				mixin = mixin;
18760
18761				for (var name in mixin) {
18762					if (name !== "init") {
18763						prop[name] = mixin[name];
18764					}
18765				}
18766			});
18767
18768			if (_super.Mixins) {
18769				prop.Mixins = _super.Mixins.concat(prop.Mixins);
18770			}
18771		}
18772
18773		// Generate dummy methods
18774		if (prop.Methods) {
18775			each(prop.Methods.split(','), function(name) {
18776				prop[name] = dummy;
18777			});
18778		}
18779
18780		// Generate property methods
18781		if (prop.Properties) {
18782			each(prop.Properties.split(','), function(name) {
18783				var fieldName = '_' + name;
18784
18785				prop[name] = function(value) {
18786					var self = this, undef;
18787
18788					// Set value
18789					if (value !== undef) {
18790						self[fieldName] = value;
18791
18792						return self;
18793					}
18794
18795					// Get value
18796					return self[fieldName];
18797				};
18798			});
18799		}
18800
18801		// Static functions
18802		if (prop.Statics) {
18803			each(prop.Statics, function(func, name) {
18804				Class[name] = func;
18805			});
18806		}
18807
18808		// Default settings
18809		if (prop.Defaults && _super.Defaults) {
18810			prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
18811		}
18812
18813		// Copy the properties over onto the new prototype
18814		for (name in prop) {
18815			member = prop[name];
18816
18817			if (typeof member == "function" && _super[name]) {
18818				prototype[name] = createMethod(name, member);
18819			} else {
18820				prototype[name] = member;
18821			}
18822		}
18823
18824		// Populate our constructed prototype object
18825		Class.prototype = prototype;
18826
18827		// Enforce the constructor to be what we expect
18828		Class.constructor = Class;
18829
18830		// And make this class extendible
18831		Class.extend = extendClass;
18832
18833		return Class;
18834	};
18835
18836	return Class;
18837});
18838
18839// Included from: js/tinymce/classes/util/EventDispatcher.js
18840
18841/**
18842 * EventDispatcher.js
18843 *
18844 * Copyright, Moxiecode Systems AB
18845 * Released under LGPL License.
18846 *
18847 * License: http://www.tinymce.com/license
18848 * Contributing: http://www.tinymce.com/contributing
18849 */
18850
18851/**
18852 * This class lets you add/remove and fire events by name on the specified scope. This makes
18853 * it easy to add event listener logic to any class.
18854 *
18855 * @class tinymce.util.EventDispatcher
18856 * @example
18857 *  var eventDispatcher = new EventDispatcher();
18858 *
18859 *  eventDispatcher.on('click', function() {console.log('data');});
18860 *  eventDispatcher.fire('click', {data: 123});
18861 */
18862define("tinymce/util/EventDispatcher", [
18863	"tinymce/util/Tools"
18864], function(Tools) {
18865	var nativeEvents = Tools.makeMap(
18866		"focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
18867		"mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
18868		"draggesture dragdrop drop drag submit " +
18869		"compositionstart compositionend compositionupdate touchstart touchend",
18870		' '
18871	);
18872
18873	function Dispatcher(settings) {
18874		var self = this, scope, bindings = {}, toggleEvent;
18875
18876		function returnFalse() {
18877			return false;
18878		}
18879
18880		function returnTrue() {
18881			return true;
18882		}
18883
18884		settings = settings || {};
18885		scope = settings.scope || self;
18886		toggleEvent = settings.toggleEvent || returnFalse;
18887
18888		/**
18889		 * Fires the specified event by name.
18890		 *
18891		 * @method fire
18892		 * @param {String} name Name of the event to fire.
18893		 * @param {Object?} args Event arguments.
18894		 * @return {Object} Event args instance passed in.
18895		 * @example
18896		 * instance.fire('event', {...});
18897		 */
18898		function fire(name, args) {
18899			var handlers, i, l, callback;
18900
18901			name = name.toLowerCase();
18902			args = args || {};
18903			args.type = name;
18904
18905			// Setup target is there isn't one
18906			if (!args.target) {
18907				args.target = scope;
18908			}
18909
18910			// Add event delegation methods if they are missing
18911			if (!args.preventDefault) {
18912				// Add preventDefault method
18913				args.preventDefault = function() {
18914					args.isDefaultPrevented = returnTrue;
18915				};
18916
18917				// Add stopPropagation
18918				args.stopPropagation = function() {
18919					args.isPropagationStopped = returnTrue;
18920				};
18921
18922				// Add stopImmediatePropagation
18923				args.stopImmediatePropagation = function() {
18924					args.isImmediatePropagationStopped = returnTrue;
18925				};
18926
18927				// Add event delegation states
18928				args.isDefaultPrevented = returnFalse;
18929				args.isPropagationStopped = returnFalse;
18930				args.isImmediatePropagationStopped = returnFalse;
18931			}
18932
18933			if (settings.beforeFire) {
18934				settings.beforeFire(args);
18935			}
18936
18937			handlers = bindings[name];
18938			if (handlers) {
18939				for (i = 0, l = handlers.length; i < l; i++) {
18940					callback = handlers[i];
18941
18942					// Unbind handlers marked with "once"
18943					if (callback.once) {
18944						off(name, callback.func);
18945					}
18946
18947					// Stop immediate propagation if needed
18948					if (args.isImmediatePropagationStopped()) {
18949						args.stopPropagation();
18950						return args;
18951					}
18952
18953					// If callback returns false then prevent default and stop all propagation
18954					if (callback.func.call(scope, args) === false) {
18955						args.preventDefault();
18956						return args;
18957					}
18958				}
18959			}
18960
18961			return args;
18962		}
18963
18964		/**
18965		 * Binds an event listener to a specific event by name.
18966		 *
18967		 * @method on
18968		 * @param {String} name Event name or space separated list of events to bind.
18969		 * @param {callback} callback Callback to be executed when the event occurs.
18970		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
18971		 * @return {Object} Current class instance.
18972		 * @example
18973		 * instance.on('event', function(e) {
18974		 *     // Callback logic
18975		 * });
18976		 */
18977		function on(name, callback, prepend, extra) {
18978			var handlers, names, i;
18979
18980			if (callback === false) {
18981				callback = returnFalse;
18982			}
18983
18984			if (callback) {
18985				callback = {
18986					func: callback
18987				};
18988
18989				if (extra) {
18990					Tools.extend(callback, extra);
18991				}
18992
18993				names = name.toLowerCase().split(' ');
18994				i = names.length;
18995				while (i--) {
18996					name = names[i];
18997					handlers = bindings[name];
18998					if (!handlers) {
18999						handlers = bindings[name] = [];
19000						toggleEvent(name, true);
19001					}
19002
19003					if (prepend) {
19004						handlers.unshift(callback);
19005					} else {
19006						handlers.push(callback);
19007					}
19008				}
19009			}
19010
19011			return self;
19012		}
19013
19014		/**
19015		 * Unbinds an event listener to a specific event by name.
19016		 *
19017		 * @method off
19018		 * @param {String?} name Name of the event to unbind.
19019		 * @param {callback?} callback Callback to unbind.
19020		 * @return {Object} Current class instance.
19021		 * @example
19022		 * // Unbind specific callback
19023		 * instance.off('event', handler);
19024		 *
19025		 * // Unbind all listeners by name
19026		 * instance.off('event');
19027		 *
19028		 * // Unbind all events
19029		 * instance.off();
19030		 */
19031		function off(name, callback) {
19032			var i, handlers, bindingName, names, hi;
19033
19034			if (name) {
19035				names = name.toLowerCase().split(' ');
19036				i = names.length;
19037				while (i--) {
19038					name = names[i];
19039					handlers = bindings[name];
19040
19041					// Unbind all handlers
19042					if (!name) {
19043						for (bindingName in bindings) {
19044							toggleEvent(bindingName, false);
19045							delete bindings[bindingName];
19046						}
19047
19048						return self;
19049					}
19050
19051					if (handlers) {
19052						// Unbind all by name
19053						if (!callback) {
19054							handlers.length = 0;
19055						} else {
19056							// Unbind specific ones
19057							hi = handlers.length;
19058							while (hi--) {
19059								if (handlers[hi].func === callback) {
19060									handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
19061									bindings[name] = handlers;
19062								}
19063							}
19064						}
19065
19066						if (!handlers.length) {
19067							toggleEvent(name, false);
19068							delete bindings[name];
19069						}
19070					}
19071				}
19072			} else {
19073				for (name in bindings) {
19074					toggleEvent(name, false);
19075				}
19076
19077				bindings = {};
19078			}
19079
19080			return self;
19081		}
19082
19083		/**
19084		 * Binds an event listener to a specific event by name
19085		 * and automatically unbind the event once the callback fires.
19086		 *
19087		 * @method once
19088		 * @param {String} name Event name or space separated list of events to bind.
19089		 * @param {callback} callback Callback to be executed when the event occurs.
19090		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
19091		 * @return {Object} Current class instance.
19092		 * @example
19093		 * instance.once('event', function(e) {
19094		 *     // Callback logic
19095		 * });
19096		 */
19097		function once(name, callback, prepend) {
19098			return on(name, callback, prepend, {once: true});
19099		}
19100
19101		/**
19102		 * Returns true/false if the dispatcher has a event of the specified name.
19103		 *
19104		 * @method has
19105		 * @param {String} name Name of the event to check for.
19106		 * @return {Boolean} true/false if the event exists or not.
19107		 */
19108		function has(name) {
19109			name = name.toLowerCase();
19110			return !(!bindings[name] || bindings[name].length === 0);
19111		}
19112
19113		// Expose
19114		self.fire = fire;
19115		self.on = on;
19116		self.off = off;
19117		self.once = once;
19118		self.has = has;
19119	}
19120
19121	/**
19122	 * Returns true/false if the specified event name is a native browser event or not.
19123	 *
19124	 * @method isNative
19125	 * @param {String} name Name to check if it's native.
19126	 * @return {Boolean} true/false if the event is native or not.
19127	 * @static
19128	 */
19129	Dispatcher.isNative = function(name) {
19130		return !!nativeEvents[name.toLowerCase()];
19131	};
19132
19133	return Dispatcher;
19134});
19135
19136// Included from: js/tinymce/classes/ui/Selector.js
19137
19138/**
19139 * Selector.js
19140 *
19141 * Copyright, Moxiecode Systems AB
19142 * Released under LGPL License.
19143 *
19144 * License: http://www.tinymce.com/license
19145 * Contributing: http://www.tinymce.com/contributing
19146 */
19147
19148/*eslint no-nested-ternary:0 */
19149
19150/**
19151 * Selector engine, enables you to select controls by using CSS like expressions.
19152 * We currently only support basic CSS expressions to reduce the size of the core
19153 * and the ones we support should be enough for most cases.
19154 *
19155 * @example
19156 * Supported expressions:
19157 *  element
19158 *  element#name
19159 *  element.class
19160 *  element[attr]
19161 *  element[attr*=value]
19162 *  element[attr~=value]
19163 *  element[attr!=value]
19164 *  element[attr^=value]
19165 *  element[attr$=value]
19166 *  element:<state>
19167 *  element:not(<expression>)
19168 *  element:first
19169 *  element:last
19170 *  element:odd
19171 *  element:even
19172 *  element element
19173 *  element > element
19174 *
19175 * @class tinymce.ui.Selector
19176 */
19177define("tinymce/ui/Selector", [
19178	"tinymce/util/Class"
19179], function(Class) {
19180	"use strict";
19181
19182	/**
19183	 * Produces an array with a unique set of objects. It will not compare the values
19184	 * but the references of the objects.
19185	 *
19186	 * @private
19187	 * @method unqiue
19188	 * @param {Array} array Array to make into an array with unique items.
19189	 * @return {Array} Array with unique items.
19190	 */
19191	function unique(array) {
19192		var uniqueItems = [], i = array.length, item;
19193
19194		while (i--) {
19195			item = array[i];
19196
19197			if (!item.__checked) {
19198				uniqueItems.push(item);
19199				item.__checked = 1;
19200			}
19201		}
19202
19203		i = uniqueItems.length;
19204		while (i--) {
19205			delete uniqueItems[i].__checked;
19206		}
19207
19208		return uniqueItems;
19209	}
19210
19211	var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
19212
19213	/*jshint maxlen:255 */
19214	/*eslint max-len:0 */
19215	var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
19216		whiteSpace = /^\s*|\s*$/g,
19217		Collection;
19218
19219	var Selector = Class.extend({
19220		/**
19221		 * Constructs a new Selector instance.
19222		 *
19223		 * @constructor
19224		 * @method init
19225		 * @param {String} selector CSS like selector expression.
19226		 */
19227		init: function(selector) {
19228			var match = this.match;
19229
19230			function compileNameFilter(name) {
19231				if (name) {
19232					name = name.toLowerCase();
19233
19234					return function(item) {
19235						return name === '*' || item.type === name;
19236					};
19237				}
19238			}
19239
19240			function compileIdFilter(id) {
19241				if (id) {
19242					return function(item) {
19243						return item._name === id;
19244					};
19245				}
19246			}
19247
19248			function compileClassesFilter(classes) {
19249				if (classes) {
19250					classes = classes.split('.');
19251
19252					return function(item) {
19253						var i = classes.length;
19254
19255						while (i--) {
19256							if (!item.hasClass(classes[i])) {
19257								return false;
19258							}
19259						}
19260
19261						return true;
19262					};
19263				}
19264			}
19265
19266			function compileAttrFilter(name, cmp, check) {
19267				if (name) {
19268					return function(item) {
19269						var value = item[name] ? item[name]() : '';
19270
19271						return !cmp ? !!check :
19272							cmp === "=" ? value === check :
19273							cmp === "*=" ? value.indexOf(check) >= 0 :
19274							cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
19275							cmp === "!=" ? value != check :
19276							cmp === "^=" ? value.indexOf(check) === 0 :
19277							cmp === "$=" ? value.substr(value.length - check.length) === check :
19278							false;
19279					};
19280				}
19281			}
19282
19283			function compilePsuedoFilter(name) {
19284				var notSelectors;
19285
19286				if (name) {
19287					name = /(?:not\((.+)\))|(.+)/i.exec(name);
19288
19289					if (!name[1]) {
19290						name = name[2];
19291
19292						return function(item, index, length) {
19293							return name === 'first' ? index === 0 :
19294								name === 'last' ? index === length - 1 :
19295								name === 'even' ? index % 2 === 0 :
19296								name === 'odd' ? index % 2 === 1 :
19297								item[name] ? item[name]() :
19298								false;
19299						};
19300					} else {
19301						// Compile not expression
19302						notSelectors = parseChunks(name[1], []);
19303
19304						return function(item) {
19305							return !match(item, notSelectors);
19306						};
19307					}
19308				}
19309			}
19310
19311			function compile(selector, filters, direct) {
19312				var parts;
19313
19314				function add(filter) {
19315					if (filter) {
19316						filters.push(filter);
19317					}
19318				}
19319
19320				// Parse expression into parts
19321				parts = expression.exec(selector.replace(whiteSpace, ''));
19322
19323				add(compileNameFilter(parts[1]));
19324				add(compileIdFilter(parts[2]));
19325				add(compileClassesFilter(parts[3]));
19326				add(compileAttrFilter(parts[4], parts[5], parts[6]));
19327				add(compilePsuedoFilter(parts[7]));
19328
19329				// Mark the filter with psuedo for performance
19330				filters.psuedo = !!parts[7];
19331				filters.direct = direct;
19332
19333				return filters;
19334			}
19335
19336			// Parser logic based on Sizzle by John Resig
19337			function parseChunks(selector, selectors) {
19338				var parts = [], extra, matches, i;
19339
19340				do {
19341					chunker.exec("");
19342					matches = chunker.exec(selector);
19343
19344					if (matches) {
19345						selector = matches[3];
19346						parts.push(matches[1]);
19347
19348						if (matches[2]) {
19349							extra = matches[3];
19350							break;
19351						}
19352					}
19353				} while (matches);
19354
19355				if (extra) {
19356					parseChunks(extra, selectors);
19357				}
19358
19359				selector = [];
19360				for (i = 0; i < parts.length; i++) {
19361					if (parts[i] != '>') {
19362						selector.push(compile(parts[i], [], parts[i - 1] === '>'));
19363					}
19364				}
19365
19366				selectors.push(selector);
19367
19368				return selectors;
19369			}
19370
19371			this._selectors = parseChunks(selector, []);
19372		},
19373
19374		/**
19375		 * Returns true/false if the selector matches the specified control.
19376		 *
19377		 * @method match
19378		 * @param {tinymce.ui.Control} control Control to match agains the selector.
19379		 * @param {Array} selectors Optional array of selectors, mostly used internally.
19380		 * @return {Boolean} true/false state if the control matches or not.
19381		 */
19382		match: function(control, selectors) {
19383			var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
19384
19385			selectors = selectors || this._selectors;
19386			for (i = 0, l = selectors.length; i < l; i++) {
19387				selector = selectors[i];
19388				sl = selector.length;
19389				item = control;
19390				count = 0;
19391
19392				for (si = sl - 1; si >= 0; si--) {
19393					filters = selector[si];
19394
19395					while (item) {
19396						// Find the index and length since a psuedo filter like :first needs it
19397						if (filters.psuedo) {
19398							siblings = item.parent().items();
19399							index = length = siblings.length;
19400							while (index--) {
19401								if (siblings[index] === item) {
19402									break;
19403								}
19404							}
19405						}
19406
19407						for (fi = 0, fl = filters.length; fi < fl; fi++) {
19408							if (!filters[fi](item, index, length)) {
19409								fi = fl + 1;
19410								break;
19411							}
19412						}
19413
19414						if (fi === fl) {
19415							count++;
19416							break;
19417						} else {
19418							// If it didn't match the right most expression then
19419							// break since it's no point looking at the parents
19420							if (si === sl - 1) {
19421								break;
19422							}
19423						}
19424
19425						item = item.parent();
19426					}
19427				}
19428
19429				// If we found all selectors then return true otherwise continue looking
19430				if (count === sl) {
19431					return true;
19432				}
19433			}
19434
19435			return false;
19436		},
19437
19438		/**
19439		 * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
19440		 *
19441		 * @method find
19442		 * @param {tinymce.ui.Control} container Container to look for items in.
19443		 * @return {tinymce.ui.Collection} Collection with matched elements.
19444		 */
19445		find: function(container) {
19446			var matches = [], i, l, selectors = this._selectors;
19447
19448			function collect(items, selector, index) {
19449				var i, l, fi, fl, item, filters = selector[index];
19450
19451				for (i = 0, l = items.length; i < l; i++) {
19452					item = items[i];
19453
19454					// Run each filter agains the item
19455					for (fi = 0, fl = filters.length; fi < fl; fi++) {
19456						if (!filters[fi](item, i, l)) {
19457							fi = fl + 1;
19458							break;
19459						}
19460					}
19461
19462					// All filters matched the item
19463					if (fi === fl) {
19464						// Matched item is on the last expression like: panel toolbar [button]
19465						if (index == selector.length - 1) {
19466							matches.push(item);
19467						} else {
19468							// Collect next expression type
19469							if (item.items) {
19470								collect(item.items(), selector, index + 1);
19471							}
19472						}
19473					} else if (filters.direct) {
19474						return;
19475					}
19476
19477					// Collect child items
19478					if (item.items) {
19479						collect(item.items(), selector, index);
19480					}
19481				}
19482			}
19483
19484			if (container.items) {
19485				for (i = 0, l = selectors.length; i < l; i++) {
19486					collect(container.items(), selectors[i], 0);
19487				}
19488
19489				// Unique the matches if needed
19490				if (l > 1) {
19491					matches = unique(matches);
19492				}
19493			}
19494
19495			// Fix for circular reference
19496			if (!Collection) {
19497				// TODO: Fix me!
19498				Collection = Selector.Collection;
19499			}
19500
19501			return new Collection(matches);
19502		}
19503	});
19504
19505	return Selector;
19506});
19507
19508// Included from: js/tinymce/classes/ui/Collection.js
19509
19510/**
19511 * Collection.js
19512 *
19513 * Copyright, Moxiecode Systems AB
19514 * Released under LGPL License.
19515 *
19516 * License: http://www.tinymce.com/license
19517 * Contributing: http://www.tinymce.com/contributing
19518 */
19519
19520/**
19521 * Control collection, this class contains control instances and it enables you to
19522 * perform actions on all the contained items. This is very similar to how jQuery works.
19523 *
19524 * @example
19525 * someCollection.show().disabled(true);
19526 *
19527 * @class tinymce.ui.Collection
19528 */
19529define("tinymce/ui/Collection", [
19530	"tinymce/util/Tools",
19531	"tinymce/ui/Selector",
19532	"tinymce/util/Class"
19533], function(Tools, Selector, Class) {
19534	"use strict";
19535
19536	var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
19537
19538	proto = {
19539		/**
19540		 * Current number of contained control instances.
19541		 *
19542		 * @field length
19543		 * @type Number
19544		 */
19545		length: 0,
19546
19547		/**
19548		 * Constructor for the collection.
19549		 *
19550		 * @constructor
19551		 * @method init
19552		 * @param {Array} items Optional array with items to add.
19553		 */
19554		init: function(items) {
19555			if (items) {
19556				this.add(items);
19557			}
19558		},
19559
19560		/**
19561		 * Adds new items to the control collection.
19562		 *
19563		 * @method add
19564		 * @param {Array} items Array if items to add to collection.
19565		 * @return {tinymce.ui.Collection} Current collection instance.
19566		 */
19567		add: function(items) {
19568			var self = this;
19569
19570			// Force single item into array
19571			if (!Tools.isArray(items)) {
19572				if (items instanceof Collection) {
19573					self.add(items.toArray());
19574				} else {
19575					push.call(self, items);
19576				}
19577			} else {
19578				push.apply(self, items);
19579			}
19580
19581			return self;
19582		},
19583
19584		/**
19585		 * Sets the contents of the collection. This will remove any existing items
19586		 * and replace them with the ones specified in the input array.
19587		 *
19588		 * @method set
19589		 * @param {Array} items Array with items to set into the Collection.
19590		 * @return {tinymce.ui.Collection} Collection instance.
19591		 */
19592		set: function(items) {
19593			var self = this, len = self.length, i;
19594
19595			self.length = 0;
19596			self.add(items);
19597
19598			// Remove old entries
19599			for (i = self.length; i < len; i++) {
19600				delete self[i];
19601			}
19602
19603			return self;
19604		},
19605
19606		/**
19607		 * Filters the collection item based on the specified selector expression or selector function.
19608		 *
19609		 * @method filter
19610		 * @param {String} selector Selector expression to filter items by.
19611		 * @return {tinymce.ui.Collection} Collection containing the filtered items.
19612		 */
19613		filter: function(selector) {
19614			var self = this, i, l, matches = [], item, match;
19615
19616			// Compile string into selector expression
19617			if (typeof(selector) === "string") {
19618				selector = new Selector(selector);
19619
19620				match = function(item) {
19621					return selector.match(item);
19622				};
19623			} else {
19624				// Use selector as matching function
19625				match = selector;
19626			}
19627
19628			for (i = 0, l = self.length; i < l; i++) {
19629				item = self[i];
19630
19631				if (match(item)) {
19632					matches.push(item);
19633				}
19634			}
19635
19636			return new Collection(matches);
19637		},
19638
19639		/**
19640		 * Slices the items within the collection.
19641		 *
19642		 * @method slice
19643		 * @param {Number} index Index to slice at.
19644		 * @param {Number} len Optional length to slice.
19645		 * @return {tinymce.ui.Collection} Current collection.
19646		 */
19647		slice: function() {
19648			return new Collection(slice.apply(this, arguments));
19649		},
19650
19651		/**
19652		 * Makes the current collection equal to the specified index.
19653		 *
19654		 * @method eq
19655		 * @param {Number} index Index of the item to set the collection to.
19656		 * @return {tinymce.ui.Collection} Current collection.
19657		 */
19658		eq: function(index) {
19659			return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
19660		},
19661
19662		/**
19663		 * Executes the specified callback on each item in collection.
19664		 *
19665		 * @method each
19666		 * @param {function} callback Callback to execute for each item in collection.
19667		 * @return {tinymce.ui.Collection} Current collection instance.
19668		 */
19669		each: function(callback) {
19670			Tools.each(this, callback);
19671
19672			return this;
19673		},
19674
19675		/**
19676		 * Returns an JavaScript array object of the contents inside the collection.
19677		 *
19678		 * @method toArray
19679		 * @return {Array} Array with all items from collection.
19680		 */
19681		toArray: function() {
19682			return Tools.toArray(this);
19683		},
19684
19685		/**
19686		 * Finds the index of the specified control or return -1 if it isn't in the collection.
19687		 *
19688		 * @method indexOf
19689		 * @param {Control} ctrl Control instance to look for.
19690		 * @return {Number} Index of the specified control or -1.
19691		 */
19692		indexOf: function(ctrl) {
19693			var self = this, i = self.length;
19694
19695			while (i--) {
19696				if (self[i] === ctrl) {
19697					break;
19698				}
19699			}
19700
19701			return i;
19702		},
19703
19704		/**
19705		 * Returns a new collection of the contents in reverse order.
19706		 *
19707		 * @method reverse
19708		 * @return {tinymce.ui.Collection} Collection instance with reversed items.
19709		 */
19710		reverse: function() {
19711			return new Collection(Tools.toArray(this).reverse());
19712		},
19713
19714		/**
19715		 * Returns true/false if the class exists or not.
19716		 *
19717		 * @method hasClass
19718		 * @param {String} cls Class to check for.
19719		 * @return {Boolean} true/false state if the class exists or not.
19720		 */
19721		hasClass: function(cls) {
19722			return this[0] ? this[0].hasClass(cls) : false;
19723		},
19724
19725		/**
19726		 * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
19727		 *
19728		 * @method prop
19729		 * @param {String} name Property name to get/set.
19730		 * @param {Object} value Optional object value to set.
19731		 * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
19732		 */
19733		prop: function(name, value) {
19734			var self = this, undef, item;
19735
19736			if (value !== undef) {
19737				self.each(function(item) {
19738					if (item[name]) {
19739						item[name](value);
19740					}
19741				});
19742
19743				return self;
19744			}
19745
19746			item = self[0];
19747
19748			if (item && item[name]) {
19749				return item[name]();
19750			}
19751		},
19752
19753		/**
19754		 * Executes the specific function name with optional arguments an all items in collection if it exists.
19755		 *
19756		 * @example collection.exec("myMethod", arg1, arg2, arg3);
19757		 * @method exec
19758		 * @param {String} name Name of the function to execute.
19759		 * @param {Object} ... Multiple arguments to pass to each function.
19760		 * @return {tinymce.ui.Collection} Current collection.
19761		 */
19762		exec: function(name) {
19763			var self = this, args = Tools.toArray(arguments).slice(1);
19764
19765			self.each(function(item) {
19766				if (item[name]) {
19767					item[name].apply(item, args);
19768				}
19769			});
19770
19771			return self;
19772		},
19773
19774		/**
19775		 * Remove all items from collection and DOM.
19776		 *
19777		 * @method remove
19778		 * @return {tinymce.ui.Collection} Current collection.
19779		 */
19780		remove: function() {
19781			var i = this.length;
19782
19783			while (i--) {
19784				this[i].remove();
19785			}
19786
19787			return this;
19788		}
19789
19790		/**
19791		 * Fires the specified event by name and arguments on the control. This will execute all
19792		 * bound event handlers.
19793		 *
19794		 * @method fire
19795		 * @param {String} name Name of the event to fire.
19796		 * @param {Object} args Optional arguments to pass to the event.
19797		 * @return {tinymce.ui.Collection} Current collection instance.
19798		 */
19799		// fire: function(event, args) {}, -- Generated by code below
19800
19801		/**
19802		 * Binds a callback to the specified event. This event can both be
19803		 * native browser events like "click" or custom ones like PostRender.
19804		 *
19805		 * The callback function will have two parameters the first one being the control that received the event
19806		 * the second one will be the event object either the browsers native event object or a custom JS object.
19807		 *
19808		 * @method on
19809		 * @param {String} name Name of the event to bind. For example "click".
19810		 * @param {String/function} callback Callback function to execute ones the event occurs.
19811		 * @return {tinymce.ui.Collection} Current collection instance.
19812		 */
19813		// on: function(name, callback) {}, -- Generated by code below
19814
19815		/**
19816		 * Unbinds the specified event and optionally a specific callback. If you omit the name
19817		 * parameter all event handlers will be removed. If you omit the callback all event handles
19818		 * by the specified name will be removed.
19819		 *
19820		 * @method off
19821		 * @param {String} name Optional name for the event to unbind.
19822		 * @param {function} callback Optional callback function to unbind.
19823		 * @return {tinymce.ui.Collection} Current collection instance.
19824		 */
19825		// off: function(name, callback) {}, -- Generated by code below
19826
19827		/**
19828		 * Shows the items in the current collection.
19829		 *
19830		 * @method show
19831		 * @return {tinymce.ui.Collection} Current collection instance.
19832		 */
19833		// show: function() {}, -- Generated by code below
19834
19835		/**
19836		 * Hides the items in the current collection.
19837		 *
19838		 * @method hide
19839		 * @return {tinymce.ui.Collection} Current collection instance.
19840		 */
19841		// hide: function() {}, -- Generated by code below
19842
19843		/**
19844		 * Sets/gets the text contents of the items in the current collection.
19845		 *
19846		 * @method text
19847		 * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
19848		 */
19849		// text: function(value) {}, -- Generated by code below
19850
19851		/**
19852		 * Sets/gets the name contents of the items in the current collection.
19853		 *
19854		 * @method name
19855		 * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
19856		 */
19857		// name: function(value) {}, -- Generated by code below
19858
19859		/**
19860		 * Sets/gets the disabled state on the items in the current collection.
19861		 *
19862		 * @method disabled
19863		 * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
19864		 */
19865		// disabled: function(state) {}, -- Generated by code below
19866
19867		/**
19868		 * Sets/gets the active state on the items in the current collection.
19869		 *
19870		 * @method active
19871		 * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
19872		 */
19873		// active: function(state) {}, -- Generated by code below
19874
19875		/**
19876		 * Sets/gets the selected state on the items in the current collection.
19877		 *
19878		 * @method selected
19879		 * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
19880		 */
19881		// selected: function(state) {}, -- Generated by code below
19882
19883		/**
19884		 * Sets/gets the selected state on the items in the current collection.
19885		 *
19886		 * @method visible
19887		 * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
19888		 */
19889		// visible: function(state) {}, -- Generated by code below
19890
19891		/**
19892		 * Adds a class to all items in the collection.
19893		 *
19894		 * @method addClass
19895		 * @param {String} cls Class to add to each item.
19896		 * @return {tinymce.ui.Collection} Current collection instance.
19897		 */
19898		// addClass: function(cls) {}, -- Generated by code below
19899
19900		/**
19901		 * Removes the specified class from all items in collection.
19902		 *
19903		 * @method removeClass
19904		 * @param {String} cls Class to remove from each item.
19905		 * @return {tinymce.ui.Collection} Current collection instance.
19906		 */
19907		// removeClass: function(cls) {}, -- Generated by code below
19908	};
19909
19910	// Extend tinymce.ui.Collection prototype with some generated control specific methods
19911	Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) {
19912		proto[name] = function() {
19913			var args = Tools.toArray(arguments);
19914
19915			this.each(function(ctrl) {
19916				if (name in ctrl) {
19917					ctrl[name].apply(ctrl, args);
19918				}
19919			});
19920
19921			return this;
19922		};
19923	});
19924
19925	// Extend tinymce.ui.Collection prototype with some property methods
19926	Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
19927		proto[name] = function(value) {
19928			return this.prop(name, value);
19929		};
19930	});
19931
19932	// Create class based on the new prototype
19933	Collection = Class.extend(proto);
19934
19935	// Stick Collection into Selector to prevent circual references
19936	Selector.Collection = Collection;
19937
19938	return Collection;
19939});
19940
19941// Included from: js/tinymce/classes/ui/DomUtils.js
19942
19943/**
19944 * DOMUtils.js
19945 *
19946 * Copyright, Moxiecode Systems AB
19947 * Released under LGPL License.
19948 *
19949 * License: http://www.tinymce.com/license
19950 * Contributing: http://www.tinymce.com/contributing
19951 */
19952
19953define("tinymce/ui/DomUtils", [
19954	"tinymce/util/Tools",
19955	"tinymce/dom/DOMUtils"
19956], function(Tools, DOMUtils) {
19957	"use strict";
19958
19959	var count = 0;
19960
19961	return {
19962		id: function() {
19963			return 'mceu_' + (count++);
19964		},
19965
19966		createFragment: function(html) {
19967			return DOMUtils.DOM.createFragment(html);
19968		},
19969
19970		getWindowSize: function() {
19971			return DOMUtils.DOM.getViewPort();
19972		},
19973
19974		getSize: function(elm) {
19975			var width, height;
19976
19977			if (elm.getBoundingClientRect) {
19978				var rect = elm.getBoundingClientRect();
19979
19980				width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
19981				height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
19982			} else {
19983				width = elm.offsetWidth;
19984				height = elm.offsetHeight;
19985			}
19986
19987			return {width: width, height: height};
19988		},
19989
19990		getPos: function(elm, root) {
19991			return DOMUtils.DOM.getPos(elm, root);
19992		},
19993
19994		getViewPort: function(win) {
19995			return DOMUtils.DOM.getViewPort(win);
19996		},
19997
19998		get: function(id) {
19999			return document.getElementById(id);
20000		},
20001
20002		addClass : function(elm, cls) {
20003			return DOMUtils.DOM.addClass(elm, cls);
20004		},
20005
20006		removeClass : function(elm, cls) {
20007			return DOMUtils.DOM.removeClass(elm, cls);
20008		},
20009
20010		hasClass : function(elm, cls) {
20011			return DOMUtils.DOM.hasClass(elm, cls);
20012		},
20013
20014		toggleClass: function(elm, cls, state) {
20015			return DOMUtils.DOM.toggleClass(elm, cls, state);
20016		},
20017
20018		css: function(elm, name, value) {
20019			return DOMUtils.DOM.setStyle(elm, name, value);
20020		},
20021
20022		getRuntimeStyle: function(elm, name) {
20023			return DOMUtils.DOM.getStyle(elm, name, true);
20024		},
20025
20026		on: function(target, name, callback, scope) {
20027			return DOMUtils.DOM.bind(target, name, callback, scope);
20028		},
20029
20030		off: function(target, name, callback) {
20031			return DOMUtils.DOM.unbind(target, name, callback);
20032		},
20033
20034		fire: function(target, name, args) {
20035			return DOMUtils.DOM.fire(target, name, args);
20036		},
20037
20038		innerHtml: function(elm, html) {
20039			// Workaround for <div> in <p> bug on IE 8 #6178
20040			DOMUtils.DOM.setHTML(elm, html);
20041		}
20042	};
20043});
20044
20045// Included from: js/tinymce/classes/ui/Control.js
20046
20047/**
20048 * Control.js
20049 *
20050 * Copyright, Moxiecode Systems AB
20051 * Released under LGPL License.
20052 *
20053 * License: http://www.tinymce.com/license
20054 * Contributing: http://www.tinymce.com/contributing
20055 */
20056
20057/*eslint consistent-this:0 */
20058
20059/**
20060 * This is the base class for all controls and containers. All UI control instances inherit
20061 * from this one as it has the base logic needed by all of them.
20062 *
20063 * @class tinymce.ui.Control
20064 */
20065define("tinymce/ui/Control", [
20066	"tinymce/util/Class",
20067	"tinymce/util/Tools",
20068	"tinymce/util/EventDispatcher",
20069	"tinymce/ui/Collection",
20070	"tinymce/ui/DomUtils"
20071], function(Class, Tools, EventDispatcher, Collection, DomUtils) {
20072	"use strict";
20073
20074	var hasMouseWheelEventSupport = "onmousewheel" in document;
20075	var hasWheelEventSupport = false;
20076	var classPrefix = "mce-";
20077
20078	function getEventDispatcher(obj) {
20079		if (!obj._eventDispatcher) {
20080			obj._eventDispatcher = new EventDispatcher({
20081				scope: obj,
20082				toggleEvent: function(name, state) {
20083					if (state && EventDispatcher.isNative(name)) {
20084						if (!obj._nativeEvents) {
20085							obj._nativeEvents = {};
20086						}
20087
20088						obj._nativeEvents[name] = true;
20089
20090						if (obj._rendered) {
20091							obj.bindPendingEvents();
20092						}
20093					}
20094				}
20095			});
20096		}
20097
20098		return obj._eventDispatcher;
20099	}
20100
20101	var Control = Class.extend({
20102		Statics: {
20103			classPrefix: classPrefix
20104		},
20105
20106		isRtl: function() {
20107			return Control.rtl;
20108		},
20109
20110		/**
20111		 * Class/id prefix to use for all controls.
20112		 *
20113		 * @final
20114		 * @field {String} classPrefix
20115		 */
20116		classPrefix: classPrefix,
20117
20118		/**
20119		 * Constructs a new control instance with the specified settings.
20120		 *
20121		 * @constructor
20122		 * @param {Object} settings Name/value object with settings.
20123		 * @setting {String} style Style CSS properties to add.
20124		 * @setting {String} border Border box values example: 1 1 1 1
20125		 * @setting {String} padding Padding box values example: 1 1 1 1
20126		 * @setting {String} margin Margin box values example: 1 1 1 1
20127		 * @setting {Number} minWidth Minimal width for the control.
20128		 * @setting {Number} minHeight Minimal height for the control.
20129		 * @setting {String} classes Space separated list of classes to add.
20130		 * @setting {String} role WAI-ARIA role to use for control.
20131		 * @setting {Boolean} hidden Is the control hidden by default.
20132		 * @setting {Boolean} disabled Is the control disabled by default.
20133		 * @setting {String} name Name of the control instance.
20134		 */
20135		init: function(settings) {
20136			var self = this, classes, i;
20137
20138			self.settings = settings = Tools.extend({}, self.Defaults, settings);
20139
20140			// Initial states
20141			self._id = settings.id || DomUtils.id();
20142			self._text = self._name = '';
20143			self._width = self._height = 0;
20144			self._aria = {role: settings.role};
20145			this._elmCache = {};
20146
20147			// Setup classes
20148			classes = settings.classes;
20149			if (classes) {
20150				classes = classes.split(' ');
20151				classes.map = {};
20152				i = classes.length;
20153				while (i--) {
20154					classes.map[classes[i]] = true;
20155				}
20156			}
20157
20158			self._classes = classes || [];
20159			self.visible(true);
20160
20161			// Set some properties
20162			Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
20163				var value = settings[name], undef;
20164
20165				if (value !== undef) {
20166					self[name](value);
20167				} else if (self['_' + name] === undef) {
20168					self['_' + name] = false;
20169				}
20170			});
20171
20172			self.on('click', function() {
20173				if (self.disabled()) {
20174					return false;
20175				}
20176			});
20177
20178			// TODO: Is this needed duplicate code see above?
20179			if (settings.classes) {
20180				Tools.each(settings.classes.split(' '), function(cls) {
20181					self.addClass(cls);
20182				});
20183			}
20184
20185			/**
20186			 * Name/value object with settings for the current control.
20187			 *
20188			 * @field {Object} settings
20189			 */
20190			self.settings = settings;
20191
20192			self._borderBox = self.parseBox(settings.border);
20193			self._paddingBox = self.parseBox(settings.padding);
20194			self._marginBox = self.parseBox(settings.margin);
20195
20196			if (settings.hidden) {
20197				self.hide();
20198			}
20199		},
20200
20201		// Will generate getter/setter methods for these properties
20202		Properties: 'parent,title,text,width,height,disabled,active,name,value',
20203
20204		// Will generate empty dummy functions for these
20205		Methods: 'renderHtml',
20206
20207		/**
20208		 * Returns the root element to render controls into.
20209		 *
20210		 * @method getContainerElm
20211		 * @return {Element} HTML DOM element to render into.
20212		 */
20213		getContainerElm: function() {
20214			return document.body;
20215		},
20216
20217		/**
20218		 * Returns a control instance for the current DOM element.
20219		 *
20220		 * @method getParentCtrl
20221		 * @param {Element} elm HTML dom element to get parent control from.
20222		 * @return {tinymce.ui.Control} Control instance or undefined.
20223		 */
20224		getParentCtrl: function(elm) {
20225			var ctrl, lookup = this.getRoot().controlIdLookup;
20226
20227			while (elm && lookup) {
20228				ctrl = lookup[elm.id];
20229				if (ctrl) {
20230					break;
20231				}
20232
20233				elm = elm.parentNode;
20234			}
20235
20236			return ctrl;
20237		},
20238
20239		/**
20240		 * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
20241		 *
20242		 * @method parseBox
20243		 * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
20244		 * @return {Object} Object with top/right/bottom/left properties.
20245		 * @private
20246		 */
20247		parseBox: function(value) {
20248			var len, radix = 10;
20249
20250			if (!value) {
20251				return;
20252			}
20253
20254			if (typeof(value) === "number") {
20255				value = value || 0;
20256
20257				return {
20258					top: value,
20259					left: value,
20260					bottom: value,
20261					right: value
20262				};
20263			}
20264
20265			value = value.split(' ');
20266			len = value.length;
20267
20268			if (len === 1) {
20269				value[1] = value[2] = value[3] = value[0];
20270			} else if (len === 2) {
20271				value[2] = value[0];
20272				value[3] = value[1];
20273			} else if (len === 3) {
20274				value[3] = value[1];
20275			}
20276
20277			return {
20278				top: parseInt(value[0], radix) || 0,
20279				right: parseInt(value[1], radix) || 0,
20280				bottom: parseInt(value[2], radix) || 0,
20281				left: parseInt(value[3], radix) || 0
20282			};
20283		},
20284
20285		borderBox: function() {
20286			return this._borderBox;
20287		},
20288
20289		paddingBox: function() {
20290			return this._paddingBox;
20291		},
20292
20293		marginBox: function() {
20294			return this._marginBox;
20295		},
20296
20297		measureBox: function(elm, prefix) {
20298			function getStyle(name) {
20299				var defaultView = document.defaultView;
20300
20301				if (defaultView) {
20302					// Remove camelcase
20303					name = name.replace(/[A-Z]/g, function(a) {
20304						return '-' + a;
20305					});
20306
20307					return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
20308				}
20309
20310				return elm.currentStyle[name];
20311			}
20312
20313			function getSide(name) {
20314				var val = parseFloat(getStyle(name), 10);
20315
20316				return isNaN(val) ? 0 : val;
20317			}
20318
20319			return {
20320				top: getSide(prefix + "TopWidth"),
20321				right: getSide(prefix + "RightWidth"),
20322				bottom: getSide(prefix + "BottomWidth"),
20323				left: getSide(prefix + "LeftWidth")
20324			};
20325		},
20326
20327		/**
20328		 * Initializes the current controls layout rect.
20329		 * This will be executed by the layout managers to determine the
20330		 * default minWidth/minHeight etc.
20331		 *
20332		 * @method initLayoutRect
20333		 * @return {Object} Layout rect instance.
20334		 */
20335		initLayoutRect: function() {
20336			var self = this, settings = self.settings, borderBox, layoutRect;
20337			var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
20338			var startMinWidth, startMinHeight, initialSize;
20339
20340			// Measure the current element
20341			borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
20342			self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
20343			self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
20344			initialSize = DomUtils.getSize(elm);
20345
20346			// Setup minWidth/minHeight and width/height
20347			startMinWidth = settings.minWidth;
20348			startMinHeight = settings.minHeight;
20349			minWidth = startMinWidth || initialSize.width;
20350			minHeight = startMinHeight || initialSize.height;
20351			width = settings.width;
20352			height = settings.height;
20353			autoResize = settings.autoResize;
20354			autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
20355
20356			width = width || minWidth;
20357			height = height || minHeight;
20358
20359			var deltaW = borderBox.left + borderBox.right;
20360			var deltaH = borderBox.top + borderBox.bottom;
20361
20362			var maxW = settings.maxWidth || 0xFFFF;
20363			var maxH = settings.maxHeight || 0xFFFF;
20364
20365			// Setup initial layout rect
20366			self._layoutRect = layoutRect = {
20367				x: settings.x || 0,
20368				y: settings.y || 0,
20369				w: width,
20370				h: height,
20371				deltaW: deltaW,
20372				deltaH: deltaH,
20373				contentW: width - deltaW,
20374				contentH: height - deltaH,
20375				innerW: width - deltaW,
20376				innerH: height - deltaH,
20377				startMinWidth: startMinWidth || 0,
20378				startMinHeight: startMinHeight || 0,
20379				minW: Math.min(minWidth, maxW),
20380				minH: Math.min(minHeight, maxH),
20381				maxW: maxW,
20382				maxH: maxH,
20383				autoResize: autoResize,
20384				scrollW: 0
20385			};
20386
20387			self._lastLayoutRect = {};
20388
20389			return layoutRect;
20390		},
20391
20392		/**
20393		 * Getter/setter for the current layout rect.
20394		 *
20395		 * @method layoutRect
20396		 * @param {Object} [newRect] Optional new layout rect.
20397		 * @return {tinymce.ui.Control/Object} Current control or rect object.
20398		 */
20399		layoutRect: function(newRect) {
20400			var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
20401
20402			// Initialize default layout rect
20403			if (!curRect) {
20404				curRect = self.initLayoutRect();
20405			}
20406
20407			// Set new rect values
20408			if (newRect) {
20409				// Calc deltas between inner and outer sizes
20410				deltaWidth = curRect.deltaW;
20411				deltaHeight = curRect.deltaH;
20412
20413				// Set x position
20414				if (newRect.x !== undef) {
20415					curRect.x = newRect.x;
20416				}
20417
20418				// Set y position
20419				if (newRect.y !== undef) {
20420					curRect.y = newRect.y;
20421				}
20422
20423				// Set minW
20424				if (newRect.minW !== undef) {
20425					curRect.minW = newRect.minW;
20426				}
20427
20428				// Set minH
20429				if (newRect.minH !== undef) {
20430					curRect.minH = newRect.minH;
20431				}
20432
20433				// Set new width and calculate inner width
20434				size = newRect.w;
20435				if (size !== undef) {
20436					size = size < curRect.minW ? curRect.minW : size;
20437					size = size > curRect.maxW ? curRect.maxW : size;
20438					curRect.w = size;
20439					curRect.innerW = size - deltaWidth;
20440				}
20441
20442				// Set new height and calculate inner height
20443				size = newRect.h;
20444				if (size !== undef) {
20445					size = size < curRect.minH ? curRect.minH : size;
20446					size = size > curRect.maxH ? curRect.maxH : size;
20447					curRect.h = size;
20448					curRect.innerH = size - deltaHeight;
20449				}
20450
20451				// Set new inner width and calculate width
20452				size = newRect.innerW;
20453				if (size !== undef) {
20454					size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
20455					size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
20456					curRect.innerW = size;
20457					curRect.w = size + deltaWidth;
20458				}
20459
20460				// Set new height and calculate inner height
20461				size = newRect.innerH;
20462				if (size !== undef) {
20463					size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
20464					size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
20465					curRect.innerH = size;
20466					curRect.h = size + deltaHeight;
20467				}
20468
20469				// Set new contentW
20470				if (newRect.contentW !== undef) {
20471					curRect.contentW = newRect.contentW;
20472				}
20473
20474				// Set new contentH
20475				if (newRect.contentH !== undef) {
20476					curRect.contentH = newRect.contentH;
20477				}
20478
20479				// Compare last layout rect with the current one to see if we need to repaint or not
20480				lastLayoutRect = self._lastLayoutRect;
20481				if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
20482					lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
20483					repaintControls = Control.repaintControls;
20484
20485					if (repaintControls) {
20486						if (repaintControls.map && !repaintControls.map[self._id]) {
20487							repaintControls.push(self);
20488							repaintControls.map[self._id] = true;
20489						}
20490					}
20491
20492					lastLayoutRect.x = curRect.x;
20493					lastLayoutRect.y = curRect.y;
20494					lastLayoutRect.w = curRect.w;
20495					lastLayoutRect.h = curRect.h;
20496				}
20497
20498				return self;
20499			}
20500
20501			return curRect;
20502		},
20503
20504		/**
20505		 * Repaints the control after a layout operation.
20506		 *
20507		 * @method repaint
20508		 */
20509		repaint: function() {
20510			var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
20511
20512			// Use Math.round on all values on IE < 9
20513			round = !document.createRange ? Math.round : function(value) {
20514				return value;
20515			};
20516
20517			style = self.getEl().style;
20518			rect = self._layoutRect;
20519			lastRepaintRect = self._lastRepaintRect || {};
20520
20521			borderBox = self._borderBox;
20522			borderW = borderBox.left + borderBox.right;
20523			borderH = borderBox.top + borderBox.bottom;
20524
20525			if (rect.x !== lastRepaintRect.x) {
20526				style.left = round(rect.x) + 'px';
20527				lastRepaintRect.x = rect.x;
20528			}
20529
20530			if (rect.y !== lastRepaintRect.y) {
20531				style.top = round(rect.y) + 'px';
20532				lastRepaintRect.y = rect.y;
20533			}
20534
20535			if (rect.w !== lastRepaintRect.w) {
20536				style.width = round(rect.w - borderW) + 'px';
20537				lastRepaintRect.w = rect.w;
20538			}
20539
20540			if (rect.h !== lastRepaintRect.h) {
20541				style.height = round(rect.h - borderH) + 'px';
20542				lastRepaintRect.h = rect.h;
20543			}
20544
20545			// Update body if needed
20546			if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
20547				bodyStyle = self.getEl('body').style;
20548				bodyStyle.width = round(rect.innerW) + 'px';
20549				lastRepaintRect.innerW = rect.innerW;
20550			}
20551
20552			if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
20553				bodyStyle = bodyStyle || self.getEl('body').style;
20554				bodyStyle.height = round(rect.innerH) + 'px';
20555				lastRepaintRect.innerH = rect.innerH;
20556			}
20557
20558			self._lastRepaintRect = lastRepaintRect;
20559			self.fire('repaint', {}, false);
20560		},
20561
20562		/**
20563		 * Binds a callback to the specified event. This event can both be
20564		 * native browser events like "click" or custom ones like PostRender.
20565		 *
20566		 * The callback function will be passed a DOM event like object that enables yout do stop propagation.
20567		 *
20568		 * @method on
20569		 * @param {String} name Name of the event to bind. For example "click".
20570		 * @param {String/function} callback Callback function to execute ones the event occurs.
20571		 * @return {tinymce.ui.Control} Current control object.
20572		 */
20573		on: function(name, callback) {
20574			var self = this;
20575
20576			function resolveCallbackName(name) {
20577				var callback, scope;
20578
20579				if (typeof(name) != 'string') {
20580					return name;
20581				}
20582
20583				return function(e) {
20584					if (!callback) {
20585						self.parentsAndSelf().each(function(ctrl) {
20586							var callbacks = ctrl.settings.callbacks;
20587
20588							if (callbacks && (callback = callbacks[name])) {
20589								scope = ctrl;
20590								return false;
20591							}
20592						});
20593					}
20594
20595					return callback.call(scope, e);
20596				};
20597			}
20598
20599			getEventDispatcher(self).on(name, resolveCallbackName(callback));
20600
20601			return self;
20602		},
20603
20604		/**
20605		 * Unbinds the specified event and optionally a specific callback. If you omit the name
20606		 * parameter all event handlers will be removed. If you omit the callback all event handles
20607		 * by the specified name will be removed.
20608		 *
20609		 * @method off
20610		 * @param {String} [name] Name for the event to unbind.
20611		 * @param {function} [callback] Callback function to unbind.
20612		 * @return {mxex.ui.Control} Current control object.
20613		 */
20614		off: function(name, callback) {
20615			getEventDispatcher(this).off(name, callback);
20616			return this;
20617		},
20618
20619		/**
20620		 * Fires the specified event by name and arguments on the control. This will execute all
20621		 * bound event handlers.
20622		 *
20623		 * @method fire
20624		 * @param {String} name Name of the event to fire.
20625		 * @param {Object} [args] Arguments to pass to the event.
20626		 * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
20627		 * @return {Object} Current arguments object.
20628		 */
20629		fire: function(name, args, bubble) {
20630			var self = this;
20631
20632			args = args || {};
20633
20634			if (!args.control) {
20635				args.control = self;
20636			}
20637
20638			args = getEventDispatcher(self).fire(name, args);
20639
20640			// Bubble event up to parents
20641			if (bubble !== false && self.parent) {
20642				var parent = self.parent();
20643				while (parent && !args.isPropagationStopped()) {
20644					parent.fire(name, args, false);
20645					parent = parent.parent();
20646				}
20647			}
20648
20649			return args;
20650		},
20651
20652		/**
20653		 * Returns true/false if the specified event has any listeners.
20654		 *
20655		 * @method hasEventListeners
20656		 * @param {String} name Name of the event to check for.
20657		 * @return {Boolean} True/false state if the event has listeners.
20658		 */
20659		hasEventListeners: function(name) {
20660			return getEventDispatcher(this).has(name);
20661		},
20662
20663		/**
20664		 * Returns a control collection with all parent controls.
20665		 *
20666		 * @method parents
20667		 * @param {String} selector Optional selector expression to find parents.
20668		 * @return {tinymce.ui.Collection} Collection with all parent controls.
20669		 */
20670		parents: function(selector) {
20671			var self = this, ctrl, parents = new Collection();
20672
20673			// Add each parent to collection
20674			for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
20675				parents.add(ctrl);
20676			}
20677
20678			// Filter away everything that doesn't match the selector
20679			if (selector) {
20680				parents = parents.filter(selector);
20681			}
20682
20683			return parents;
20684		},
20685
20686		/**
20687		 * Returns the current control and it's parents.
20688		 *
20689		 * @method parentsAndSelf
20690		 * @param {String} selector Optional selector expression to find parents.
20691		 * @return {tinymce.ui.Collection} Collection with all parent controls.
20692		 */
20693		parentsAndSelf: function(selector) {
20694			return new Collection(this).add(this.parents(selector));
20695		},
20696
20697		/**
20698		 * Returns the control next to the current control.
20699		 *
20700		 * @method next
20701		 * @return {tinymce.ui.Control} Next control instance.
20702		 */
20703		next: function() {
20704			var parentControls = this.parent().items();
20705
20706			return parentControls[parentControls.indexOf(this) + 1];
20707		},
20708
20709		/**
20710		 * Returns the control previous to the current control.
20711		 *
20712		 * @method prev
20713		 * @return {tinymce.ui.Control} Previous control instance.
20714		 */
20715		prev: function() {
20716			var parentControls = this.parent().items();
20717
20718			return parentControls[parentControls.indexOf(this) - 1];
20719		},
20720
20721		/**
20722		 * Find the common ancestor for two control instances.
20723		 *
20724		 * @method findCommonAncestor
20725		 * @param {tinymce.ui.Control} ctrl1 First control.
20726		 * @param {tinymce.ui.Control} ctrl2 Second control.
20727		 * @return {tinymce.ui.Control} Ancestor control instance.
20728		 */
20729		findCommonAncestor: function(ctrl1, ctrl2) {
20730			var parentCtrl;
20731
20732			while (ctrl1) {
20733				parentCtrl = ctrl2;
20734
20735				while (parentCtrl && ctrl1 != parentCtrl) {
20736					parentCtrl = parentCtrl.parent();
20737				}
20738
20739				if (ctrl1 == parentCtrl) {
20740					break;
20741				}
20742
20743				ctrl1 = ctrl1.parent();
20744			}
20745
20746			return ctrl1;
20747		},
20748
20749		/**
20750		 * Returns true/false if the specific control has the specific class.
20751		 *
20752		 * @method hasClass
20753		 * @param {String} cls Class to check for.
20754		 * @param {String} [group] Sub element group name.
20755		 * @return {Boolean} True/false if the control has the specified class.
20756		 */
20757		hasClass: function(cls, group) {
20758			var classes = this._classes[group || 'control'];
20759
20760			cls = this.classPrefix + cls;
20761
20762			return classes && !!classes.map[cls];
20763		},
20764
20765		/**
20766		 * Adds the specified class to the control
20767		 *
20768		 * @method addClass
20769		 * @param {String} cls Class to check for.
20770		 * @param {String} [group] Sub element group name.
20771		 * @return {tinymce.ui.Control} Current control object.
20772		 */
20773		addClass: function(cls, group) {
20774			var self = this, classes, elm;
20775
20776			cls = this.classPrefix + cls;
20777			classes = self._classes[group || 'control'];
20778
20779			if (!classes) {
20780				classes = [];
20781				classes.map = {};
20782				self._classes[group || 'control'] = classes;
20783			}
20784
20785			if (!classes.map[cls]) {
20786				classes.map[cls] = cls;
20787				classes.push(cls);
20788
20789				if (self._rendered) {
20790					elm = self.getEl(group);
20791
20792					if (elm) {
20793						elm.className = classes.join(' ');
20794					}
20795				}
20796			}
20797
20798			return self;
20799		},
20800
20801		/**
20802		 * Removes the specified class from the control.
20803		 *
20804		 * @method removeClass
20805		 * @param {String} cls Class to remove.
20806		 * @param {String} [group] Sub element group name.
20807		 * @return {tinymce.ui.Control} Current control object.
20808		 */
20809		removeClass: function(cls, group) {
20810			var self = this, classes, i, elm;
20811
20812			cls = this.classPrefix + cls;
20813			classes = self._classes[group || 'control'];
20814			if (classes && classes.map[cls]) {
20815				delete classes.map[cls];
20816
20817				i = classes.length;
20818				while (i--) {
20819					if (classes[i] === cls) {
20820						classes.splice(i, 1);
20821					}
20822				}
20823			}
20824
20825			if (self._rendered) {
20826				elm = self.getEl(group);
20827
20828				if (elm) {
20829					elm.className = classes.join(' ');
20830				}
20831			}
20832
20833			return self;
20834		},
20835
20836		/**
20837		 * Toggles the specified class on the control.
20838		 *
20839		 * @method toggleClass
20840		 * @param {String} cls Class to remove.
20841		 * @param {Boolean} state True/false state to add/remove class.
20842		 * @param {String} [group] Sub element group name.
20843		 * @return {tinymce.ui.Control} Current control object.
20844		 */
20845		toggleClass: function(cls, state, group) {
20846			var self = this;
20847
20848			if (state) {
20849				self.addClass(cls, group);
20850			} else {
20851				self.removeClass(cls, group);
20852			}
20853
20854			return self;
20855		},
20856
20857		/**
20858		 * Returns the class string for the specified group name.
20859		 *
20860		 * @method classes
20861		 * @param {String} [group] Group to get clases by.
20862		 * @return {String} Classes for the specified group.
20863		 */
20864		classes: function(group) {
20865			var classes = this._classes[group || 'control'];
20866
20867			return classes ? classes.join(' ') : '';
20868		},
20869
20870		/**
20871		 * Sets the inner HTML of the control element.
20872		 *
20873		 * @method innerHtml
20874		 * @param {String} html Html string to set as inner html.
20875		 * @return {tinymce.ui.Control} Current control object.
20876		 */
20877		innerHtml: function(html) {
20878			DomUtils.innerHtml(this.getEl(), html);
20879			return this;
20880		},
20881
20882		/**
20883		 * Returns the control DOM element or sub element.
20884		 *
20885		 * @method getEl
20886		 * @param {String} [suffix] Suffix to get element by.
20887		 * @return {Element} HTML DOM element for the current control or it's children.
20888		 */
20889		getEl: function(suffix) {
20890			var id = suffix ? this._id + '-' + suffix : this._id;
20891
20892			if (!this._elmCache[id]) {
20893				this._elmCache[id] = DomUtils.get(id);
20894			}
20895
20896			return this._elmCache[id];
20897		},
20898
20899		/**
20900		 * Sets/gets the visible for the control.
20901		 *
20902		 * @method visible
20903		 * @param {Boolean} state Value to set to control.
20904		 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
20905		 */
20906		visible: function(state) {
20907			var self = this, parentCtrl;
20908
20909			if (typeof(state) !== "undefined") {
20910				if (self._visible !== state) {
20911					if (self._rendered) {
20912						self.getEl().style.display = state ? '' : 'none';
20913					}
20914
20915					self._visible = state;
20916
20917					// Parent container needs to reflow
20918					parentCtrl = self.parent();
20919					if (parentCtrl) {
20920						parentCtrl._lastRect = null;
20921					}
20922
20923					self.fire(state ? 'show' : 'hide');
20924				}
20925
20926				return self;
20927			}
20928
20929			return self._visible;
20930		},
20931
20932		/**
20933		 * Sets the visible state to true.
20934		 *
20935		 * @method show
20936		 * @return {tinymce.ui.Control} Current control instance.
20937		 */
20938		show: function() {
20939			return this.visible(true);
20940		},
20941
20942		/**
20943		 * Sets the visible state to false.
20944		 *
20945		 * @method hide
20946		 * @return {tinymce.ui.Control} Current control instance.
20947		 */
20948		hide: function() {
20949			return this.visible(false);
20950		},
20951
20952		/**
20953		 * Focuses the current control.
20954		 *
20955		 * @method focus
20956		 * @return {tinymce.ui.Control} Current control instance.
20957		 */
20958		focus: function() {
20959			try {
20960				this.getEl().focus();
20961			} catch (ex) {
20962				// Ignore IE error
20963			}
20964
20965			return this;
20966		},
20967
20968		/**
20969		 * Blurs the current control.
20970		 *
20971		 * @method blur
20972		 * @return {tinymce.ui.Control} Current control instance.
20973		 */
20974		blur: function() {
20975			this.getEl().blur();
20976
20977			return this;
20978		},
20979
20980		/**
20981		 * Sets the specified aria property.
20982		 *
20983		 * @method aria
20984		 * @param {String} name Name of the aria property to set.
20985		 * @param {String} value Value of the aria property.
20986		 * @return {tinymce.ui.Control} Current control instance.
20987		 */
20988		aria: function(name, value) {
20989			var self = this, elm = self.getEl(self.ariaTarget);
20990
20991			if (typeof(value) === "undefined") {
20992				return self._aria[name];
20993			} else {
20994				self._aria[name] = value;
20995			}
20996
20997			if (self._rendered) {
20998				elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
20999			}
21000
21001			return self;
21002		},
21003
21004		/**
21005		 * Encodes the specified string with HTML entities. It will also
21006		 * translate the string to different languages.
21007		 *
21008		 * @method encode
21009		 * @param {String/Object/Array} text Text to entity encode.
21010		 * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
21011		 * @return {String} Encoded and possible traslated string.
21012		 */
21013		encode: function(text, translate) {
21014			if (translate !== false) {
21015				text = this.translate(text);
21016			}
21017
21018			return (text || '').replace(/[&<>"]/g, function(match) {
21019				return '&#' + match.charCodeAt(0) + ';';
21020			});
21021		},
21022
21023		/**
21024		 * Returns the translated string.
21025		 *
21026		 * @method translate
21027		 * @param {String} text Text to translate.
21028		 * @return {String} Translated string or the same as the input.
21029		 */
21030		translate: function(text) {
21031			return Control.translate ? Control.translate(text) : text;
21032		},
21033
21034		/**
21035		 * Adds items before the current control.
21036		 *
21037		 * @method before
21038		 * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
21039		 * @return {tinymce.ui.Control} Current control instance.
21040		 */
21041		before: function(items) {
21042			var self = this, parent = self.parent();
21043
21044			if (parent) {
21045				parent.insert(items, parent.items().indexOf(self), true);
21046			}
21047
21048			return self;
21049		},
21050
21051		/**
21052		 * Adds items after the current control.
21053		 *
21054		 * @method after
21055		 * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
21056		 * @return {tinymce.ui.Control} Current control instance.
21057		 */
21058		after: function(items) {
21059			var self = this, parent = self.parent();
21060
21061			if (parent) {
21062				parent.insert(items, parent.items().indexOf(self));
21063			}
21064
21065			return self;
21066		},
21067
21068		/**
21069		 * Removes the current control from DOM and from UI collections.
21070		 *
21071		 * @method remove
21072		 * @return {tinymce.ui.Control} Current control instance.
21073		 */
21074		remove: function() {
21075			var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
21076
21077			if (self.items) {
21078				var controls = self.items().toArray();
21079				i = controls.length;
21080				while (i--) {
21081					controls[i].remove();
21082				}
21083			}
21084
21085			if (parent && parent.items) {
21086				newItems = [];
21087
21088				parent.items().each(function(item) {
21089					if (item !== self) {
21090						newItems.push(item);
21091					}
21092				});
21093
21094				parent.items().set(newItems);
21095				parent._lastRect = null;
21096			}
21097
21098			if (self._eventsRoot && self._eventsRoot == self) {
21099				DomUtils.off(elm);
21100			}
21101
21102			var lookup = self.getRoot().controlIdLookup;
21103			if (lookup) {
21104				delete lookup[self._id];
21105			}
21106
21107			if (elm && elm.parentNode) {
21108				elm.parentNode.removeChild(elm);
21109			}
21110
21111			self._rendered = false;
21112
21113			return self;
21114		},
21115
21116		/**
21117		 * Renders the control before the specified element.
21118		 *
21119		 * @method renderBefore
21120		 * @param {Element} elm Element to render before.
21121		 * @return {tinymce.ui.Control} Current control instance.
21122		 */
21123		renderBefore: function(elm) {
21124			var self = this;
21125
21126			elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
21127			self.postRender();
21128
21129			return self;
21130		},
21131
21132		/**
21133		 * Renders the control to the specified element.
21134		 *
21135		 * @method renderBefore
21136		 * @param {Element} elm Element to render to.
21137		 * @return {tinymce.ui.Control} Current control instance.
21138		 */
21139		renderTo: function(elm) {
21140			var self = this;
21141
21142			elm = elm || self.getContainerElm();
21143			elm.appendChild(DomUtils.createFragment(self.renderHtml()));
21144			self.postRender();
21145
21146			return self;
21147		},
21148
21149		/**
21150		 * Post render method. Called after the control has been rendered to the target.
21151		 *
21152		 * @method postRender
21153		 * @return {tinymce.ui.Control} Current control instance.
21154		 */
21155		postRender: function() {
21156			var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
21157
21158			// Bind on<event> settings
21159			for (name in settings) {
21160				if (name.indexOf("on") === 0) {
21161					self.on(name.substr(2), settings[name]);
21162				}
21163			}
21164
21165			if (self._eventsRoot) {
21166				for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
21167					parentEventsRoot = parent._eventsRoot;
21168				}
21169
21170				if (parentEventsRoot) {
21171					for (name in parentEventsRoot._nativeEvents) {
21172						self._nativeEvents[name] = true;
21173					}
21174				}
21175			}
21176
21177			self.bindPendingEvents();
21178
21179			if (settings.style) {
21180				elm = self.getEl();
21181				if (elm) {
21182					elm.setAttribute('style', settings.style);
21183					elm.style.cssText = settings.style;
21184				}
21185			}
21186
21187			if (!self._visible) {
21188				DomUtils.css(self.getEl(), 'display', 'none');
21189			}
21190
21191			if (self.settings.border) {
21192				box = self.borderBox();
21193				DomUtils.css(self.getEl(), {
21194					'border-top-width': box.top,
21195					'border-right-width': box.right,
21196					'border-bottom-width': box.bottom,
21197					'border-left-width': box.left
21198				});
21199			}
21200
21201			// Add instance to lookup
21202			var root = self.getRoot();
21203			if (!root.controlIdLookup) {
21204				root.controlIdLookup = {};
21205			}
21206
21207			root.controlIdLookup[self._id] = self;
21208
21209			for (var key in self._aria) {
21210				self.aria(key, self._aria[key]);
21211			}
21212
21213			self.fire('postrender', {}, false);
21214		},
21215
21216		/**
21217		 * Scrolls the current control into view.
21218		 *
21219		 * @method scrollIntoView
21220		 * @param {String} align Alignment in view top|center|bottom.
21221		 * @return {tinymce.ui.Control} Current control instance.
21222		 */
21223		scrollIntoView: function(align) {
21224			function getOffset(elm, rootElm) {
21225				var x, y, parent = elm;
21226
21227				x = y = 0;
21228				while (parent && parent != rootElm && parent.nodeType) {
21229					x += parent.offsetLeft || 0;
21230					y += parent.offsetTop || 0;
21231					parent = parent.offsetParent;
21232				}
21233
21234				return {x: x, y: y};
21235			}
21236
21237			var elm = this.getEl(), parentElm = elm.parentNode;
21238			var x, y, width, height, parentWidth, parentHeight;
21239			var pos = getOffset(elm, parentElm);
21240
21241			x = pos.x;
21242			y = pos.y;
21243			width = elm.offsetWidth;
21244			height = elm.offsetHeight;
21245			parentWidth = parentElm.clientWidth;
21246			parentHeight = parentElm.clientHeight;
21247
21248			if (align == "end") {
21249				x -= parentWidth - width;
21250				y -= parentHeight - height;
21251			} else if (align == "center") {
21252				x -= (parentWidth / 2) - (width / 2);
21253				y -= (parentHeight / 2) - (height / 2);
21254			}
21255
21256			parentElm.scrollLeft = x;
21257			parentElm.scrollTop = y;
21258
21259			return this;
21260		},
21261
21262		/**
21263		 * Binds pending DOM events.
21264		 *
21265		 * @private
21266		 */
21267		bindPendingEvents: function() {
21268			var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;
21269
21270			function delegate(e) {
21271				var control = self.getParentCtrl(e.target);
21272
21273				if (control) {
21274					control.fire(e.type, e);
21275				}
21276			}
21277
21278			function mouseLeaveHandler() {
21279				var ctrl = eventRootCtrl._lastHoverCtrl;
21280
21281				if (ctrl) {
21282					ctrl.fire("mouseleave", {target: ctrl.getEl()});
21283
21284					ctrl.parents().each(function(ctrl) {
21285						ctrl.fire("mouseleave", {target: ctrl.getEl()});
21286					});
21287
21288					eventRootCtrl._lastHoverCtrl = null;
21289				}
21290			}
21291
21292			function mouseEnterHandler(e) {
21293				var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
21294
21295				// Over on a new control
21296				if (ctrl !== lastCtrl) {
21297					eventRootCtrl._lastHoverCtrl = ctrl;
21298
21299					parents = ctrl.parents().toArray().reverse();
21300					parents.push(ctrl);
21301
21302					if (lastCtrl) {
21303						lastParents = lastCtrl.parents().toArray().reverse();
21304						lastParents.push(lastCtrl);
21305
21306						for (idx = 0; idx < lastParents.length; idx++) {
21307							if (parents[idx] !== lastParents[idx]) {
21308								break;
21309							}
21310						}
21311
21312						for (i = lastParents.length - 1; i >= idx; i--) {
21313							lastCtrl = lastParents[i];
21314							lastCtrl.fire("mouseleave", {
21315								target : lastCtrl.getEl()
21316							});
21317						}
21318					}
21319
21320					for (i = idx; i < parents.length; i++) {
21321						ctrl = parents[i];
21322						ctrl.fire("mouseenter", {
21323							target : ctrl.getEl()
21324						});
21325					}
21326				}
21327			}
21328
21329			function fixWheelEvent(e) {
21330				e.preventDefault();
21331
21332				if (e.type == "mousewheel") {
21333					e.deltaY = -1 / 40 * e.wheelDelta;
21334
21335					if (e.wheelDeltaX) {
21336						e.deltaX = -1 / 40 * e.wheelDeltaX;
21337					}
21338				} else {
21339					e.deltaX = 0;
21340					e.deltaY = e.detail;
21341				}
21342
21343				e = self.fire("wheel", e);
21344			}
21345
21346			self._rendered = true;
21347
21348			nativeEvents = self._nativeEvents;
21349			if (nativeEvents) {
21350				// Find event root element if it exists
21351				parents = self.parents().toArray();
21352				parents.unshift(self);
21353				for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
21354					eventRootCtrl = parents[i]._eventsRoot;
21355				}
21356
21357				// Event root wasn't found the use the root control
21358				if (!eventRootCtrl) {
21359					eventRootCtrl = parents[parents.length - 1] || self;
21360				}
21361
21362				// Set the eventsRoot property on children that didn't have it
21363				self._eventsRoot = eventRootCtrl;
21364				for (l = i, i = 0; i < l; i++) {
21365					parents[i]._eventsRoot = eventRootCtrl;
21366				}
21367
21368				var eventRootDelegates = eventRootCtrl._delegates;
21369				if (!eventRootDelegates) {
21370					eventRootDelegates = eventRootCtrl._delegates = {};
21371				}
21372
21373				// Bind native event delegates
21374				for (name in nativeEvents) {
21375					if (!nativeEvents) {
21376						return false;
21377					}
21378
21379					if (name === "wheel" && !hasWheelEventSupport) {
21380						if (hasMouseWheelEventSupport) {
21381							DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
21382						} else {
21383							DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
21384						}
21385
21386						continue;
21387					}
21388
21389					// Special treatment for mousenter/mouseleave since these doesn't bubble
21390					if (name === "mouseenter" || name === "mouseleave") {
21391						// Fake mousenter/mouseleave
21392						if (!eventRootCtrl._hasMouseEnter) {
21393							DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
21394							DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
21395							eventRootCtrl._hasMouseEnter = 1;
21396						}
21397					} else if (!eventRootDelegates[name]) {
21398						DomUtils.on(eventRootCtrl.getEl(), name, delegate);
21399						eventRootDelegates[name] = true;
21400					}
21401
21402					// Remove the event once it's bound
21403					nativeEvents[name] = false;
21404				}
21405			}
21406		},
21407
21408		getRoot: function() {
21409			var ctrl = this, rootControl, parents = [];
21410
21411			while (ctrl) {
21412				if (ctrl.rootControl) {
21413					rootControl = ctrl.rootControl;
21414					break;
21415				}
21416
21417				parents.push(ctrl);
21418				rootControl = ctrl;
21419				ctrl = ctrl.parent();
21420			}
21421
21422			if (!rootControl) {
21423				rootControl = this;
21424			}
21425
21426			var i = parents.length;
21427			while (i--) {
21428				parents[i].rootControl = rootControl;
21429			}
21430
21431			return rootControl;
21432		},
21433
21434		/**
21435		 * Reflows the current control and it's parents.
21436		 * This should be used after you for example append children to the current control so
21437		 * that the layout managers know that they need to reposition everything.
21438		 *
21439		 * @example
21440		 * container.append({type: 'button', text: 'My button'}).reflow();
21441		 *
21442		 * @method reflow
21443		 * @return {tinymce.ui.Control} Current control instance.
21444		 */
21445		reflow: function() {
21446			this.repaint();
21447
21448			return this;
21449		}
21450
21451		/**
21452		 * Sets/gets the parent container for the control.
21453		 *
21454		 * @method parent
21455		 * @param {tinymce.ui.Container} parent Optional parent to set.
21456		 * @return {tinymce.ui.Control} Parent control or the current control on a set action.
21457		 */
21458		// parent: function(parent) {} -- Generated
21459
21460		/**
21461		 * Sets/gets the text for the control.
21462		 *
21463		 * @method text
21464		 * @param {String} value Value to set to control.
21465		 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
21466		 */
21467		// text: function(value) {} -- Generated
21468
21469		/**
21470		 * Sets/gets the width for the control.
21471		 *
21472		 * @method width
21473		 * @param {Number} value Value to set to control.
21474		 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
21475		 */
21476		// width: function(value) {} -- Generated
21477
21478		/**
21479		 * Sets/gets the height for the control.
21480		 *
21481		 * @method height
21482		 * @param {Number} value Value to set to control.
21483		 * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
21484		 */
21485		// height: function(value) {} -- Generated
21486
21487		/**
21488		 * Sets/gets the disabled state on the control.
21489		 *
21490		 * @method disabled
21491		 * @param {Boolean} state Value to set to control.
21492		 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
21493		 */
21494		// disabled: function(state) {} -- Generated
21495
21496		/**
21497		 * Sets/gets the active for the control.
21498		 *
21499		 * @method active
21500		 * @param {Boolean} state Value to set to control.
21501		 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
21502		 */
21503		// active: function(state) {} -- Generated
21504
21505		/**
21506		 * Sets/gets the name for the control.
21507		 *
21508		 * @method name
21509		 * @param {String} value Value to set to control.
21510		 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
21511		 */
21512		// name: function(value) {} -- Generated
21513
21514		/**
21515		 * Sets/gets the title for the control.
21516		 *
21517		 * @method title
21518		 * @param {String} value Value to set to control.
21519		 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
21520		 */
21521		// title: function(value) {} -- Generated
21522	});
21523
21524	return Control;
21525});
21526
21527// Included from: js/tinymce/classes/ui/Factory.js
21528
21529/**
21530 * Factory.js
21531 *
21532 * Copyright, Moxiecode Systems AB
21533 * Released under LGPL License.
21534 *
21535 * License: http://www.tinymce.com/license
21536 * Contributing: http://www.tinymce.com/contributing
21537 */
21538
21539/*global tinymce:true */
21540
21541/**
21542 * This class is a factory for control instances. This enables you
21543 * to create instances of controls without having to require the UI controls directly.
21544 *
21545 * It also allow you to override or add new control types.
21546 *
21547 * @class tinymce.ui.Factory
21548 */
21549define("tinymce/ui/Factory", [], function() {
21550	"use strict";
21551
21552	var types = {}, namespaceInit;
21553
21554	return {
21555		/**
21556		 * Adds a new control instance type to the factory.
21557		 *
21558		 * @method add
21559		 * @param {String} type Type name for example "button".
21560		 * @param {function} typeClass Class type function.
21561		 */
21562		add: function(type, typeClass) {
21563			types[type.toLowerCase()] = typeClass;
21564		},
21565
21566		/**
21567		 * Returns true/false if the specified type exists or not.
21568		 *
21569		 * @method has
21570		 * @param {String} type Type to look for.
21571		 * @return {Boolean} true/false if the control by name exists.
21572		 */
21573		has: function(type) {
21574			return !!types[type.toLowerCase()];
21575		},
21576
21577		/**
21578		 * Creates a new control instance based on the settings provided. The instance created will be
21579		 * based on the specified type property it can also create whole structures of components out of
21580		 * the specified JSON object.
21581		 *
21582		 * @example
21583		 * tinymce.ui.Factory.create({
21584		 *     type: 'button',
21585		 *     text: 'Hello world!'
21586		 * });
21587		 *
21588		 * @method create
21589		 * @param {Object/String} settings Name/Value object with items used to create the type.
21590		 * @return {tinymce.ui.Control} Control instance based on the specified type.
21591		 */
21592		create: function(type, settings) {
21593			var ControlType, name, namespace;
21594
21595			// Build type lookup
21596			if (!namespaceInit) {
21597				namespace = tinymce.ui;
21598
21599				for (name in namespace) {
21600					types[name.toLowerCase()] = namespace[name];
21601				}
21602
21603				namespaceInit = true;
21604			}
21605
21606			// If string is specified then use it as the type
21607			if (typeof(type) == 'string') {
21608				settings = settings || {};
21609				settings.type = type;
21610			} else {
21611				settings = type;
21612				type = settings.type;
21613			}
21614
21615			// Find control type
21616			type = type.toLowerCase();
21617			ControlType = types[type];
21618
21619			// #if debug
21620
21621			if (!ControlType) {
21622				throw new Error("Could not find control by type: " + type);
21623			}
21624
21625			// #endif
21626
21627			ControlType = new ControlType(settings);
21628			ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
21629
21630			return ControlType;
21631		}
21632	};
21633});
21634
21635// Included from: js/tinymce/classes/ui/KeyboardNavigation.js
21636
21637/**
21638 * KeyboardNavigation.js
21639 *
21640 * Copyright, Moxiecode Systems AB
21641 * Released under LGPL License.
21642 *
21643 * License: http://www.tinymce.com/license
21644 * Contributing: http://www.tinymce.com/contributing
21645 */
21646
21647/**
21648 * This class handles keyboard navigation of controls and elements.
21649 *
21650 * @class tinymce.ui.KeyboardNavigation
21651 */
21652define("tinymce/ui/KeyboardNavigation", [
21653], function() {
21654	"use strict";
21655
21656	/**
21657	 * This class handles all keyboard navigation for WAI-ARIA support. Each root container
21658	 * gets an instance of this class.
21659	 *
21660	 * @constructor
21661	 */
21662	return function(settings) {
21663		var root = settings.root, focusedElement, focusedControl;
21664
21665		try {
21666			focusedElement = document.activeElement;
21667		} catch (ex) {
21668			// IE sometimes fails to return a proper element
21669			focusedElement = document.body;
21670		}
21671
21672		focusedControl = root.getParentCtrl(focusedElement);
21673
21674		/**
21675		 * Returns the currently focused elements wai aria role of the currently
21676		 * focused element or specified element.
21677		 *
21678		 * @private
21679		 * @param {Element} elm Optional element to get role from.
21680		 * @return {String} Role of specified element.
21681		 */
21682		function getRole(elm) {
21683			elm = elm || focusedElement;
21684
21685			return elm && elm.getAttribute('role');
21686		}
21687
21688		/**
21689		 * Returns the wai role of the parent element of the currently
21690		 * focused element or specified element.
21691		 *
21692		 * @private
21693		 * @param {Element} elm Optional element to get parent role from.
21694		 * @return {String} Role of the first parent that has a role.
21695		 */
21696		function getParentRole(elm) {
21697			var role, parent = elm || focusedElement;
21698
21699			while ((parent = parent.parentNode)) {
21700				if ((role = getRole(parent))) {
21701					return role;
21702				}
21703			}
21704		}
21705
21706		/**
21707		 * Returns a wai aria property by name for example aria-selected.
21708		 *
21709		 * @private
21710		 * @param {String} name Name of the aria property to get for example "disabled".
21711		 * @return {String} Aria property value.
21712		 */
21713		function getAriaProp(name) {
21714			var elm = focusedElement;
21715
21716			if (elm) {
21717				return elm.getAttribute('aria-' + name);
21718			}
21719		}
21720
21721		/**
21722		 * Is the element a text input element or not.
21723		 *
21724		 * @private
21725		 * @param {Element} elm Element to check if it's an text input element or not.
21726		 * @return {Boolean} True/false if the element is a text element or not.
21727		 */
21728		function isTextInputElement(elm) {
21729			var tagName = elm.tagName.toUpperCase();
21730
21731			// Notice: since type can be "email" etc we don't check the type
21732			// So all input elements gets treated as text input elements
21733			return tagName == "INPUT" || tagName == "TEXTAREA";
21734		}
21735
21736		/**
21737		 * Returns true/false if the specified element can be focused or not.
21738		 *
21739		 * @private
21740		 * @param {Element} elm DOM element to check if it can be focused or not.
21741		 * @return {Boolean} True/false if the element can have focus.
21742		 */
21743		function canFocus(elm) {
21744			if (isTextInputElement(elm) && !elm.hidden) {
21745				return true;
21746			}
21747
21748			if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
21749				return true;
21750			}
21751
21752			return false;
21753		}
21754
21755		/**
21756		 * Returns an array of focusable visible elements within the specified container element.
21757		 *
21758		 * @private
21759		 * @param {Element} elm DOM element to find focusable elements within.
21760		 * @return {Array} Array of focusable elements.
21761		 */
21762		function getFocusElements(elm) {
21763			var elements = [];
21764
21765			function collect(elm) {
21766				if (elm.nodeType != 1 || elm.style.display == 'none') {
21767					return;
21768				}
21769
21770				if (canFocus(elm)) {
21771					elements.push(elm);
21772				}
21773
21774				for (var i = 0; i < elm.childNodes.length; i++) {
21775					collect(elm.childNodes[i]);
21776				}
21777			}
21778
21779			collect(elm || root.getEl());
21780
21781			return elements;
21782		}
21783
21784		/**
21785		 * Returns the navigation root control for the specified control. The navigation root
21786		 * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
21787		 * It will look for parents of the specified target control or the currently focused control if this option is omitted.
21788		 *
21789		 * @private
21790		 * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
21791		 * @return {tinymce.ui.Control} Navigation root control.
21792		 */
21793		function getNavigationRoot(targetControl) {
21794			var navigationRoot, controls;
21795
21796			targetControl = targetControl || focusedControl;
21797			controls = targetControl.parents().toArray();
21798			controls.unshift(targetControl);
21799
21800			for (var i = 0; i < controls.length; i++) {
21801				navigationRoot = controls[i];
21802
21803				if (navigationRoot.settings.ariaRoot) {
21804					break;
21805				}
21806			}
21807
21808			return navigationRoot;
21809		}
21810
21811		/**
21812		 * Focuses the first item in the specified targetControl element or the last aria index if the
21813		 * navigation root has the ariaRemember option enabled.
21814		 *
21815		 * @private
21816		 * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
21817		 */
21818		function focusFirst(targetControl) {
21819			var navigationRoot = getNavigationRoot(targetControl);
21820			var focusElements = getFocusElements(navigationRoot.getEl());
21821
21822			if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
21823				moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
21824			} else {
21825				moveFocusToIndex(0, focusElements);
21826			}
21827		}
21828
21829		/**
21830		 * Moves the focus to the specified index within the elements list.
21831		 * This will scope the index to the size of the element list if it changed.
21832		 *
21833		 * @private
21834		 * @param {Number} idx Specified index to move to.
21835		 * @param {Array} elements Array with dom elements to move focus within.
21836		 * @return {Number} Input index or a changed index if it was out of range.
21837		 */
21838		function moveFocusToIndex(idx, elements) {
21839			if (idx < 0) {
21840				idx = elements.length - 1;
21841			} else if (idx >= elements.length) {
21842				idx = 0;
21843			}
21844
21845			if (elements[idx]) {
21846				elements[idx].focus();
21847			}
21848
21849			return idx;
21850		}
21851
21852		/**
21853		 * Moves the focus forwards or backwards.
21854		 *
21855		 * @private
21856		 * @param {Number} dir Direction to move in positive means forward, negative means backwards.
21857		 * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
21858		 */
21859		function moveFocus(dir, elements) {
21860			var idx = -1, navigationRoot = getNavigationRoot();
21861
21862			elements = elements || getFocusElements(navigationRoot.getEl());
21863
21864			for (var i = 0; i < elements.length; i++) {
21865				if (elements[i] === focusedElement) {
21866					idx = i;
21867				}
21868			}
21869
21870			idx += dir;
21871			navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
21872		}
21873
21874		/**
21875		 * Moves the focus to the left this is called by the left key.
21876		 *
21877		 * @private
21878		 */
21879		function left() {
21880			var parentRole = getParentRole();
21881
21882			if (parentRole == "tablist") {
21883				moveFocus(-1, getFocusElements(focusedElement.parentNode));
21884			} else if (focusedControl.parent().submenu) {
21885				cancel();
21886			} else {
21887				moveFocus(-1);
21888			}
21889		}
21890
21891		/**
21892		 * Moves the focus to the right this is called by the right key.
21893		 *
21894		 * @private
21895		 */
21896		function right() {
21897			var role = getRole(), parentRole = getParentRole();
21898
21899			if (parentRole == "tablist") {
21900				moveFocus(1, getFocusElements(focusedElement.parentNode));
21901			} else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
21902				enter();
21903			} else {
21904				moveFocus(1);
21905			}
21906		}
21907
21908		/**
21909		 * Moves the focus to the up this is called by the up key.
21910		 *
21911		 * @private
21912		 */
21913		function up() {
21914			moveFocus(-1);
21915		}
21916
21917		/**
21918		 * Moves the focus to the up this is called by the down key.
21919		 *
21920		 * @private
21921		 */
21922		function down() {
21923			var role = getRole(), parentRole = getParentRole();
21924
21925			if (role == "menuitem" && parentRole == "menubar") {
21926				enter();
21927			} else if (role == "button" && getAriaProp('haspopup')) {
21928				enter({key: 'down'});
21929			} else {
21930				moveFocus(1);
21931			}
21932		}
21933
21934		/**
21935		 * Moves the focus to the next item or previous item depending on shift key.
21936		 *
21937		 * @private
21938		 * @param {DOMEvent} e DOM event object.
21939		 */
21940		function tab(e) {
21941			var parentRole = getParentRole();
21942
21943			if (parentRole == "tablist") {
21944				var elm = getFocusElements(focusedControl.getEl('body'))[0];
21945
21946				if (elm) {
21947					elm.focus();
21948				}
21949			} else {
21950				moveFocus(e.shiftKey ? -1 : 1);
21951			}
21952		}
21953
21954		/**
21955		 * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
21956		 *
21957		 * @private
21958		 */
21959		function cancel() {
21960			focusedControl.fire('cancel');
21961		}
21962
21963		/**
21964		 * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
21965		 *
21966		 * @private
21967		 * @param {Object} aria Optional aria data to pass along with the enter event.
21968		 */
21969		function enter(aria) {
21970			aria = aria || {};
21971			focusedControl.fire('click', {target: focusedElement, aria: aria});
21972		}
21973
21974		root.on('keydown', function(e) {
21975			function handleNonTabOrEscEvent(e, handler) {
21976				// Ignore non tab keys for text elements
21977				if (isTextInputElement(focusedElement)) {
21978					return;
21979				}
21980
21981				if (handler(e) !== false) {
21982					e.preventDefault();
21983				}
21984			}
21985
21986			if (e.isDefaultPrevented()) {
21987				return;
21988			}
21989
21990			switch (e.keyCode) {
21991				case 37: // DOM_VK_LEFT
21992					handleNonTabOrEscEvent(e, left);
21993					break;
21994
21995				case 39: // DOM_VK_RIGHT
21996					handleNonTabOrEscEvent(e, right);
21997					break;
21998
21999				case 38: // DOM_VK_UP
22000					handleNonTabOrEscEvent(e, up);
22001					break;
22002
22003				case 40: // DOM_VK_DOWN
22004					handleNonTabOrEscEvent(e, down);
22005					break;
22006
22007				case 27: // DOM_VK_ESCAPE
22008					cancel();
22009					break;
22010
22011				case 14: // DOM_VK_ENTER
22012				case 13: // DOM_VK_RETURN
22013				case 32: // DOM_VK_SPACE
22014					handleNonTabOrEscEvent(e, enter);
22015					break;
22016
22017				case 9: // DOM_VK_TAB
22018					if (tab(e) !== false) {
22019						e.preventDefault();
22020					}
22021					break;
22022			}
22023		});
22024
22025		root.on('focusin', function(e) {
22026			focusedElement = e.target;
22027			focusedControl = e.control;
22028		});
22029
22030		return {
22031			focusFirst: focusFirst
22032		};
22033	};
22034});
22035
22036// Included from: js/tinymce/classes/ui/Container.js
22037
22038/**
22039 * Container.js
22040 *
22041 * Copyright, Moxiecode Systems AB
22042 * Released under LGPL License.
22043 *
22044 * License: http://www.tinymce.com/license
22045 * Contributing: http://www.tinymce.com/contributing
22046 */
22047
22048/**
22049 * Container control. This is extended by all controls that can have
22050 * children such as panels etc. You can also use this class directly as an
22051 * generic container instance. The container doesn't have any specific role or style.
22052 *
22053 * @-x-less Container.less
22054 * @class tinymce.ui.Container
22055 * @extends tinymce.ui.Control
22056 */
22057define("tinymce/ui/Container", [
22058	"tinymce/ui/Control",
22059	"tinymce/ui/Collection",
22060	"tinymce/ui/Selector",
22061	"tinymce/ui/Factory",
22062	"tinymce/ui/KeyboardNavigation",
22063	"tinymce/util/Tools",
22064	"tinymce/ui/DomUtils"
22065], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
22066	"use strict";
22067
22068	var selectorCache = {};
22069
22070	return Control.extend({
22071		layout: '',
22072		innerClass: 'container-inner',
22073
22074		/**
22075		 * Constructs a new control instance with the specified settings.
22076		 *
22077		 * @constructor
22078		 * @param {Object} settings Name/value object with settings.
22079		 * @setting {Array} items Items to add to container in JSON format or control instances.
22080		 * @setting {String} layout Layout manager by name to use.
22081		 * @setting {Object} defaults Default settings to apply to all items.
22082		 */
22083		init: function(settings) {
22084			var self = this;
22085
22086			self._super(settings);
22087			settings = self.settings;
22088			self._fixed = settings.fixed;
22089			self._items = new Collection();
22090
22091			if (self.isRtl()) {
22092				self.addClass('rtl');
22093			}
22094
22095			self.addClass('container');
22096			self.addClass('container-body', 'body');
22097
22098			if (settings.containerCls) {
22099				self.addClass(settings.containerCls);
22100			}
22101
22102			self._layout = Factory.create((settings.layout || self.layout) + 'layout');
22103
22104			if (self.settings.items) {
22105				self.add(self.settings.items);
22106			}
22107
22108			// TODO: Fix this!
22109			self._hasBody = true;
22110		},
22111
22112		/**
22113		 * Returns a collection of child items that the container currently have.
22114		 *
22115		 * @method items
22116		 * @return {tinymce.ui.Collection} Control collection direct child controls.
22117		 */
22118		items: function() {
22119			return this._items;
22120		},
22121
22122		/**
22123		 * Find child controls by selector.
22124		 *
22125		 * @method find
22126		 * @param {String} selector Selector CSS pattern to find children by.
22127		 * @return {tinymce.ui.Collection} Control collection with child controls.
22128		 */
22129		find: function(selector) {
22130			selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
22131
22132			return selector.find(this);
22133		},
22134
22135		/**
22136		 * Adds one or many items to the current container. This will create instances of
22137		 * the object representations if needed.
22138		 *
22139		 * @method add
22140		 * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
22141		 * @return {tinymce.ui.Collection} Current collection control.
22142		 */
22143		add: function(items) {
22144			var self = this;
22145
22146			self.items().add(self.create(items)).parent(self);
22147
22148			return self;
22149		},
22150
22151		/**
22152		 * Focuses the current container instance. This will look
22153		 * for the first control in the container and focus that.
22154		 *
22155		 * @method focus
22156		 * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
22157		 * @return {tinymce.ui.Collection} Current instance.
22158		 */
22159		focus: function(keyboard) {
22160			var self = this, focusCtrl, keyboardNav, items;
22161
22162			if (keyboard) {
22163				keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
22164
22165				if (keyboardNav) {
22166					keyboardNav.focusFirst(self);
22167					return;
22168				}
22169			}
22170
22171			items = self.find('*');
22172
22173			// TODO: Figure out a better way to auto focus alert dialog buttons
22174			if (self.statusbar) {
22175				items.add(self.statusbar.items());
22176			}
22177
22178			items.each(function(ctrl) {
22179				if (ctrl.settings.autofocus) {
22180					focusCtrl = null;
22181					return false;
22182				}
22183
22184				if (ctrl.canFocus) {
22185					focusCtrl = focusCtrl || ctrl;
22186				}
22187			});
22188
22189			if (focusCtrl) {
22190				focusCtrl.focus();
22191			}
22192
22193			return self;
22194		},
22195
22196		/**
22197		 * Replaces the specified child control with a new control.
22198		 *
22199		 * @method replace
22200		 * @param {tinymce.ui.Control} oldItem Old item to be replaced.
22201		 * @param {tinymce.ui.Control} newItem New item to be inserted.
22202		 */
22203		replace: function(oldItem, newItem) {
22204			var ctrlElm, items = this.items(), i = items.length;
22205
22206			// Replace the item in collection
22207			while (i--) {
22208				if (items[i] === oldItem) {
22209					items[i] = newItem;
22210					break;
22211				}
22212			}
22213
22214			if (i >= 0) {
22215				// Remove new item from DOM
22216				ctrlElm = newItem.getEl();
22217				if (ctrlElm) {
22218					ctrlElm.parentNode.removeChild(ctrlElm);
22219				}
22220
22221				// Remove old item from DOM
22222				ctrlElm = oldItem.getEl();
22223				if (ctrlElm) {
22224					ctrlElm.parentNode.removeChild(ctrlElm);
22225				}
22226			}
22227
22228			// Adopt the item
22229			newItem.parent(this);
22230		},
22231
22232		/**
22233		 * Creates the specified items. If any of the items is plain JSON style objects
22234		 * it will convert these into real tinymce.ui.Control instances.
22235		 *
22236		 * @method create
22237		 * @param {Array} items Array of items to convert into control instances.
22238		 * @return {Array} Array with control instances.
22239		 */
22240		create: function(items) {
22241			var self = this, settings, ctrlItems = [];
22242
22243			// Non array structure, then force it into an array
22244			if (!Tools.isArray(items)) {
22245				items = [items];
22246			}
22247
22248			// Add default type to each child control
22249			Tools.each(items, function(item) {
22250				if (item) {
22251					// Construct item if needed
22252					if (!(item instanceof Control)) {
22253						// Name only then convert it to an object
22254						if (typeof(item) == "string") {
22255							item = {type: item};
22256						}
22257
22258						// Create control instance based on input settings and default settings
22259						settings = Tools.extend({}, self.settings.defaults, item);
22260						item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
22261							(settings.defaults ? settings.defaults.type : null);
22262						item = Factory.create(settings);
22263					}
22264
22265					ctrlItems.push(item);
22266				}
22267			});
22268
22269			return ctrlItems;
22270		},
22271
22272		/**
22273		 * Renders new control instances.
22274		 *
22275		 * @private
22276		 */
22277		renderNew: function() {
22278			var self = this;
22279
22280			// Render any new items
22281			self.items().each(function(ctrl, index) {
22282				var containerElm, fragment;
22283
22284				ctrl.parent(self);
22285
22286				if (!ctrl._rendered) {
22287					containerElm = self.getEl('body');
22288					fragment = DomUtils.createFragment(ctrl.renderHtml());
22289
22290					// Insert or append the item
22291					if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
22292						containerElm.insertBefore(fragment, containerElm.childNodes[index]);
22293					} else {
22294						containerElm.appendChild(fragment);
22295					}
22296
22297					ctrl.postRender();
22298				}
22299			});
22300
22301			self._layout.applyClasses(self);
22302			self._lastRect = null;
22303
22304			return self;
22305		},
22306
22307		/**
22308		 * Appends new instances to the current container.
22309		 *
22310		 * @method append
22311		 * @param {Array/tinymce.ui.Collection} items Array if controls to append.
22312		 * @return {tinymce.ui.Container} Current container instance.
22313		 */
22314		append: function(items) {
22315			return this.add(items).renderNew();
22316		},
22317
22318		/**
22319		 * Prepends new instances to the current container.
22320		 *
22321		 * @method prepend
22322		 * @param {Array/tinymce.ui.Collection}
22322 items Array if controls to prepend.
22323		 * @return {tinymce.ui.Container} Current container instance.
22324		 */
22325		prepend: function(items) {
22326			var self = this;
22327
22328			self.items().set(self.create(items).concat(self.items().toArray()));
22329
22330			return self.renderNew();
22331		},
22332
22333		/**
22334		 * Inserts an control at a specific index.
22335		 *
22336		 * @method insert
22337		 * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
22338		 * @param {Number} index Index to insert controls at.
22339		 * @param {Boolean} [before=false] Inserts controls before the index.
22340		 */
22341		insert: function(items, index, before) {
22342			var self = this, curItems, beforeItems, afterItems;
22343
22344			items = self.create(items);
22345			curItems = self.items();
22346
22347			if (!before && index < curItems.length - 1) {
22348				index += 1;
22349			}
22350
22351			if (index >= 0 && index < curItems.length) {
22352				beforeItems = curItems.slice(0, index).toArray();
22353				afterItems = curItems.slice(index).toArray();
22354				curItems.set(beforeItems.concat(items, afterItems));
22355			}
22356
22357			return self.renderNew();
22358		},
22359
22360		/**
22361		 * Populates the form fields from the specified JSON data object.
22362		 *
22363		 * Control items in the form that matches the data will have it's value set.
22364		 *
22365		 * @method fromJSON
22366		 * @param {Object} data JSON data object to set control values by.
22367		 * @return {tinymce.ui.Container} Current form instance.
22368		 */
22369		fromJSON: function(data) {
22370			var self = this;
22371
22372			for (var name in data) {
22373				self.find('#' + name).value(data[name]);
22374			}
22375
22376			return self;
22377		},
22378
22379		/**
22380		 * Serializes the form into a JSON object by getting all items
22381		 * that has a name and a value.
22382		 *
22383		 * @method toJSON
22384		 * @return {Object} JSON object with form data.
22385		 */
22386		toJSON: function() {
22387			var self = this, data = {};
22388
22389			self.find('*').each(function(ctrl) {
22390				var name = ctrl.name(), value = ctrl.value();
22391
22392				if (name && typeof(value) != "undefined") {
22393					data[name] = value;
22394				}
22395			});
22396
22397			return data;
22398		},
22399
22400		preRender: function() {
22401		},
22402
22403		/**
22404		 * Renders the control as a HTML string.
22405		 *
22406		 * @method renderHtml
22407		 * @return {String} HTML representing the control.
22408		 */
22409		renderHtml: function() {
22410			var self = this, layout = self._layout, role = this.settings.role;
22411
22412			self.preRender();
22413			layout.preRender(self);
22414
22415			return (
22416				'<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
22417					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
22418						(self.settings.html || '') + layout.renderHtml(self) +
22419					'</div>' +
22420				'</div>'
22421			);
22422		},
22423
22424		/**
22425		 * Post render method. Called after the control has been rendered to the target.
22426		 *
22427		 * @method postRender
22428		 * @return {tinymce.ui.Container} Current combobox instance.
22429		 */
22430		postRender: function() {
22431			var self = this, box;
22432
22433			self.items().exec('postRender');
22434			self._super();
22435
22436			self._layout.postRender(self);
22437			self._rendered = true;
22438
22439			if (self.settings.style) {
22440				DomUtils.css(self.getEl(), self.settings.style);
22441			}
22442
22443			if (self.settings.border) {
22444				box = self.borderBox();
22445				DomUtils.css(self.getEl(), {
22446					'border-top-width': box.top,
22447					'border-right-width': box.right,
22448					'border-bottom-width': box.bottom,
22449					'border-left-width': box.left
22450				});
22451			}
22452
22453			if (!self.parent()) {
22454				self.keyboardNav = new KeyboardNavigation({
22455					root: self
22456				});
22457			}
22458
22459			return self;
22460		},
22461
22462		/**
22463		 * Initializes the current controls layout rect.
22464		 * This will be executed by the layout managers to determine the
22465		 * default minWidth/minHeight etc.
22466		 *
22467		 * @method initLayoutRect
22468		 * @return {Object} Layout rect instance.
22469		 */
22470		initLayoutRect: function() {
22471			var self = this, layoutRect = self._super();
22472
22473			// Recalc container size by asking layout manager
22474			self._layout.recalc(self);
22475
22476			return layoutRect;
22477		},
22478
22479		/**
22480		 * Recalculates the positions of the controls in the current container.
22481		 * This is invoked by the reflow method and shouldn't be called directly.
22482		 *
22483		 * @method recalc
22484		 */
22485		recalc: function() {
22486			var self = this, rect = self._layoutRect, lastRect = self._lastRect;
22487
22488			if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
22489				self._layout.recalc(self);
22490				rect = self.layoutRect();
22491				self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
22492				return true;
22493			}
22494		},
22495
22496		/**
22497		 * Reflows the current container and it's children and possible parents.
22498		 * This should be used after you for example append children to the current control so
22499		 * that the layout managers know that they need to reposition everything.
22500		 *
22501		 * @example
22502		 * container.append({type: 'button', text: 'My button'}).reflow();
22503		 *
22504		 * @method reflow
22505		 * @return {tinymce.ui.Container} Current container instance.
22506		 */
22507		reflow: function() {
22508			var i;
22509
22510			if (this.visible()) {
22511				Control.repaintControls = [];
22512				Control.repaintControls.map = {};
22513
22514				this.recalc();
22515				i = Control.repaintControls.length;
22516
22517				while (i--) {
22518					Control.repaintControls[i].repaint();
22519				}
22520
22521				// TODO: Fix me!
22522				if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
22523					this.repaint();
22524				}
22525
22526				Control.repaintControls = [];
22527			}
22528
22529			return this;
22530		}
22531	});
22532});
22533
22534// Included from: js/tinymce/classes/ui/DragHelper.js
22535
22536/**
22537 * DragHelper.js
22538 *
22539 * Copyright, Moxiecode Systems AB
22540 * Released under LGPL License.
22541 *
22542 * License: http://www.tinymce.com/license
22543 * Contributing: http://www.tinymce.com/contributing
22544 */
22545
22546/**
22547 * Drag/drop helper class.
22548 *
22549 * @example
22550 * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
22551 *     start: function(evt) {
22552 *     },
22553 *
22554 *     drag: function(evt) {
22555 *     },
22556 *
22557 *     end: function(evt) {
22558 *     }
22559 * });
22560 *
22561 * @class tinymce.ui.DragHelper
22562 */
22563define("tinymce/ui/DragHelper", [
22564	"tinymce/ui/DomUtils"
22565], function(DomUtils) {
22566	"use strict";
22567
22568	function getDocumentSize() {
22569		var doc = document, documentElement, body, scrollWidth, clientWidth;
22570		var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
22571
22572		documentElement = doc.documentElement;
22573		body = doc.body;
22574
22575		scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
22576		clientWidth = max(documentElement.clientWidth, body.clientWidth);
22577		offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
22578
22579		scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
22580		clientHeight = max(documentElement.clientHeight, body.clientHeight);
22581		offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
22582
22583		return {
22584			width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
22585			height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
22586		};
22587	}
22588
22589	return function(id, settings) {
22590		var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY;
22591
22592		settings = settings || {};
22593
22594		function getHandleElm() {
22595			return doc.getElementById(settings.handle || id);
22596		}
22597
22598		start = function(e) {
22599			var docSize = getDocumentSize(), handleElm, cursor;
22600
22601			e.preventDefault();
22602			downButton = e.button;
22603			handleElm = getHandleElm();
22604			startX = e.screenX;
22605			startY = e.screenY;
22606
22607			// Grab cursor from handle
22608			if (window.getComputedStyle) {
22609				cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
22610			} else {
22611				cursor = handleElm.runtimeStyle.cursor;
22612			}
22613
22614			// Create event overlay and add it to document
22615			eventOverlayElm = doc.createElement('div');
22616			DomUtils.css(eventOverlayElm, {
22617				position: "absolute",
22618				top: 0, left: 0,
22619				width: docSize.width,
22620				height: docSize.height,
22621				zIndex: 0x7FFFFFFF,
22622				opacity: 0.0001,
22623				cursor: cursor
22624			});
22625
22626			doc.body.appendChild(eventOverlayElm);
22627
22628			// Bind mouse events
22629			DomUtils.on(doc, 'mousemove', drag);
22630			DomUtils.on(doc, 'mouseup', stop);
22631
22632			// Begin drag
22633			settings.start(e);
22634		};
22635
22636		drag = function(e) {
22637			if (e.button !== downButton) {
22638				return stop(e);
22639			}
22640
22641			e.deltaX = e.screenX - startX;
22642			e.deltaY = e.screenY - startY;
22643
22644			e.preventDefault();
22645			settings.drag(e);
22646		};
22647
22648		stop = function(e) {
22649			DomUtils.off(doc, 'mousemove', drag);
22650			DomUtils.off(doc, 'mouseup', stop);
22651
22652			eventOverlayElm.parentNode.removeChild(eventOverlayElm);
22653
22654			if (settings.stop) {
22655				settings.stop(e);
22656			}
22657		};
22658
22659		/**
22660		 * Destroys the drag/drop helper instance.
22661		 *
22662		 * @method destroy
22663		 */
22664		this.destroy = function() {
22665			DomUtils.off(getHandleElm());
22666		};
22667
22668		DomUtils.on(getHandleElm(), 'mousedown', start);
22669	};
22670});
22671
22672// Included from: js/tinymce/classes/ui/Scrollable.js
22673
22674/**
22675 * Scrollable.js
22676 *
22677 * Copyright, Moxiecode Systems AB
22678 * Released under LGPL License.
22679 *
22680 * License: http://www.tinymce.com/license
22681 * Contributing: http://www.tinymce.com/contributing
22682 */
22683
22684/**
22685 * This mixin makes controls scrollable using custom scrollbars.
22686 *
22687 * @-x-less Scrollable.less
22688 * @mixin tinymce.ui.Scrollable
22689 */
22690define("tinymce/ui/Scrollable", [
22691	"tinymce/ui/DomUtils",
22692	"tinymce/ui/DragHelper"
22693], function(DomUtils, DragHelper) {
22694	"use strict";
22695
22696	return {
22697		init: function() {
22698			var self = this;
22699			self.on('repaint', self.renderScroll);
22700		},
22701
22702		renderScroll: function() {
22703			var self = this, margin = 2;
22704
22705			function repaintScroll() {
22706				var hasScrollH, hasScrollV, bodyElm;
22707
22708				function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
22709					var containerElm, scrollBarElm, scrollThumbElm;
22710					var containerSize, scrollSize, ratio, rect;
22711					var posNameLower, sizeNameLower;
22712
22713					scrollBarElm = self.getEl('scroll' + axisName);
22714					if (scrollBarElm) {
22715						posNameLower = posName.toLowerCase();
22716						sizeNameLower = sizeName.toLowerCase();
22717
22718						if (self.getEl('absend')) {
22719							DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1);
22720						}
22721
22722						if (!hasScroll) {
22723							DomUtils.css(scrollBarElm, 'display', 'none');
22724							return;
22725						}
22726
22727						DomUtils.css(scrollBarElm, 'display', 'block');
22728						containerElm = self.getEl('body');
22729						scrollThumbElm = self.getEl('scroll' + axisName + "t");
22730						containerSize = containerElm["client" + sizeName] - (margin * 2);
22731						containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
22732						scrollSize = containerElm["scroll" + sizeName];
22733						ratio = containerSize / scrollSize;
22734
22735						rect = {};
22736						rect[posNameLower] = containerElm["offset" + posName] + margin;
22737						rect[sizeNameLower] = containerSize;
22738						DomUtils.css(scrollBarElm, rect);
22739
22740						rect = {};
22741						rect[posNameLower] = containerElm["scroll" + posName] * ratio;
22742						rect[sizeNameLower] = containerSize * ratio;
22743						DomUtils.css(scrollThumbElm, rect);
22744					}
22745				}
22746
22747				bodyElm = self.getEl('body');
22748				hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
22749				hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
22750
22751				repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
22752				repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
22753			}
22754
22755			function addScroll() {
22756				function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
22757					var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
22758
22759					self.getEl().appendChild(DomUtils.createFragment(
22760						'<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
22761							'<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
22762						'</div>'
22763					));
22764
22765					self.draghelper = new DragHelper(axisId + 't', {
22766						start: function() {
22767							scrollStart = self.getEl('body')["scroll" + posName];
22768							DomUtils.addClass(DomUtils.get(axisId), prefix + 'active');
22769						},
22770
22771						drag: function(e) {
22772							var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
22773
22774							hasScrollH = layoutRect.contentW > layoutRect.innerW;
22775							hasScrollV = layoutRect.contentH > layoutRect.innerH;
22776							containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
22777							containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
22778
22779							ratio = containerSize / self.getEl('body')["scroll" + sizeName];
22780							self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
22781						},
22782
22783						stop: function() {
22784							DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active');
22785						}
22786					});
22787/*
22788					self.on('click', function(e) {
22789						if (e.target.id == self._id + '-scrollv') {
22790
22791						}
22792					});*/
22793				}
22794
22795				self.addClass('scroll');
22796
22797				addScrollAxis("v", "Top", "Height", "Y", "Width");
22798				addScrollAxis("h", "Left", "Width", "X", "Height");
22799			}
22800
22801			if (self.settings.autoScroll) {
22802				if (!self._hasScroll) {
22803					self._hasScroll = true;
22804					addScroll();
22805
22806					self.on('wheel', function(e) {
22807						var bodyEl = self.getEl('body');
22808
22809						bodyEl.scrollLeft += (e.deltaX || 0) * 10;
22810						bodyEl.scrollTop += e.deltaY * 10;
22811
22812						repaintScroll();
22813					});
22814
22815					DomUtils.on(self.getEl('body'), "scroll", repaintScroll);
22816				}
22817
22818				repaintScroll();
22819			}
22820		}
22821	};
22822});
22823
22824// Included from: js/tinymce/classes/ui/Panel.js
22825
22826/**
22827 * Panel.js
22828 *
22829 * Copyright, Moxiecode Systems AB
22830 * Released under LGPL License.
22831 *
22832 * License: http://www.tinymce.com/license
22833 * Contributing: http://www.tinymce.com/contributing
22834 */
22835
22836/**
22837 * Creates a new panel.
22838 *
22839 * @-x-less Panel.less
22840 * @class tinymce.ui.Panel
22841 * @extends tinymce.ui.Container
22842 * @mixes tinymce.ui.Scrollable
22843 */
22844define("tinymce/ui/Panel", [
22845	"tinymce/ui/Container",
22846	"tinymce/ui/Scrollable"
22847], function(Container, Scrollable) {
22848	"use strict";
22849
22850	return Container.extend({
22851		Defaults: {
22852			layout: 'fit',
22853			containerCls: 'panel'
22854		},
22855
22856		Mixins: [Scrollable],
22857
22858		/**
22859		 * Renders the control as a HTML string.
22860		 *
22861		 * @method renderHtml
22862		 * @return {String} HTML representing the control.
22863		 */
22864		renderHtml: function() {
22865			var self = this, layout = self._layout, innerHtml = self.settings.html;
22866
22867			self.preRender();
22868			layout.preRender(self);
22869
22870			if (typeof(innerHtml) == "undefined") {
22871				innerHtml = (
22872					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
22873						layout.renderHtml(self) +
22874					'</div>'
22875				);
22876			} else {
22877				if (typeof(innerHtml) == 'function') {
22878					innerHtml = innerHtml.call(self);
22879				}
22880
22881				self._hasBody = false;
22882			}
22883
22884			return (
22885				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
22886					(self._preBodyHtml || '') +
22887					innerHtml +
22888				'</div>'
22889			);
22890		}
22891	});
22892});
22893
22894// Included from: js/tinymce/classes/ui/Movable.js
22895
22896/**
22897 * Movable.js
22898 *
22899 * Copyright, Moxiecode Systems AB
22900 * Released under LGPL License.
22901 *
22902 * License: http://www.tinymce.com/license
22903 * Contributing: http://www.tinymce.com/contributing
22904 */
22905
22906/**
22907 * Movable mixin. Makes controls movable absolute and relative to other elements.
22908 *
22909 * @mixin tinymce.ui.Movable
22910 */
22911define("tinymce/ui/Movable", [
22912	"tinymce/ui/DomUtils"
22913], function(DomUtils) {
22914	"use strict";
22915
22916	function calculateRelativePosition(ctrl, targetElm, rel) {
22917		var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;
22918
22919		viewport = DomUtils.getViewPort();
22920
22921		// Get pos of target
22922		pos = DomUtils.getPos(targetElm);
22923		x = pos.x;
22924		y = pos.y;
22925
22926		if (ctrl._fixed && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') {
22927			x -= viewport.x;
22928			y -= viewport.y;
22929		}
22930
22931		// Get size of self
22932		ctrlElm = ctrl.getEl();
22933		size = DomUtils.getSize(ctrlElm);
22934		selfW = size.width;
22935		selfH = size.height;
22936
22937		// Get size of target
22938		size = DomUtils.getSize(targetElm);
22939		targetW = size.width;
22940		targetH = size.height;
22941
22942		// Parse align string
22943		rel = (rel || '').split('');
22944
22945		// Target corners
22946		if (rel[0] === 'b') {
22947			y += targetH;
22948		}
22949
22950		if (rel[1] === 'r') {
22951			x += targetW;
22952		}
22953
22954		if (rel[0] === 'c') {
22955			y += Math.round(targetH / 2);
22956		}
22957
22958		if (rel[1] === 'c') {
22959			x += Math.round(targetW / 2);
22960		}
22961
22962		// Self corners
22963		if (rel[3] === 'b') {
22964			y -= selfH;
22965		}
22966
22967		if (rel[4] === 'r') {
22968			x -= selfW;
22969		}
22970
22971		if (rel[3] === 'c') {
22972			y -= Math.round(selfH / 2);
22973		}
22974
22975		if (rel[4] === 'c') {
22976			x -= Math.round(selfW / 2);
22977		}
22978
22979		return {
22980			x: x,
22981			y: y,
22982			w: selfW,
22983			h: selfH
22984		};
22985	}
22986
22987	return {
22988		/**
22989		 * Tests various positions to get the most suitable one.
22990		 *
22991		 * @method testMoveRel
22992		 * @param {DOMElement} elm Element to position against.
22993		 * @param {Array} rels Array with relative positions.
22994		 * @return {String} Best suitable relative position.
22995		 */
22996		testMoveRel: function(elm, rels) {
22997			var viewPortRect = DomUtils.getViewPort();
22998
22999			for (var i = 0; i < rels.length; i++) {
23000				var pos = calculateRelativePosition(this, elm, rels[i]);
23001
23002				if (this._fixed) {
23003					if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
23004						return rels[i];
23005					}
23006				} else {
23007					if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
23008						pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
23009						return rels[i];
23010					}
23011				}
23012			}
23013
23014			return rels[0];
23015		},
23016
23017		/**
23018		 * Move relative to the specified element.
23019		 *
23020		 * @method moveRel
23021		 * @param {Element} elm Element to move relative to.
23022		 * @param {String} rel Relative mode. For example: br-tl.
23023		 * @return {tinymce.ui.Control} Current control instance.
23024		 */
23025		moveRel: function(elm, rel) {
23026			if (typeof(rel) != 'string') {
23027				rel = this.testMoveRel(elm, rel);
23028			}
23029
23030			var pos = calculateRelativePosition(this, elm, rel);
23031			return this.moveTo(pos.x, pos.y);
23032		},
23033
23034		/**
23035		 * Move by a relative x, y values.
23036		 *
23037		 * @method moveBy
23038		 * @param {Number} dx Relative x position.
23039		 * @param {Number} dy Relative y position.
23040		 * @return {tinymce.ui.Control} Current control instance.
23041		 */
23042		moveBy: function(dx, dy) {
23043			var self = this, rect = self.layoutRect();
23044
23045			self.moveTo(rect.x + dx, rect.y + dy);
23046
23047			return self;
23048		},
23049
23050		/**
23051		 * Move to absolute position.
23052		 *
23053		 * @method moveTo
23054		 * @param {Number} x Absolute x position.
23055		 * @param {Number} y Absolute y position.
23056		 * @return {tinymce.ui.Control} Current control instance.
23057		 */
23058		moveTo: function(x, y) {
23059			var self = this;
23060
23061			// TODO: Move this to some global class
23062			function contrain(value, max, size) {
23063				if (value < 0) {
23064					return 0;
23065				}
23066
23067				if (value + size > max) {
23068					value = max - size;
23069					return value < 0 ? 0 : value;
23070				}
23071
23072				return value;
23073			}
23074
23075			if (self.settings.constrainToViewport) {
23076				var viewPortRect = DomUtils.getViewPort(window);
23077				var layoutRect = self.layoutRect();
23078
23079				x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
23080				y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
23081			}
23082
23083			if (self._rendered) {
23084				self.layoutRect({x: x, y: y}).repaint();
23085			} else {
23086				self.settings.x = x;
23087				self.settings.y = y;
23088			}
23089
23090			self.fire('move', {x: x, y: y});
23091
23092			return self;
23093		}
23094	};
23095});
23096
23097// Included from: js/tinymce/classes/ui/Resizable.js
23098
23099/**
23100 * Resizable.js
23101 *
23102 * Copyright, Moxiecode Systems AB
23103 * Released under LGPL License.
23104 *
23105 * License: http://www.tinymce.com/license
23106 * Contributing: http://www.tinymce.com/contributing
23107 */
23108
23109/**
23110 * Resizable mixin. Enables controls to be resized.
23111 *
23112 * @mixin tinymce.ui.Resizable
23113 */
23114define("tinymce/ui/Resizable", [
23115	"tinymce/ui/DomUtils"
23116], function(DomUtils) {
23117	"use strict";
23118
23119	return {
23120		/**
23121		 * Resizes the control to contents.
23122		 *
23123		 * @method resizeToContent
23124		 */
23125		resizeToContent: function() {
23126			this._layoutRect.autoResize = true;
23127			this._lastRect = null;
23128			this.reflow();
23129		},
23130
23131		/**
23132		 * Resizes the control to a specific width/height.
23133		 *
23134		 * @method resizeTo
23135		 * @param {Number} w Control width.
23136		 * @param {Number} h Control height.
23137		 * @return {tinymce.ui.Control} Current control instance.
23138		 */
23139		resizeTo: function(w, h) {
23140			// TODO: Fix hack
23141			if (w <= 1 || h <= 1) {
23142				var rect = DomUtils.getWindowSize();
23143
23144				w = w <= 1 ? w * rect.w : w;
23145				h = h <= 1 ? h * rect.h : h;
23146			}
23147
23148			this._layoutRect.autoResize = false;
23149			return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
23150		},
23151
23152		/**
23153		 * Resizes the control to a specific relative width/height.
23154		 *
23155		 * @method resizeBy
23156		 * @param {Number} dw Relative control width.
23157		 * @param {Number} dh Relative control height.
23158		 * @return {tinymce.ui.Control} Current control instance.
23159		 */
23160		resizeBy: function(dw, dh) {
23161			var self = this, rect = self.layoutRect();
23162
23163			return self.resizeTo(rect.w + dw, rect.h + dh);
23164		}
23165	};
23166});
23167
23168// Included from: js/tinymce/classes/ui/FloatPanel.js
23169
23170/**
23171 * FloatPanel.js
23172 *
23173 * Copyright, Moxiecode Systems AB
23174 * Released under LGPL License.
23175 *
23176 * License: http://www.tinymce.com/license
23177 * Contributing: http://www.tinymce.com/contributing
23178 */
23179
23180/**
23181 * This class creates a floating panel.
23182 *
23183 * @-x-less FloatPanel.less
23184 * @class tinymce.ui.FloatPanel
23185 * @extends tinymce.ui.Panel
23186 * @mixes tinymce.ui.Movable
23187 * @mixes tinymce.ui.Resizable
23188 */
23189define("tinymce/ui/FloatPanel", [
23190	"tinymce/ui/Panel",
23191	"tinymce/ui/Movable",
23192	"tinymce/ui/Resizable",
23193	"tinymce/ui/DomUtils"
23194], function(Panel, Movable, Resizable, DomUtils) {
23195	"use strict";
23196
23197	var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
23198	var zOrder = [], hasModal;
23199
23200	function bindDocumentClickHandler() {
23201		function isChildOf(ctrl, parent) {
23202			while (ctrl) {
23203				if (ctrl == parent) {
23204					return true;
23205				}
23206
23207				ctrl = ctrl.parent();
23208			}
23209		}
23210
23211		if (!documentClickHandler) {
23212			documentClickHandler = function(e) {
23213				// Gecko fires click event and in the wrong order on Mac so lets normalize
23214				if (e.button == 2) {
23215					return;
23216				}
23217
23218				// Hide any float panel when a click is out side that float panel and the
23219				// float panels direct parent for example a click on a menu button
23220				var i = visiblePanels.length;
23221				while (i--) {
23222					var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
23223
23224					if (panel.settings.autohide) {
23225						if (clickCtrl) {
23226							if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
23227								continue;
23228							}
23229						}
23230
23231						e = panel.fire('autohide', {target: e.target});
23232						if (!e.isDefaultPrevented()) {
23233							panel.hide();
23234						}
23235					}
23236				}
23237			};
23238
23239			DomUtils.on(document, 'click', documentClickHandler);
23240		}
23241	}
23242
23243	function bindDocumentScrollHandler() {
23244		if (!documentScrollHandler) {
23245			documentScrollHandler = function() {
23246				var i;
23247
23248				i = visiblePanels.length;
23249				while (i--) {
23250					repositionPanel(visiblePanels[i]);
23251				}
23252			};
23253
23254			DomUtils.on(window, 'scroll', documentScrollHandler);
23255		}
23256	}
23257
23258	function bindWindowResizeHandler() {
23259		if (!windowResizeHandler) {
23260			var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight;
23261
23262			windowResizeHandler = function() {
23263				// Workaround for #7065 IE 7 fires resize events event though the window wasn't resized
23264				if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) {
23265					clientWidth = docElm.clientWidth;
23266					clientHeight = docElm.clientHeight;
23267					FloatPanel.hideAll();
23268				}
23269			};
23270
23271			DomUtils.on(window, 'resize', windowResizeHandler);
23272		}
23273	}
23274
23275	/**
23276	 * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
23277	 * also reposition all child panels of the current panel.
23278	 */
23279	function repositionPanel(panel) {
23280		var scrollY = DomUtils.getViewPort().y;
23281
23282		function toggleFixedChildPanels(fixed, deltaY) {
23283			var parent;
23284
23285			for (var i = 0; i < visiblePanels.length; i++) {
23286				if (visiblePanels[i] != panel) {
23287					parent = visiblePanels[i].parent();
23288
23289					while (parent && (parent = parent.parent())) {
23290						if (parent == panel) {
23291							visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
23292						}
23293					}
23294				}
23295			}
23296		}
23297
23298		if (panel.settings.autofix) {
23299			if (!panel._fixed) {
23300				panel._autoFixY = panel.layoutRect().y;
23301
23302				if (panel._autoFixY < scrollY) {
23303					panel.fixed(true).layoutRect({y: 0}).repaint();
23304					toggleFixedChildPanels(true, scrollY - panel._autoFixY);
23305				}
23306			} else {
23307				if (panel._autoFixY > scrollY) {
23308					panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
23309					toggleFixedChildPanels(false, panel._autoFixY - scrollY);
23310				}
23311			}
23312		}
23313	}
23314
23315	function addRemove(add, ctrl) {
23316		var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
23317
23318		if (add) {
23319			zOrder.push(ctrl);
23320		} else {
23321			i = zOrder.length;
23322
23323			while (i--) {
23324				if (zOrder[i] === ctrl) {
23325					zOrder.splice(i, 1);
23326				}
23327			}
23328		}
23329
23330		if (zOrder.length) {
23331			for (i = 0; i < zOrder.length; i++) {
23332				if (zOrder[i].modal) {
23333					zIndex++;
23334					topModal = zOrder[i];
23335				}
23336
23337				zOrder[i].getEl().style.zIndex = zIndex;
23338				zOrder[i].zIndex = zIndex;
23339				zIndex++;
23340			}
23341		}
23342
23343		var modalBlockEl = document.getElementById(ctrl.classPrefix + 'modal-block');
23344
23345		if (topModal) {
23346			DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
23347		} else if (modalBlockEl) {
23348			modalBlockEl.parentNode.removeChild(modalBlockEl);
23349			hasModal = false;
23350		}
23351
23352		FloatPanel.currentZIndex = zIndex;
23353	}
23354
23355	var FloatPanel = Panel.extend({
23356		Mixins: [Movable, Resizable],
23357
23358		/**
23359		 * Constructs a new control instance with the specified settings.
23360		 *
23361		 * @constructor
23362		 * @param {Object} settings Name/value object with settings.
23363		 * @setting {Boolean} autohide Automatically hide the panel.
23364		 */
23365		init: function(settings) {
23366			var self = this;
23367
23368			self._super(settings);
23369			self._eventsRoot = self;
23370
23371			self.addClass('floatpanel');
23372
23373			// Hide floatpanes on click out side the root button
23374			if (settings.autohide) {
23375				bindDocumentClickHandler();
23376				bindWindowResizeHandler();
23377				visiblePanels.push(self);
23378			}
23379
23380			if (settings.autofix) {
23381				bindDocumentScrollHandler();
23382
23383				self.on('move', function() {
23384					repositionPanel(this);
23385				});
23386			}
23387
23388			self.on('postrender show', function(e) {
23389				if (e.control == self) {
23390					var modalBlockEl, prefix = self.classPrefix;
23391
23392					if (self.modal && !hasModal) {
23393						modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' +
23394							prefix + 'reset ' + prefix + 'fade"></div>');
23395						modalBlockEl = modalBlockEl.firstChild;
23396
23397						self.getContainerElm().appendChild(modalBlockEl);
23398
23399						setTimeout(function() {
23400							DomUtils.addClass(modalBlockEl, prefix + 'in');
23401							DomUtils.addClass(self.getEl(), prefix + 'in');
23402						}, 0);
23403
23404						hasModal = true;
23405					}
23406
23407					addRemove(true, self);
23408				}
23409			});
23410
23411			self.on('show', function() {
23412				self.parents().each(function(ctrl) {
23413					if (ctrl._fixed) {
23414						self.fixed(true);
23415						return false;
23416					}
23417				});
23418			});
23419
23420			if (settings.popover) {
23421				self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
23422				self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start');
23423			}
23424		},
23425
23426		fixed: function(state) {
23427			var self = this;
23428
23429			if (self._fixed != state) {
23430				if (self._rendered) {
23431					var viewport = DomUtils.getViewPort();
23432
23433					if (state) {
23434						self.layoutRect().y -= viewport.y;
23435					} else {
23436						self.layoutRect().y += viewport.y;
23437					}
23438				}
23439
23440				self.toggleClass('fixed', state);
23441				self._fixed = state;
23442			}
23443
23444			return self;
23445		},
23446
23447		/**
23448		 * Shows the current float panel.
23449		 *
23450		 * @method show
23451		 * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
23452		 */
23453		show: function() {
23454			var self = this, i, state = self._super();
23455
23456			i = visiblePanels.length;
23457			while (i--) {
23458				if (visiblePanels[i] === self) {
23459					break;
23460				}
23461			}
23462
23463			if (i === -1) {
23464				visiblePanels.push(self);
23465			}
23466
23467			return state;
23468		},
23469
23470		/**
23471		 * Hides the current float panel.
23472		 *
23473		 * @method hide
23474		 * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
23475		 */
23476		hide: function() {
23477			removeVisiblePanel(this);
23478			addRemove(false, this);
23479
23480			return this._super();
23481		},
23482
23483		/**
23484		 * Hide all visible float panels with he autohide setting enabled. This is for
23485		 * manually hiding floating menus or panels.
23486		 *
23487		 * @method hideAll
23488		 */
23489		hideAll: function() {
23490			FloatPanel.hideAll();
23491		},
23492
23493		/**
23494		 * Closes the float panel. This will remove the float panel from page and fire the close event.
23495		 *
23496		 * @method close
23497		 */
23498		close: function() {
23499			var self = this;
23500
23501			if (!self.fire('close').isDefaultPrevented()) {
23502				self.remove();
23503				addRemove(false, self);
23504			}
23505
23506			return self;
23507		},
23508
23509		/**
23510		 * Removes the float panel from page.
23511		 *
23512		 * @method remove
23513		 */
23514		remove: function() {
23515			removeVisiblePanel(this);
23516			this._super();
23517		},
23518
23519		postRender: function() {
23520			var self = this;
23521
23522			if (self.settings.bodyRole) {
23523				this.getEl('body').setAttribute('role', self.settings.bodyRole);
23524			}
23525
23526			return self._super();
23527		}
23528	});
23529
23530	/**
23531	 * Hide all visible float panels with he autohide setting enabled. This is for
23532	 * manually hiding floating menus or panels.
23533	 *
23534	 * @static
23535	 * @method hideAll
23536	 */
23537	FloatPanel.hideAll = function() {
23538		var i = visiblePanels.length;
23539
23540		while (i--) {
23541			var panel = visiblePanels[i];
23542
23543			if (panel && panel.settings.autohide) {
23544				panel.hide();
23545				visiblePanels.splice(i, 1);
23546			}
23547		}
23548	};
23549
23550	function removeVisiblePanel(panel) {
23551		var i;
23552
23553		i = visiblePanels.length;
23554		while (i--) {
23555			if (visiblePanels[i] === panel) {
23556				visiblePanels.splice(i, 1);
23557			}
23558		}
23559
23560		i = zOrder.length;
23561		while (i--) {
23562			if (zOrder[i] === panel) {
23563				zOrder.splice(i, 1);
23564			}
23565		}
23566	}
23567
23568	return FloatPanel;
23569});
23570
23571// Included from: js/tinymce/classes/ui/Window.js
23572
23573/**
23574 * Window.js
23575 *
23576 * Copyright, Moxiecode Systems AB
23577 * Released under LGPL License.
23578 *
23579 * License: http://www.tinymce.com/license
23580 * Contributing: http://www.tinymce.com/contributing
23581 */
23582
23583/**
23584 * Creates a new window.
23585 *
23586 * @-x-less Window.less
23587 * @class tinymce.ui.Window
23588 * @extends tinymce.ui.FloatPanel
23589 */
23590define("tinymce/ui/Window", [
23591	"tinymce/ui/FloatPanel",
23592	"tinymce/ui/Panel",
23593	"tinymce/ui/DomUtils",
23594	"tinymce/ui/DragHelper"
23595], function(FloatPanel, Panel, DomUtils, DragHelper) {
23596	"use strict";
23597
23598	var Window = FloatPanel.extend({
23599		modal: true,
23600
23601		Defaults: {
23602			border: 1,
23603			layout: 'flex',
23604			containerCls: 'panel',
23605			role: 'dialog',
23606			callbacks: {
23607				submit: function() {
23608					this.fire('submit', {data: this.toJSON()});
23609				},
23610
23611				close: function() {
23612					this.close();
23613				}
23614			}
23615		},
23616
23617		/**
23618		 * Constructs a instance with the specified settings.
23619		 *
23620		 * @constructor
23621		 * @param {Object} settings Name/value object with settings.
23622		 */
23623		init: function(settings) {
23624			var self = this;
23625
23626			self._super(settings);
23627
23628			if (self.isRtl()) {
23629				self.addClass('rtl');
23630			}
23631
23632			self.addClass('window');
23633			self._fixed = true;
23634
23635			// Create statusbar
23636			if (settings.buttons) {
23637				self.statusbar = new Panel({
23638					layout: 'flex',
23639					border: '1 0 0 0',
23640					spacing: 3,
23641					padding: 10,
23642					align: 'center',
23643					pack: self.isRtl() ? 'start' : 'end',
23644					defaults: {
23645						type: 'button'
23646					},
23647					items: settings.buttons
23648				});
23649
23650				self.statusbar.addClass('foot');
23651				self.statusbar.parent(self);
23652			}
23653
23654			self.on('click', function(e) {
23655				if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
23656					self.close();
23657				}
23658			});
23659
23660			self.on('cancel', function() {
23661				self.close();
23662			});
23663
23664			self.aria('describedby', self.describedBy || self._id + '-none');
23665			self.aria('label', settings.title);
23666			self._fullscreen = false;
23667		},
23668
23669		/**
23670		 * Recalculates the positions of the controls in the current container.
23671		 * This is invoked by the reflow method and shouldn't be called directly.
23672		 *
23673		 * @method recalc
23674		 */
23675		recalc: function() {
23676			var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;
23677
23678			if (self._fullscreen) {
23679				self.layoutRect(DomUtils.getWindowSize());
23680				self.layoutRect().contentH = self.layoutRect().innerH;
23681			}
23682
23683			self._super();
23684
23685			layoutRect = self.layoutRect();
23686
23687			// Resize window based on title width
23688			if (self.settings.title && !self._fullscreen) {
23689				width = layoutRect.headerW;
23690				if (width > layoutRect.w) {
23691					x = layoutRect.x - Math.max(0, width / 2);
23692					self.layoutRect({w: width, x: x});
23693					needsRecalc = true;
23694				}
23695			}
23696
23697			// Resize window based on statusbar width
23698			if (statusbar) {
23699				statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
23700
23701				width = statusbar.layoutRect().minW + layoutRect.deltaW;
23702				if (width > layoutRect.w) {
23703					x = layoutRect.x - Math.max(0, width - layoutRect.w);
23704					self.layoutRect({w: width, x: x});
23705					needsRecalc = true;
23706				}
23707			}
23708
23709			// Recalc body and disable auto resize
23710			if (needsRecalc) {
23711				self.recalc();
23712			}
23713		},
23714
23715		/**
23716		 * Initializes the current controls layout rect.
23717		 * This will be executed by the layout managers to determine the
23718		 * default minWidth/minHeight etc.
23719		 *
23720		 * @method initLayoutRect
23721		 * @return {Object} Layout rect instance.
23722		 */
23723		initLayoutRect: function() {
23724			var self = this, layoutRect = self._super(), deltaH = 0, headEl;
23725
23726			// Reserve vertical space for title
23727			if (self.settings.title && !self._fullscreen) {
23728				headEl = self.getEl('head');
23729
23730				var size = DomUtils.getSize(headEl);
23731
23732				layoutRect.headerW = size.width;
23733				layoutRect.headerH = size.height;
23734
23735				deltaH += layoutRect.headerH;
23736			}
23737
23738			// Reserve vertical space for statusbar
23739			if (self.statusbar) {
23740				deltaH += self.statusbar.layoutRect().h;
23741			}
23742
23743			layoutRect.deltaH += deltaH;
23744			layoutRect.minH += deltaH;
23745			//layoutRect.innerH -= deltaH;
23746			layoutRect.h += deltaH;
23747
23748			var rect = DomUtils.getWindowSize();
23749
23750			layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2);
23751			layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2);
23752
23753			return layoutRect;
23754		},
23755
23756		/**
23757		 * Renders the control as a HTML string.
23758		 *
23759		 * @method renderHtml
23760		 * @return {String} HTML representing the control.
23761		 */
23762		renderHtml: function() {
23763			var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
23764			var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
23765
23766			self.preRender();
23767			layout.preRender(self);
23768
23769			if (settings.title) {
23770				headerHtml = (
23771					'<div id="' + id + '-head" class="' + prefix + 'window-head">' +
23772						'<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
23773						'<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
23774						'<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
23775					'</div>'
23776				);
23777			}
23778
23779			if (settings.url) {
23780				html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
23781			}
23782
23783			if (typeof(html) == "undefined") {
23784				html = layout.renderHtml(self);
23785			}
23786
23787			if (self.statusbar) {
23788				footerHtml = self.statusbar.renderHtml();
23789			}
23790
23791			return (
23792				'<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
23793					'<div class="' + self.classPrefix + 'reset" role="application">' +
23794						headerHtml +
23795						'<div id="' + id + '-body" class="' + self.classes('body') + '">' +
23796							html +
23797						'</div>' +
23798						footerHtml +
23799					'</div>' +
23800				'</div>'
23801			);
23802		},
23803
23804		/**
23805		 * Switches the window fullscreen mode.
23806		 *
23807		 * @method fullscreen
23808		 * @param {Boolean} state True/false state.
23809		 * @return {tinymce.ui.Window} Current window instance.
23810		 */
23811		fullscreen: function(state) {
23812			var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
23813
23814			if (state != self._fullscreen) {
23815				DomUtils.on(window, 'resize', function() {
23816					var time;
23817
23818					if (self._fullscreen) {
23819						// Time the layout time if it's to slow use a timeout to not hog the CPU
23820						if (!slowRendering) {
23821							time = new Date().getTime();
23822
23823							var rect = DomUtils.getWindowSize();
23824							self.moveTo(0, 0).resizeTo(rect.w, rect.h);
23825
23826							if ((new Date().getTime()) - time > 50) {
23827								slowRendering = true;
23828							}
23829						} else {
23830							if (!self._timer) {
23831								self._timer = setTimeout(function() {
23832									var rect = DomUtils.getWindowSize();
23833									self.moveTo(0, 0).resizeTo(rect.w, rect.h);
23834
23835									self._timer = 0;
23836								}, 50);
23837							}
23838						}
23839					}
23840				});
23841
23842				layoutRect = self.layoutRect();
23843				self._fullscreen = state;
23844
23845				if (!state) {
23846					self._borderBox = self.parseBox(self.settings.border);
23847					self.getEl('head').style.display = '';
23848					layoutRect.deltaH += layoutRect.headerH;
23849					DomUtils.removeClass(documentElement, prefix + 'fullscreen');
23850					DomUtils.removeClass(document.body, prefix + 'fullscreen');
23851					self.removeClass('fullscreen');
23852					self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
23853				} else {
23854					self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
23855
23856					self._borderBox = self.parseBox('0');
23857					self.getEl('head').style.display = 'none';
23858					layoutRect.deltaH -= layoutRect.headerH + 2;
23859					DomUtils.addClass(documentElement, prefix + 'fullscreen');
23860					DomUtils.addClass(document.body, prefix + 'fullscreen');
23861					self.addClass('fullscreen');
23862
23863					var rect = DomUtils.getWindowSize();
23864					self.moveTo(0, 0).resizeTo(rect.w, rect.h);
23865				}
23866			}
23867
23868			return self.reflow();
23869		},
23870
23871		/**
23872		 * Called after the control has been rendered.
23873		 *
23874		 * @method postRender
23875		 */
23876		postRender: function() {
23877			var self = this, startPos;
23878
23879			setTimeout(function() {
23880				self.addClass('in');
23881			}, 0);
23882
23883			self._super();
23884
23885			if (self.statusbar) {
23886				self.statusbar.postRender();
23887			}
23888
23889			self.focus();
23890
23891			this.dragHelper = new DragHelper(self._id + '-dragh', {
23892				start: function() {
23893					startPos = {
23894						x: self.layoutRect().x,
23895						y: self.layoutRect().y
23896					};
23897				},
23898
23899				drag: function(e) {
23900					self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
23901				}
23902			});
23903
23904			self.on('submit', function(e) {
23905				if (!e.isDefaultPrevented()) {
23906					self.close();
23907				}
23908			});
23909		},
23910
23911		/**
23912		 * Fires a submit event with the serialized form.
23913		 *
23914		 * @method submit
23915		 * @return {Object} Event arguments object.
23916		 */
23917		submit: function() {
23918			return this.fire('submit', {data: this.toJSON()});
23919		},
23920
23921		/**
23922		 * Removes the current control from DOM and from UI collections.
23923		 *
23924		 * @method remove
23925		 * @return {tinymce.ui.Control} Current control instance.
23926		 */
23927		remove: function() {
23928			var self = this, prefix = self.classPrefix;
23929
23930			self.dragHelper.destroy();
23931			self._super();
23932
23933			if (self.statusbar) {
23934				this.statusbar.remove();
23935			}
23936
23937			if (self._fullscreen) {
23938				DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
23939				DomUtils.removeClass(document.body, prefix + 'fullscreen');
23940			}
23941		},
23942
23943		/**
23944		 * Returns the contentWindow object of the iframe if it exists.
23945		 *
23946		 * @method getContentWindow
23947		 * @return {Window} window object or null.
23948		 */
23949		getContentWindow: function() {
23950			var ifr = this.getEl().getElementsByTagName('iframe')[0];
23951			return ifr ? ifr.contentWindow : null;
23952		}
23953	});
23954
23955	return Window;
23956});
23957
23958// Included from: js/tinymce/classes/ui/MessageBox.js
23959
23960/**
23961 * MessageBox.js
23962 *
23963 * Copyright, Moxiecode Systems AB
23964 * Released under LGPL License.
23965 *
23966 * License: http://www.tinymce.com/license
23967 * Contributing: http://www.tinymce.com/contributing
23968 */
23969
23970/**
23971 * This class is used to create MessageBoxes like alerts/confirms etc.
23972 *
23973 * @class tinymce.ui.MessageBox
23974 * @extends tinymce.ui.Window
23975 */
23976define("tinymce/ui/MessageBox", [
23977	"tinymce/ui/Window"
23978], function(Window) {
23979	"use strict";
23980
23981	var MessageBox = Window.extend({
23982		/**
23983		 * Constructs a instance with the specified settings.
23984		 *
23985		 * @constructor
23986		 * @param {Object} settings Name/value object with settings.
23987		 */
23988		init: function(settings) {
23989			settings = {
23990				border: 1,
23991				padding: 20,
23992				layout: 'flex',
23993				pack: "center",
23994				align: "center",
23995				containerCls: 'panel',
23996				autoScroll: true,
23997				buttons: {type: "button", text: "Ok", action: "ok"},
23998				items: {
23999					type: "label",
24000					multiline: true,
24001					maxWidth: 500,
24002					maxHeight: 200
24003				}
24004			};
24005
24006			this._super(settings);
24007		},
24008
24009		Statics: {
24010			/**
24011			 * Ok buttons constant.
24012			 *
24013			 * @static
24014			 * @final
24015			 * @field {Number} OK
24016			 */
24017			OK: 1,
24018
24019			/**
24020			 * Ok/cancel buttons constant.
24021			 *
24022			 * @static
24023			 * @final
24024			 * @field {Number} OK_CANCEL
24025			 */
24026			OK_CANCEL: 2,
24027
24028			/**
24029			 * yes/no buttons constant.
24030			 *
24031			 * @static
24032			 * @final
24033			 * @field {Number} YES_NO
24034			 */
24035			YES_NO: 3,
24036
24037			/**
24038			 * yes/no/cancel buttons constant.
24039			 *
24040			 * @static
24041			 * @final
24042			 * @field {Number} YES_NO_CANCEL
24043			 */
24044			YES_NO_CANCEL: 4,
24045
24046			/**
24047			 * Constructs a new message box and renders it to the body element.
24048			 *
24049			 * @static
24050			 * @method msgBox
24051			 * @param {Object} settings Name/value object with settings.
24052			 */
24053			msgBox: function(settings) {
24054				var buttons, callback = settings.callback || function() {};
24055
24056				function createButton(text, status, primary) {
24057					return {
24058						type: "button",
24059						text: text,
24060						subtype: primary ? 'primary' : '',
24061						onClick: function(e) {
24062							e.control.parents()[1].close();
24063							callback(status);
24064						}
24065					};
24066				}
24067
24068				switch (settings.buttons) {
24069					case MessageBox.OK_CANCEL:
24070						buttons = [
24071							createButton('Ok', true, true),
24072							createButton('Cancel', false)
24073						];
24074						break;
24075
24076					case MessageBox.YES_NO:
24077					case MessageBox.YES_NO_CANCEL:
24078						buttons = [
24079							createButton('Yes', 1, true),
24080							createButton('No', 0)
24081						];
24082
24083						if (settings.buttons == MessageBox.YES_NO_CANCEL) {
24084							buttons.push(createButton('Cancel', -1));
24085						}
24086						break;
24087
24088					default:
24089						buttons = [
24090							createButton('Ok', true, true)
24091						];
24092						break;
24093				}
24094
24095				return new Window({
24096					padding: 20,
24097					x: settings.x,
24098					y: settings.y,
24099					minWidth: 300,
24100					minHeight: 100,
24101					layout: "flex",
24102					pack: "center",
24103					align: "center",
24104					buttons: buttons,
24105					title: settings.title,
24106					role: 'alertdialog',
24107					items: {
24108						type: "label",
24109						multiline: true,
24110						maxWidth: 500,
24111						maxHeight: 200,
24112						text: settings.text
24113					},
24114					onPostRender: function() {
24115						this.aria('describedby', this.items()[0]._id);
24116					},
24117					onClose: settings.onClose,
24118					onCancel: function() {
24119						callback(false);
24120					}
24121				}).renderTo(document.body).reflow();
24122			},
24123
24124			/**
24125			 * Creates a new alert dialog.
24126			 *
24127			 * @method alert
24128			 * @param {Object} settings Settings for the alert dialog.
24129			 * @param {function} [callback] Callback to execute when the user makes a choice.
24130			 */
24131			alert: function(settings, callback) {
24132				if (typeof(settings) == "string") {
24133					settings = {text: settings};
24134				}
24135
24136				settings.callback = callback;
24137				return MessageBox.msgBox(settings);
24138			},
24139
24140			/**
24141			 * Creates a new confirm dialog.
24142			 *
24143			 * @method confirm
24144			 * @param {Object} settings Settings for the confirm dialog.
24145			 * @param {function} [callback] Callback to execute when the user makes a choice.
24146			 */
24147			confirm: function(settings, callback) {
24148				if (typeof(settings) == "string") {
24149					settings = {text: settings};
24150				}
24151
24152				settings.callback = callback;
24153				settings.buttons = MessageBox.OK_CANCEL;
24154
24155				return MessageBox.msgBox(settings);
24156			}
24157		}
24158	});
24159
24160	return MessageBox;
24161});
24162
24163// Included from: js/tinymce/classes/WindowManager.js
24164
24165/**
24166 * WindowManager.js
24167 *
24168 * Copyright, Moxiecode Systems AB
24169 * Released under LGPL License.
24170 *
24171 * License: http://www.tinymce.com/license
24172 * Contributing: http://www.tinymce.com/contributing
24173 */
24174
24175/**
24176 * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
24177 *
24178 * @class tinymce.WindowManager
24179 * @example
24180 * // Opens a new dialog with the file.htm file and the size 320x240
24181 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
24182 * tinymce.activeEditor.windowManager.open({
24183 *    url: 'file.htm',
24184 *    width: 320,
24185 *    height: 240
24186 * }, {
24187 *    custom_param: 1
24188 * });
24189 *
24190 * // Displays an alert box using the active editors window manager instance
24191 * tinymce.activeEditor.windowManager.alert('Hello world!');
24192 *
24193 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
24194 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
24195 *    if (s)
24196 *       tinymce.activeEditor.windowManager.alert("Ok");
24197 *    else
24198 *       tinymce.activeEditor.windowManager.alert("Cancel");
24199 * });
24200 */
24201define("tinymce/WindowManager", [
24202	"tinymce/ui/Window",
24203	"tinymce/ui/MessageBox"
24204], function(Window, MessageBox) {
24205	return function(editor) {
24206		var self = this, windows = [];
24207
24208		function getTopMostWindow() {
24209			if (windows.length) {
24210				return windows[windows.length - 1];
24211			}
24212		}
24213
24214		self.windows = windows;
24215
24216		editor.on('remove', function() {
24217			var i = windows.length;
24218
24219			while (i--) {
24220				windows[i].close();
24221			}
24222		});
24223
24224		/**
24225		 * Opens a new window.
24226		 *
24227		 * @method open
24228		 * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
24229		 * @option {String} title Window title.
24230		 * @option {String} file URL of the file to open in the window.
24231		 * @option {Number} width Width in pixels.
24232		 * @option {Number} height Height in pixels.
24233		 * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
24234		 * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
24235		 * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
24236		 * larger than the popup size specified).
24237		 */
24238		self.open = function(args, params) {
24239			var win;
24240
24241			editor.editorManager.setActive(editor);
24242
24243			args.title = args.title || ' ';
24244
24245			// Handle URL
24246			args.url = args.url || args.file; // Legacy
24247			if (args.url) {
24248				args.width = parseInt(args.width || 320, 10);
24249				args.height = parseInt(args.height || 240, 10);
24250			}
24251
24252			// Handle body
24253			if (args.body) {
24254				args.items = {
24255					defaults: args.defaults,
24256					type: args.bodyType || 'form',
24257					items: args.body
24258				};
24259			}
24260
24261			if (!args.url && !args.buttons) {
24262				args.buttons = [
24263					{text: 'Ok', subtype: 'primary', onclick: function() {
24264						win.find('form')[0].submit();
24265					}},
24266
24267					{text: 'Cancel', onclick: function() {
24268						win.close();
24269					}}
24270				];
24271			}
24272
24273			win = new Window(args);
24274			windows.push(win);
24275
24276			win.on('close', function() {
24277				var i = windows.length;
24278
24279				while (i--) {
24280					if (windows[i] === win) {
24281						windows.splice(i, 1);
24282					}
24283				}
24284
24285				if (!windows.length) {
24286					editor.focus();
24287				}
24288			});
24289
24290			// Handle data
24291			if (args.data) {
24292				win.on('postRender', function() {
24293					this.find('*').each(function(ctrl) {
24294						var name = ctrl.name();
24295
24296						if (name in args.data) {
24297							ctrl.value(args.data[name]);
24298						}
24299					});
24300				});
24301			}
24302
24303			// store args and parameters
24304			win.features = args || {};
24305			win.params = params || {};
24306
24307			// Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
24308			if (windows.length === 1) {
24309				editor.nodeChanged();
24310			}
24311
24312			return win.renderTo().reflow();
24313		};
24314
24315		/**
24316		 * Creates a alert dialog. Please don't use the blocking behavior of this
24317		 * native version use the callback method instead then it can be extended.
24318		 *
24319		 * @method alert
24320		 * @param {String} message Text to display in the new alert dialog.
24321		 * @param {function} callback Callback function to be executed after the user has selected ok.
24322		 * @param {Object} scope Optional scope to execute the callback in.
24323		 * @example
24324		 * // Displays an alert box using the active editors window manager instance
24325		 * tinymce.activeEditor.windowManager.alert('Hello world!');
24326		 */
24327		self.alert = function(message, callback, scope) {
24328			MessageBox.alert(message, function() {
24329				if (callback) {
24330					callback.call(scope || this);
24331				} else {
24332					editor.focus();
24333				}
24334			});
24335		};
24336
24337		/**
24338		 * Creates a confirm dialog. Please don't use the blocking behavior of this
24339		 * native version use the callback method instead then it can be extended.
24340		 *
24341		 * @method confirm
24342		 * @param {String} messageText to display in the new confirm dialog.
24343		 * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
24344		 * @param {Object} scope Optional scope to execute the callback in.
24345		 * @example
24346		 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
24347		 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
24348		 *    if (s)
24349		 *       tinymce.activeEditor.windowManager.alert("Ok");
24350		 *    else
24351		 *       tinymce.activeEditor.windowManager.alert("Cancel");
24352		 * });
24353		 */
24354		self.confirm = function(message, callback, scope) {
24355			MessageBox.confirm(message, function(state) {
24356				callback.call(scope || this, state);
24357			});
24358		};
24359
24360		/**
24361		 * Closes the top most window.
24362		 *
24363		 * @method close
24364		 */
24365		self.close = function() {
24366			if (getTopMostWindow()) {
24367				getTopMostWindow().close();
24368			}
24369		};
24370
24371		/**
24372		 * Returns the params of the last window open call. This can be used in iframe based
24373		 * dialog to get params passed from the tinymce plugin.
24374		 *
24375		 * @example
24376		 * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
24377		 *
24378		 * @method getParams
24379		 * @return {Object} Name/value object with parameters passed from windowManager.open call.
24380		 */
24381		self.getParams = function() {
24382			return getTopMostWindow() ? getTopMostWindow().params : null;
24383		};
24384
24385		/**
24386		 * Sets the params of the last opened window.
24387		 *
24388		 * @method setParams
24389		 * @param {Object} params Params object to set for the last opened window.
24390		 */
24391		self.setParams = function(params) {
24392			if (getTopMostWindow()) {
24393				getTopMostWindow().params = params;
24394			}
24395		};
24396
24397		/**
24398		 * Returns the currently opened window objects.
24399		 *
24400		 * @method getWindows
24401		 * @return {Array} Array of the currently opened windows.
24402		 */
24403		self.getWindows = function() {
24404			return windows;
24405		};
24406	};
24407});
24408
24409// Included from: js/tinymce/classes/util/Quirks.js
24410
24411/**
24412 * Quirks.js
24413 *
24414 * Copyright, Moxiecode Systems AB
24415 * Released under LGPL License.
24416 *
24417 * License: http://www.tinymce.com/license
24418 * Contributing: http://www.tinymce.com/contributing
24419 *
24420 * @ignore-file
24421 */
24422
24423/**
24424 * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
24425 *
24426 * @class tinymce.util.Quirks
24427 */
24428define("tinymce/util/Quirks", [
24429	"tinymce/util/VK",
24430	"tinymce/dom/RangeUtils",
24431	"tinymce/html/Node",
24432	"tinymce/html/Entities",
24433	"tinymce/Env",
24434	"tinymce/util/Tools"
24435], function(VK, RangeUtils, Node, Entities, Env, Tools) {
24436	return function(editor) {
24437		var each = Tools.each;
24438		var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
24439			settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
24440		var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
24441
24442		/**
24443		 * Executes a command with a specific state this can be to enable/disable browser editing features.
24444		 */
24445		function setEditorCommandState(cmd, state) {
24446			try {
24447				editor.getDoc().execCommand(cmd, false, state);
24448			} catch (ex) {
24449				// Ignore
24450			}
24451		}
24452
24453		/**
24454		 * Returns current IE document mode.
24455		 */
24456		function getDocumentMode() {
24457			var documentMode = editor.getDoc().documentMode;
24458
24459			return documentMode ? documentMode : 6;
24460		}
24461
24462		/**
24463		 * Returns true/false if the event is prevented or not.
24464		 *
24465		 * @private
24466		 * @param {Event} e Event object.
24467		 * @return {Boolean} true/false if the event is prevented or not.
24468		 */
24469		function isDefaultPrevented(e) {
24470			return e.isDefaultPrevented();
24471		}
24472
24473		/**
24474		 * Fixes a WebKit bug when deleting contents using backspace or delete key.
24475		 * WebKit will produce a span element if you delete across two block elements.
24476		 *
24477		 * Example:
24478		 * <h1>a</h1><p>|b</p>
24479		 *
24480		 * Will produce this on backspace:
24481		 * <h1>a<span style="<all runtime styles>">b</span></p>
24482		 *
24483		 * This fixes the backspace to produce:
24484		 * <h1>a|b</p>
24485		 *
24486		 * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
24487		 *
24488		 * This fixes the following delete scenarios:
24489		 *  1. Delete by pressing backspace key.
24490		 *  2. Delete by pressing delete key.
24491		 *  3. Delete by pressing backspace key with ctrl/cmd (Word delete).
24492		 *  4. Delete by pressing delete key with ctrl/cmd (Word delete).
24493		 *  5. Delete by drag/dropping contents inside the editor.
24494		 *  6. Delete by using Cut Ctrl+X/Cmd+X.
24495		 *  7. Delete by selecting contents and writing a character.'
24496		 *
24497		 * This code is a ugly hack since writing full custom delete logic for just this bug
24498		 * fix seemed like a huge task. I hope we can remove this before the year 2030.
24499		 */
24500		function cleanupStylesWhenDeleting() {
24501			var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,';
24502			var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
24503
24504			// Add mini polyfill for older WebKits
24505			// TODO: Remove this when old Safari versions gets updated
24506			if (!MutationObserver) {
24507				olderWebKit = true;
24508
24509				MutationObserver = function() {
24510					var records = [], target;
24511
24512					function nodeInsert(e) {
24513						var target = e.relatedNode || e.target;
24514						records.push({target: target, addedNodes: [target]});
24515					}
24516
24517					function attrModified(e) {
24518						var target = e.relatedNode || e.target;
24519						records.push({target: target, attributeName: e.attrName});
24520					}
24521
24522					this.observe = function(node) {
24523						target = node;
24524						target.addEventListener('DOMSubtreeModified', nodeInsert, false);
24525						target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
24526						target.addEventListener('DOMNodeInserted', nodeInsert, false);
24527						target.addEventListener('DOMAttrModified', attrModified, false);
24528					};
24529
24530					this.disconnect = function() {
24531						target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
24532						target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
24533						target.removeEventListener('DOMNodeInserted', nodeInsert, false);
24534						target.removeEventListener('DOMAttrModified', attrModified, false);
24535					};
24536
24537					this.takeRecords = function() {
24538						return records;
24539					};
24540				};
24541			}
24542
24543			function customDelete(isForward) {
24544				var mutationObserver = new MutationObserver(function() {});
24545
24546				Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
24547					// Mark existing spans
24548					if (elm.tagName == 'SPAN') {
24549						elm.setAttribute('mce-data-marked', 1);
24550					}
24551
24552					// Make sure all elements has a data-mce-style attribute
24553					if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
24554						editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style'));
24555					}
24556				});
24557
24558				// Observe added nodes and style attribute changes
24559				mutationObserver.observe(editor.getDoc(), {
24560					childList: true,
24561					attributes: true,
24562					subtree: true,
24563					attributeFilter: ['style']
24564				});
24565
24566				editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
24567
24568				var rng = editor.selection.getRng();
24569				var caretElement = rng.startContainer.parentNode;
24570
24571				Tools.each(mutationObserver.takeRecords(), function(record) {
24572					if (!dom.isChildOf(record.target, editor.getBody())) {
24573						return;
24574					}
24575
24576					// Restore style attribute to previous value
24577					if (record.attributeName == "style") {
24578						var oldValue = record.target.getAttribute('data-mce-style');
24579
24580						if (oldValue) {
24581							record.target.setAttribute("style", oldValue);
24582						} else {
24583							record.target.removeAttribute("style");
24584						}
24585					}
24586
24587					// Remove all spans that isn't maked and retain selection
24588					Tools.each(record.addedNodes, function(node) {
24589						if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
24590							var offset, container;
24591
24592							if (node == caretElement) {
24593								offset = rng.startOffset;
24594								container = node.firstChild;
24595							}
24596
24597							dom.remove(node, true);
24598
24599							if (container) {
24600								rng.setStart(container, offset);
24601								rng.setEnd(container, offset);
24602								editor.selection.setRng(rng);
24603							}
24604						}
24605					});
24606				});
24607
24608				mutationObserver.disconnect();
24609
24610				// Remove any left over marks
24611				Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
24612					span.removeAttribute('mce-data-marked');
24613				});
24614			}
24615
24616			editor.on('keydown', function(e) {
24617				var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e);
24618
24619				if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
24620					var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
24621
24622					// Ignore non meta delete in the where there is text before/after the caret
24623					if (!isMeta && rng.collapsed && container.nodeType == 3) {
24624						if (isForward ? offset < container.data.length : offset > 0) {
24625							return;
24626						}
24627					}
24628
24629					e.preventDefault();
24630
24631					if (isMeta) {
24632						editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word");
24633					}
24634
24635					customDelete(isForward);
24636				}
24637			});
24638
24639			editor.on('keypress', function(e) {
24640				if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) {
24641					e.preventDefault();
24642					customDelete(true);
24643					editor.selection.setContent(String.fromCharCode(e.charCode));
24644				}
24645			});
24646
24647			editor.addCommand('Delete', function() {
24648				customDelete();
24649			});
24650
24651			editor.addCommand('ForwardDelete', function() {
24652				customDelete(true);
24653			});
24654
24655			// Older WebKits doesn't properly handle the clipboard so we can't add the rest
24656			if (olderWebKit) {
24657				return;
24658			}
24659
24660			editor.on('dragstart', function(e) {
24661				var selectionHtml;
24662
24663				if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
24664					selection.select(e.target);
24665				}
24666
24667				dragStartRng = selection.getRng();
24668				selectionHtml = editor.selection.getContent();
24669
24670				// Safari doesn't support custom dataTransfer items so we can only use URL and Text
24671				if (selectionHtml.length > 0) {
24672					e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(selectionHtml));
24673				}
24674			});
24675
24676			editor.on('drop', function(e) {
24677				if (!isDefaultPrevented(e)) {
24678					var internalContent = e.dataTransfer.getData('URL');
24679
24680					if (!internalContent || internalContent.indexOf(urlPrefix) == -1 || !doc.caretRangeFromPoint) {
24681						return;
24682					}
24683
24684					internalContent = unescape(internalContent.substr(urlPrefix.length));
24685					if (doc.caretRangeFromPoint) {
24686						e.preventDefault();
24687
24688						// Safari has a weird issue where drag/dropping images sometimes
24689						// produces a green plus icon. When this happens the caretRangeFromPoint
24690						// will return "null" even though the x, y coordinate is correct.
24691						// But if we detach the insert from the drop event we will get a proper range
24692						window.setTimeout(function() {
24693							var pointRng = doc.caretRangeFromPoint(e.x, e.y);
24694
24695							if (dragStartRng) {
24696								selection.setRng(dragStartRng);
24697								dragStartRng = null;
24698							}
24699
24700							customDelete();
24701
24702							selection.setRng(pointRng);
24703							editor.insertContent(internalContent);
24704						}, 0);
24705					}
24706
24707				}
24708			});
24709
24710			editor.on('cut', function(e) {
24711				if (!isDefaultPrevented(e) && e.clipboardData) {
24712					e.preventDefault();
24713					e.clipboardData.clearData();
24714					e.clipboardData.setData('text/html', editor.selection.getContent());
24715					e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
24716					customDelete(true);
24717				}
24718			});
24719		}
24720
24721		/**
24722		 * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
24723		 *
24724		 * For example:
24725		 * <p><b>|</b></p>
24726		 *
24727		 * Or:
24728		 * <h1>|</h1>
24729		 *
24730		 * Or:
24731		 * [<h1></h1>]
24732		 */
24733		function emptyEditorWhenDeleting() {
24734			function serializeRng(rng) {
24735				var body = dom.create("body");
24736				var contents = rng.cloneContents();
24737				body.appendChild(contents);
24738				return selection.serializer.serialize(body, {format: 'html'});
24739			}
24740
24741			function allContentsSelected(rng) {
24742				if (!rng.setStart) {
24743					if (rng.item) {
24744						return false;
24745					}
24746
24747					var bodyRng = rng.duplicate();
24748					bodyRng.moveToElementText(editor.getBody());
24749					return RangeUtils.compareRanges(rng, bodyRng);
24750				}
24751
24752				var selection = serializeRng(rng);
24753
24754				var allRng = dom.createRng();
24755				allRng.selectNode(editor.getBody());
24756
24757				var allSelection = serializeRng(allRng);
24758				return selection === allSelection;
24759			}
24760
24761			editor.on('keydown', function(e) {
24762				var keyCode = e.keyCode, isCollapsed, body;
24763
24764				// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
24765				if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
24766					isCollapsed = editor.selection.isCollapsed();
24767					body = editor.getBody();
24768
24769					// Selection is collapsed but the editor isn't empty
24770					if (isCollapsed && !dom.isEmpty(body)) {
24771						return;
24772					}
24773
24774					// Selection isn't collapsed but not all the contents is selected
24775					if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
24776						return;
24777					}
24778
24779					// Manually empty the editor
24780					e.preventDefault();
24781					editor.setContent('');
24782
24783					if (body.firstChild && dom.isBlock(body.firstChild)) {
24784						editor.selection.setCursorLocation(body.firstChild, 0);
24785					} else {
24786						editor.selection.setCursorLocation(body, 0);
24787					}
24788
24789					editor.nodeChanged();
24790				}
24791			});
24792		}
24793
24794		/**
24795		 * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
24796		 * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
24797		 * This selects the whole body so that backspace/delete logic will delete everything
24798		 */
24799		function selectAll() {
24800			editor.shortcuts.add('ctrl+a', null, 'SelectAll');
24801		}
24802
24803		/**
24804		 * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
24805		 * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
24806		 *
24807		 * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
24808		 * you enter a character into the editor.
24809		 *
24810		 * It also happens when the first focus in made to the body.
24811		 *
24812		 * See: https://bugs.webkit.org/show_bug.cgi?id=83566
24813		 */
24814		function inputMethodFocus() {
24815			if (!editor.settings.content_editable) {
24816				// Case 1 IME doesn't initialize if you focus the document
24817				dom.bind(editor.getDoc(), 'focusin', function() {
24818					selection.setRng(selection.getRng());
24819				});
24820
24821				// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
24822				// Needs to be both down/up due to weird rendering bug on Chrome Windows
24823				dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) {
24824					if (e.target == editor.getDoc().documentElement) {
24825						editor.getBody().focus();
24826
24827						if (e.type == 'mousedown') {
24828							// Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret
24829							selection.placeCaretAt(e.clientX, e.clientY);
24830						} else {
24831							selection.setRng(selection.getRng());
24832						}
24833					}
24834				});
24835			}
24836		}
24837
24838		/**
24839		 * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
24840		 * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
24841		 * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
24842		 * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
24843		 * browsers.
24844		 *
24845		 * It also fixes a bug on Firefox where it's impossible to delete HR elements.
24846		 */
24847		function removeHrOnBackspace() {
24848			editor.on('keydown', function(e) {
24849				if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
24850					// Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow
24851					if (!editor.getBody().getElementsByTagName('hr').length) {
24852						return;
24853					}
24854
24855					if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
24856						var node = selection.getNode();
24857						var previousSibling = node.previousSibling;
24858
24859						if (node.nodeName == 'HR') {
24860							dom.remove(node);
24861							e.preventDefault();
24862							return;
24863						}
24864
24865						if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
24866							dom.remove(previousSibling);
24867							e.preventDefault();
24868						}
24869					}
24870				}
24871			});
24872		}
24873
24874		/**
24875		 * Firefox 3.x has an issue where the body element won't get proper focus if you click out
24876		 * side it's rectangle.
24877		 */
24878		function focusBody() {
24879			// Fix for a focus bug in FF 3.x where the body element
24880			// wouldn't get proper focus if the user clicked on the HTML element
24881			if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
24882				editor.on('mousedown', function(e) {
24883					if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
24884						var body = editor.getBody();
24885
24886						// Blur the body it's focused but not correctly focused
24887						body.blur();
24888
24889						// Refocus the body after a little while
24890						setTimeout(function() {
24891							body.focus();
24892						}, 0);
24893					}
24894				});
24895			}
24896		}
24897
24898		/**
24899		 * WebKit has a bug where it isn't possible to select image, hr or anchor elements
24900		 * by clicking on them so we need to fake that.
24901		 */
24902		function selectControlElements() {
24903			editor.on('click', function(e) {
24904				var target = e.target;
24905
24906				// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
24907				// WebKit can't even do simple things like selecting an image
24908				// Needs to be the setBaseAndExtend or it will fail to select floated images
24909				if (/^(IMG|HR)$/.test(target.nodeName)) {
24910					e.preventDefault();
24911					selection.getSel().setBaseAndExtent(target, 0, target, 1);
24912					editor.nodeChanged();
24913				}
24914
24915				if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) {
24916					e.preventDefault();
24917					selection.select(target);
24918				}
24919			});
24920		}
24921
24922		/**
24923		 * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
24924		 *
24925		 * Fixes do backspace/delete on this:
24926		 * <p>bla[ck</p><p style="color:red">r]ed</p>
24927		 *
24928		 * Would become:
24929		 * <p>bla|ed</p>
24930		 *
24931		 * Instead of:
24932		 * <p style="color:red">bla|ed</p>
24933		 */
24934		function removeStylesWhenDeletingAcrossBlockElements() {
24935			function getAttributeApplyFunction() {
24936				var template = dom.getAttribs(selection.getStart().cloneNode(false));
24937
24938				return function() {
24939					var target = selection.getStart();
24940
24941					if (target !== editor.getBody()) {
24942						dom.setAttrib(target, "style", null);
24943
24944						each(template, function(attr) {
24945							target.setAttributeNode(attr.cloneNode(true));
24946						});
24947					}
24948				};
24949			}
24950
24951			function isSelectionAcrossElements() {
24952				return !selection.isCollapsed() &&
24953					dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
24954			}
24955
24956			editor.on('keypress', function(e) {
24957				var applyAttributes;
24958
24959				if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
24960					applyAttributes = getAttributeApplyFunction();
24961					editor.getDoc().execCommand('delete', false, null);
24962					applyAttributes();
24963					e.preventDefault();
24964					return false;
24965				}
24966			});
24967
24968			dom.bind(editor.getDoc(), 'cut', function(e) {
24969				var applyAttributes;
24970
24971				if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
24972					applyAttributes = getAttributeApplyFunction();
24973
24974					setTimeout(function() {
24975						applyAttributes();
24976					}, 0);
24977				}
24978			});
24979		}
24980
24981		/**
24982		 * Screen readers on IE needs to have the role application set on the body.
24983		 */
24984		function ensureBodyHasRoleApplication() {
24985			document.body.setAttribute("role", "application");
24986		}
24987
24988		/**
24989		 * Backspacing into a table behaves differently depending upon browser type.
24990		 * Therefore, disable Backspace when cursor immediately follows a table.
24991		 */
24992		function disableBackspaceIntoATable() {
24993			editor.on('keydown', function(e) {
24994				if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
24995					if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
24996						var previousSibling = selection.getNode().previousSibling;
24997						if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
24998							e.preventDefault();
24999							return false;
25000						}
25001					}
25002				}
25003			});
25004		}
25005
25006		/**
25007		 * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
25008		 * logic adds a \n before the BR so that it will get rendered.
25009		 */
25010		function addNewLinesBeforeBrInPre() {
25011			// IE8+ rendering mode does the right thing with BR in PRE
25012			if (getDocumentMode() > 7) {
25013				return;
25014			}
25015
25016			// Enable display: none in area and add a specific class that hides all BR elements in PRE to
25017			// avoid the caret from getting stuck at the BR elements while pressing the right arrow key
25018			setEditorCommandState('RespectVisibilityInDesign', true);
25019			editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
25020			dom.addClass(editor.getBody(), 'mceHideBrInPre');
25021
25022			// Adds a \n before all BR elements in PRE to get them visual
25023			parser.addNodeFilter('pre', function(nodes) {
25024				var i = nodes.length, brNodes, j, brElm, sibling;
25025
25026				while (i--) {
25027					brNodes = nodes[i].getAll('br');
25028					j = brNodes.length;
25029					while (j--) {
25030						brElm = brNodes[j];
25031
25032						// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
25033						sibling = brElm.prev;
25034						if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
25035							sibling.value += '\n';
25036						} else {
25037							brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
25038						}
25039					}
25040				}
25041			});
25042
25043			// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
25044			serializer.addNodeFilter('pre', function(nodes) {
25045				var i = nodes.length, brNodes, j, brElm, sibling;
25046
25047				while (i--) {
25048					brNodes = nodes[i].getAll('br');
25049					j = brNodes.length;
25050					while (j--) {
25051						brElm = brNodes[j];
25052						sibling = brElm.prev;
25053						if (sibling && sibling.type == 3) {
25054							sibling.value = sibling.value.replace(/\r?\n$/, '');
25055						}
25056					}
25057				}
25058			});
25059		}
25060
25061		/**
25062		 * Moves style width/height to attribute width/height when the user resizes an image on IE.
25063		 */
25064		function removePreSerializedStylesWhenSelectingControls() {
25065			dom.bind(editor.getBody(), 'mouseup', function() {
25066				var value, node = selection.getNode();
25067
25068				// Moved styles to attributes on IMG eements
25069				if (node.nodeName == 'IMG') {
25070					// Convert style width to width attribute
25071					if ((value = dom.getStyle(node, 'width'))) {
25072						dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
25073						dom.setStyle(node, 'width', '');
25074					}
25075
25076					// Convert style height to height attribute
25077					if ((value = dom.getStyle(node, 'height'))) {
25078						dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
25079						dom.setStyle(node, 'height', '');
25080					}
25081				}
25082			});
25083		}
25084
25085		/**
25086		 * Removes a blockquote when backspace is pressed at the beginning of it.
25087		 *
25088		 * For example:
25089		 * <blockquote><p>|x</p></blockquote>
25090		 *
25091		 * Becomes:
25092		 * <p>|x</p>
25093		 */
25094		function removeBlockQuoteOnBackSpace() {
25095			// Add block quote deletion handler
25096			editor.on('keydown', function(e) {
25097				var rng, container, offset, root, parent;
25098
25099				if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
25100					return;
25101				}
25102
25103				rng = selection.getRng();
25104				container = rng.startContainer;
25105				offset = rng.startOffset;
25106				root = dom.getRoot();
25107				parent = container;
25108
25109				if (!rng.collapsed || offset !== 0) {
25110					return;
25111				}
25112
25113				while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
25114					parent = parent.parentNode;
25115				}
25116
25117				// Is the cursor at the beginning of a blockquote?
25118				if (parent.tagName === 'BLOCKQUOTE') {
25119					// Remove the blockquote
25120					editor.formatter.toggle('blockquote', null, parent);
25121
25122					// Move the caret to the beginning of container
25123					rng = dom.createRng();
25124					rng.setStart(container, 0);
25125					rng.setEnd(container, 0);
25126					selection.setRng(rng);
25127				}
25128			});
25129		}
25130
25131		/**
25132		 * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
25133		 */
25134		function setGeckoEditingOptions() {
25135			function setOpts() {
25136				editor._refreshContentEditable();
25137
25138				setEditorCommandState("StyleWithCSS", false);
25139				setEditorCommandState("enableInlineTableEditing", false);
25140
25141				if (!settings.object_resizing) {
25142					setEditorCommandState("enableObjectResizing", false);
25143				}
25144			}
25145
25146			if (!settings.readonly) {
25147				editor.on('BeforeExecCommand MouseDown', setOpts);
25148			}
25149		}
25150
25151		/**
25152		 * Fixes a gecko link bug, when a link is placed at the end of block elements there is
25153		 * no way to move the caret behind the link. This fix adds a bogus br element after the link.
25154		 *
25155		 * For example this:
25156		 * <p><b><a href="#">x</a></b></p>
25157		 *
25158		 * Becomes this:
25159		 * <p><b><a href="#">x</a></b><br></p>
25160		 */
25161		function addBrAfterLastLinks() {
25162			function fixLinks() {
25163				each(dom.select('a'), function(node) {
25164					var parentNode = node.parentNode, root = dom.getRoot();
25165
25166					if (parentNode.lastChild === node) {
25167						while (parentNode && !dom.isBlock(parentNode)) {
25168							if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
25169								return;
25170							}
25171
25172							parentNode = parentNode.parentNode;
25173						}
25174
25175						dom.add(parentNode, 'br', {'data-mce-bogus': 1});
25176					}
25177				});
25178			}
25179
25180			editor.on('SetContent ExecCommand', function(e) {
25181				if (e.type == "setcontent" || e.command === 'mceInsertLink') {
25182					fixLinks();
25183				}
25184			});
25185		}
25186
25187		/**
25188		 * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
25189		 * default we want to change that behavior.
25190		 */
25191		function setDefaultBlockType() {
25192			if (settings.forced_root_block) {
25193				editor.on('init', function() {
25194					setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
25195				});
25196			}
25197		}
25198
25199		/**
25200		 * Removes ghost selections from images/tables on Gecko.
25201		 */
25202		function removeGhostSelection() {
25203			editor.on('Undo Redo SetContent', function(e) {
25204				if (!e.initial) {
25205					editor.execCommand('mceRepaint');
25206				}
25207			});
25208		}
25209
25210		/**
25211		 * Deletes the selected image on IE instead of navigating to previous page.
25212		 */
25213		function deleteControlItemOnBackSpace() {
25214			editor.on('keydown', function(e) {
25215				var rng;
25216
25217				if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
25218					rng = editor.getDoc().selection.createRange();
25219					if (rng && rng.item) {
25220						e.preventDefault();
25221						editor.undoManager.beforeChange();
25222						dom.remove(rng.item(0));
25223						editor.undoManager.add();
25224					}
25225				}
25226			});
25227		}
25228
25229		/**
25230		 * IE10 doesn't properly render block elements with the right height until you add contents to them.
25231		 * This fixes that by adding a padding-right to all empty text block elements.
25232		 * See: https://connect.microsoft.com/IE/feedback/details/743881
25233		 */
25234		function renderEmptyBlocksFix() {
25235			var emptyBlocksCSS;
25236
25237			// IE10+
25238			if (getDocumentMode() >= 10) {
25239				emptyBlocksCSS = '';
25240				each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
25241					emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
25242				});
25243
25244				editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
25245			}
25246		}
25247
25248		/**
25249		 * Old IE versions can't retain contents within noscript elements so this logic will store the contents
25250		 * as a attribute and the insert that value as it's raw text when the DOM is serialized.
25251		 */
25252		function keepNoScriptContents() {
25253			if (getDocumentMode() < 9) {
25254				parser.addNodeFilter('noscript', function(nodes) {
25255					var i = nodes.length, node, textNode;
25256
25257					while (i--) {
25258						node = nodes[i];
25259						textNode = node.firstChild;
25260
25261						if (textNode) {
25262							node.attr('data-mce-innertext', textNode.value);
25263						}
25264					}
25265				});
25266
25267				serializer.addNodeFilter('noscript', function(nodes) {
25268					var i = nodes.length, node, textNode, value;
25269
25270					while (i--) {
25271						node = nodes[i];
25272						textNode = nodes[i].firstChild;
25273
25274						if (textNode) {
25275							textNode.value = Entities.decode(textNode.value);
25276						} else {
25277							// Old IE can't retain noscript value so an attribute is used to store it
25278							value = node.attributes.map['data-mce-innertext'];
25279							if (value) {
25280								node.attr('data-mce-innertext', null);
25281								textNode = new Node('#text', 3);
25282								textNode.value = value;
25283								textNode.raw = true;
25284								node.append(textNode);
25285							}
25286						}
25287					}
25288				});
25289			}
25290		}
25291
25292		/**
25293		 * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
25294		 */
25295		function fixCaretSelectionOfDocumentElementOnIe() {
25296			var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
25297
25298			// Return range from point or null if it failed
25299			function rngFromPoint(x, y) {
25300				var rng = body.createTextRange();
25301
25302				try {
25303					rng.moveToPoint(x, y);
25304				} catch (ex) {
25305					// IE sometimes throws and exception, so lets just ignore it
25306					rng = null;
25307				}
25308
25309				return rng;
25310			}
25311
25312			// Fires while the selection is changing
25313			function selectionChange(e) {
25314				var pointRng;
25315
25316				// Check if the button is down or not
25317				if (e.button) {
25318					// Create range from mouse position
25319					pointRng = rngFromPoint(e.x, e.y);
25320
25321					if (pointRng) {
25322						// Check if pointRange is before/after selection then change the endPoint
25323						if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
25324							pointRng.setEndPoint('StartToStart', startRng);
25325						} else {
25326							pointRng.setEndPoint('EndToEnd', startRng);
25327						}
25328
25329						pointRng.select();
25330					}
25331				} else {
25332					endSelection();
25333				}
25334			}
25335
25336			// Removes listeners
25337			function endSelection() {
25338				var rng = doc.selection.createRange();
25339
25340				// If the range is collapsed then use the last start range
25341				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
25342					startRng.select();
25343				}
25344
25345				dom.unbind(doc, 'mouseup', endSelection);
25346				dom.unbind(doc, 'mousemove', selectionChange);
25347				startRng = started = 0;
25348			}
25349
25350			// Make HTML element unselectable since we are going to handle selection by hand
25351			doc.documentElement.unselectable = true;
25352
25353			// Detect when user selects outside BODY
25354			dom.bind(doc, 'mousedown contextmenu', function(e) {
25355				if (e.target.nodeName === 'HTML') {
25356					if (started) {
25357						endSelection();
25358					}
25359
25360					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
25361					htmlElm = doc.documentElement;
25362					if (htmlElm.scrollHeight > htmlElm.clientHeight) {
25363						return;
25364					}
25365
25366					started = 1;
25367					// Setup start position
25368					startRng = rngFromPoint(e.x, e.y);
25369					if (startRng) {
25370						// Listen for selection change events
25371						dom.bind(doc, 'mouseup', endSelection);
25372						dom.bind(doc, 'mousemove', selectionChange);
25373
25374						dom.getRoot().focus();
25375						startRng.select();
25376					}
25377				}
25378			});
25379		}
25380
25381		/**
25382		 * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
25383		 * this fix will lean the caret right into the closest inline element.
25384		 */
25385		function normalizeSelection() {
25386			// 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
25387			editor.on('keyup focusin mouseup', function(e) {
25388				if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
25389					selection.normalize();
25390				}
25391			}, true);
25392		}
25393
25394		/**
25395		 * Forces Gecko to render a broken image icon if it fails to load an image.
25396		 */
25397		function showBrokenImageIcon() {
25398			editor.contentStyles.push(
25399				'img:-moz-broken {' +
25400					'-moz-force-broken-image-icon:1;' +
25401					'min-width:24px;' +
25402					'min-height:24px' +
25403				'}'
25404			);
25405		}
25406
25407		/**
25408		 * iOS has a bug where it's impossible to type if the document has a touchstart event
25409		 * bound and the user touches the document while having the on screen keyboard visible.
25410		 *
25411		 * The touch event moves the focus to the parent document while having the caret inside the iframe
25412		 * this fix moves the focus back into the iframe document.
25413		 */
25414		function restoreFocusOnKeyDown() {
25415			if (!editor.inline) {
25416				editor.on('keydown', function() {
25417					if (document.activeElement == document.body) {
25418						editor.getWin().focus();
25419					}
25420				});
25421			}
25422		}
25423
25424		/**
25425		 * IE 11 has an annoying issue where you can't move focus into the editor
25426		 * by clicking on the white area HTML element. We used to be able to to fix this with
25427		 * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
25428		 * object it's not possible anymore. So we need to hack in a ungly CSS to force the
25429		 * body to be at least 150px. If the user clicks the HTML element out side this 150px region
25430		 * we simply move the focus into the first paragraph. Not ideal since you loose the
25431		 * positioning of the caret but goot enough for most cases.
25432		 */
25433		function bodyHeight() {
25434			if (!editor.inline) {
25435				editor.contentStyles.push('body {min-height: 150px}');
25436				editor.on('click', function(e) {
25437					if (e.target.nodeName == 'HTML') {
25438						editor.getBody().focus();
25439						editor.selection.normalize();
25440						editor.nodeChanged();
25441					}
25442				});
25443			}
25444		}
25445
25446		/**
25447		 * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
25448		 * You might then loose all your work so we need to block that behavior and replace it with our own.
25449		 */
25450		function blockCmdArrowNavigation() {
25451			if (Env.mac) {
25452				editor.on('keydown', function(e) {
25453					if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) {
25454						e.preventDefault();
25455						editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word');
25456					}
25457				});
25458			}
25459		}
25460
25461		/**
25462		 * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
25463		 */
25464		function disableAutoUrlDetect() {
25465			setEditorCommandState("AutoUrlDetect", false);
25466		}
25467
25468		/**
25469		 * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
25470		 * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
25471		 * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
25472		 * but not as the lastChild of the body. However is we add a BR element to the body then remove it
25473		 * it doesn't seem to add these BR elements makes sence right?!
25474		 *
25475		 * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
25476		 */
25477		function doubleTrailingBrElements() {
25478			if (!editor.inline) {
25479				editor.on('focus blur beforegetcontent', function() {
25480					var br = editor.dom.create('br');
25481					editor.getBody().appendChild(br);
25482					br.parentNode.removeChild(br);
25483				}, true);
25484			}
25485		}
25486
25487		/**
25488		 * iOS 7.1 introduced two new bugs:
25489		 * 1) It's possible to open links within a contentEditable area by clicking on them.
25490		 * 2) If you hold down the finger it will display the link/image touch callout menu.
25491		 */
25492		function tapLinksAndImages() {
25493			editor.on('click', function(e) {
25494				var elm = e.target;
25495
25496				do {
25497					if (elm.tagName === 'A') {
25498						e.preventDefault();
25499						return;
25500					}
25501				} while ((elm = elm.parentNode));
25502			});
25503
25504			editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
25505		}
25506
25507		/**
25508		 * iOS Safari and possible other browsers have a bug where it won't fire
25509		 * a click event when a contentEditable is focused. This function fakes click events
25510		 * by using touchstart/touchend and measuring the time and distance travelled.
25511		 */
25512		function touchClickEvent() {
25513			editor.on('touchstart', function(e) {
25514				var elm, time, startTouch, changedTouches;
25515
25516				elm = e.target;
25517				time = new Date().getTime();
25518				changedTouches = e.changedTouches;
25519
25520				if (!changedTouches || changedTouches.length > 1) {
25521					return;
25522				}
25523
25524				startTouch = changedTouches[0];
25525
25526				editor.once('touchend', function(e) {
25527					var endTouch = e.changedTouches[0], args;
25528
25529					if (new Date().getTime() - time > 500) {
25530						return;
25531					}
25532
25533					if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) {
25534						return;
25535					}
25536
25537					if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) {
25538						return;
25539					}
25540
25541					args = {
25542						target: elm
25543					};
25544
25545					each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) {
25546						args[key] = endTouch[key];
25547					});
25548
25549					args = editor.fire('click', args);
25550
25551					if (!args.isDefaultPrevented()) {
25552						// iOS WebKit can't place the caret properly once
25553						// you bind touch events so we need to do this manually
25554						// TODO: Expand to the closest word? Touble tap still works.
25555						editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY);
25556						editor.nodeChanged();
25557					}
25558				});
25559			});
25560		}
25561
25562		/**
25563		 * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
25564		 * For example this: <form><button></form>
25565		 */
25566		function blockFormSubmitInsideEditor() {
25567			editor.on('init', function() {
25568				editor.dom.bind(editor.getBody(), 'submit', function(e) {
25569					e.preventDefault();
25570				});
25571			});
25572		}
25573
25574		/**
25575		 * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
25576		 *
25577		 * Scenario:
25578		 *  1) Create a table 2x2.
25579		 *  2) Select and copy cells A2-B2.
25580		 *  3) Paste and it will add BR element to table cell.
25581		 */
25582		function removeAppleInterchangeBrs() {
25583			parser.addNodeFilter('br', function(nodes) {
25584				var i = nodes.length;
25585
25586				while (i--) {
25587					if (nodes[i].attr('class') == 'Apple-interchange-newline') {
25588						nodes[i].remove();
25589					}
25590				}
25591			});
25592		}
25593
25594		// All browsers
25595		removeBlockQuoteOnBackSpace();
25596		emptyEditorWhenDeleting();
25597		normalizeSelection();
25598
25599		// WebKit
25600		if (isWebKit) {
25601			cleanupStylesWhenDeleting();
25602			inputMethodFocus();
25603			selectControlElements();
25604			setDefaultBlockType();
25605			blockFormSubmitInsideEditor();
25606			disableBackspaceIntoATable();
25607			removeAppleInterchangeBrs();
25608			touchClickEvent();
25609
25610			// iOS
25611			if (Env.iOS) {
25612				restoreFocusOnKeyDown();
25613				bodyHeight();
25614				tapLinksAndImages();
25615			} else {
25616				selectAll();
25617			}
25618		}
25619
25620		// IE
25621		if (isIE && Env.ie < 11) {
25622			removeHrOnBackspace();
25623			ensureBodyHasRoleApplication();
25624			addNewLinesBeforeBrInPre();
25625			removePreSerializedStylesWhenSelectingControls();
25626			deleteControlItemOnBackSpace();
25627			renderEmptyBlocksFix();
25628			keepNoScriptContents();
25629			fixCaretSelectionOfDocumentElementOnIe();
25630		}
25631
25632		if (Env.ie >= 11) {
25633			bodyHeight();
25634			doubleTrailingBrElements();
25635			disableBackspaceIntoATable();
25636		}
25637
25638		if (Env.ie) {
25639			selectAll();
25640			disableAutoUrlDetect();
25641		}
25642
25643		// Gecko
25644		if (isGecko) {
25645			removeHrOnBackspace();
25646			focusBody();
25647			removeStylesWhenDeletingAcrossBlockElements();
25648			setGeckoEditingOptions();
25649			addBrAfterLastLinks();
25650			removeGhostSelection();
25651			showBrokenImageIcon();
25652			blockCmdArrowNavigation();
25653			disableBackspaceIntoATable();
25654		}
25655	};
25656});
25657
25658// Included from: js/tinymce/classes/util/Observable.js
25659
25660/**
25661 * Observable.js
25662 *
25663 * Copyright, Moxiecode Systems AB
25664 * Released under LGPL License.
25665 *
25666 * License: http://www.tinymce.com/license
25667 * Contributing: http://www.tinymce.com/contributing
25668 */
25669
25670/**
25671 * This mixin will add event binding logic to classes.
25672 *
25673 * @mixin tinymce.util.Observable
25674 */
25675define("tinymce/util/Observable", [
25676	"tinymce/util/EventDispatcher"
25677], function(EventDispatcher) {
25678	function getEventDispatcher(obj) {
25679		if (!obj._eventDispatcher) {
25680			obj._eventDispatcher = new EventDispatcher({
25681				scope: obj,
25682				toggleEvent: function(name, state) {
25683					if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
25684						obj.toggleNativeEvent(name, state);
25685					}
25686				}
25687			});
25688		}
25689
25690		return obj._eventDispatcher;
25691	}
25692
25693	return {
25694		/**
25695		 * Fires the specified event by name.
25696		 *
25697		 * @method fire
25698		 * @param {String} name Name of the event to fire.
25699		 * @param {Object?} args Event arguments.
25700		 * @param {Boolean?} bubble True/false if the event is to be bubbled.
25701		 * @return {Object} Event args instance passed in.
25702		 * @example
25703		 * instance.fire('event', {...});
25704		 */
25705		fire: function(name, args, bubble) {
25706			var self = this;
25707
25708			// Prevent all events except the remove event after the instance has been removed
25709			if (self.removed && name !== "remove") {
25710				return args;
25711			}
25712
25713			args = getEventDispatcher(self).fire(name, args, bubble);
25714
25715			// Bubble event up to parents
25716			if (bubble !== false && self.parent) {
25717				var parent = self.parent();
25718				while (parent && !args.isPropagationStopped()) {
25719					parent.fire(name, args, false);
25720					parent = parent.parent();
25721				}
25722			}
25723
25724			return args;
25725		},
25726
25727		/**
25728		 * Binds an event listener to a specific event by name.
25729		 *
25730		 * @method on
25731		 * @param {String} name Event name or space separated list of events to bind.
25732		 * @param {callback} callback Callback to be executed when the event occurs.
25733		 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
25734		 * @return {Object} Current class instance.
25735		 * @example
25736		 * instance.on('event', function(e) {
25737		 *     // Callback logic
25738		 * });
25739		 */
25740		on: function(name, callback, prepend) {
25741			return getEventDispatcher(this).on(name, callback, prepend);
25742		},
25743
25744		/**
25745		 * Unbinds an event listener to a specific event by name.
25746		 *
25747		 * @method off
25748		 * @param {String?} name Name of the event to unbind.
25749		 * @param {callback?} callback Callback to unbind.
25750		 * @return {Object} Current class instance.
25751		 * @example
25752		 * // Unbind specific callback
25753		 * instance.off('event', handler);
25754		 *
25755		 * // Unbind all listeners by name
25756		 * instance.off('event');
25757		 *
25758		 * // Unbind all events
25759		 * instance.off();
25760		 */
25761		off: function(name, callback) {
25762			return getEventDispatcher(this).off(name, callback);
25763		},
25764
25765		/**
25766		 * Bind the event callback and once it fires the callback is removed.
25767		 *
25768		 * @method once
25769		 * @param {String} name Name of the event to bind.
25770		 * @param {callback} callback Callback to bind only once.
25771		 * @return {Object} Current class instance.
25772		 */
25773		once: function(name, callback) {
25774			return getEventDispatcher(this).once(name, callback);
25775		},
25776
25777		/**
25778		 * Returns true/false if the object has a event of the specified name.
25779		 *
25780		 * @method hasEventListeners
25781		 * @param {String} name Name of the event to check for.
25782		 * @return {Boolean} true/false if the event exists or not.
25783		 */
25784		hasEventListeners: function(name) {
25785			return getEventDispatcher(this).has(name);
25786		}
25787	};
25788});
25789
25790// Included from: js/tinymce/classes/EditorObservable.js
25791
25792/**
25793 * EditorObservable.js
25794 *
25795 * Copyright, Moxiecode Systems AB
25796 * Released under LGPL License.
25797 *
25798 * License: http://www.tinymce.com/license
25799 * Contributing: http://www.tinymce.com/contributing
25800 */
25801
25802/**
25803 * This mixin contains the event logic for the tinymce.Editor class.
25804 *
25805 * @mixin tinymce.EditorObservable
25806 * @extends tinymce.util.Observable
25807 */
25808define("tinymce/EditorObservable", [
25809	"tinymce/util/Observable",
25810	"tinymce/dom/DOMUtils",
25811	"tinymce/util/Tools"
25812], function(Observable, DOMUtils, Tools) {
25813	var DOM = DOMUtils.DOM, customEventRootDelegates;
25814
25815	/**
25816	 * Returns the event target so for the specified event. Some events fire
25817	 * only on document, some fire on documentElement etc. This also handles the
25818	 * custom event root setting where it returns that element instead of the body.
25819	 *
25820	 * @private
25821	 * @param {tinymce.Editor} editor Editor instance to get event target from.
25822	 * @param {String} eventName Name of the event for example "click".
25823	 * @return {Element/Document} HTML Element or document target to bind on.
25824	 */
25825	function getEventTarget(editor, eventName) {
25826		if (eventName == 'selectionchange') {
25827			return editor.getDoc();
25828		}
25829
25830		// Need to bind mousedown/mouseup etc to document not body in iframe mode
25831		// Since the user might click on the HTML element not the BODY
25832		if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
25833			return editor.getDoc().documentElement;
25834		}
25835
25836		// Bind to event root instead of body if it's defined
25837		if (editor.settings.event_root) {
25838			if (!editor.eventRoot) {
25839				editor.eventRoot = DOM.select(editor.settings.event_root)[0];
25840			}
25841
25842			return editor.eventRoot;
25843		}
25844
25845		return editor.getBody();
25846	}
25847
25848	/**
25849	 * Binds a event delegate for the specified name this delegate will fire
25850	 * the event to the editor dispatcher.
25851	 *
25852	 * @private
25853	 * @param {tinymce.Editor} editor Editor instance to get event target from.
25854	 * @param {String} eventName Name of the event for example "click".
25855	 */
25856	function bindEventDelegate(editor, eventName) {
25857		var eventRootElm = getEventTarget(editor, eventName), delegate;
25858
25859		if (!editor.delegates) {
25860			editor.delegates = {};
25861		}
25862
25863		if (editor.delegates[eventName]) {
25864			return;
25865		}
25866
25867		if (editor.settings.event_root) {
25868			if (!customEventRootDelegates) {
25869				customEventRootDelegates = {};
25870				editor.editorManager.on('removeEditor', function() {
25871					var name;
25872
25873					if (!editor.editorManager.activeEditor) {
25874						if (customEventRootDelegates) {
25875							for (name in customEventRootDelegates) {
25876								editor.dom.unbind(getEventTarget(editor, name));
25877							}
25878
25879							customEventRootDelegates = null;
25880						}
25881					}
25882				});
25883			}
25884
25885			if (customEventRootDelegates[eventName]) {
25886				return;
25887			}
25888
25889			delegate = function(e) {
25890				var target = e.target, editors = editor.editorManager.editors, i = editors.length;
25891
25892				while (i--) {
25893					var body = editors[i].getBody();
25894
25895					if (body === target || DOM.isChildOf(target, body)) {
25896						if (!editors[i].hidden) {
25897							editors[i].fire(eventName, e);
25898						}
25899					}
25900				}
25901			};
25902
25903			customEventRootDelegates[eventName] = delegate;
25904			DOM.bind(eventRootElm, eventName, delegate);
25905		} else {
25906			delegate = function(e) {
25907				if (!editor.hidden) {
25908					editor.fire(eventName, e);
25909				}
25910			};
25911
25912			DOM.bind(eventRootElm, eventName, delegate);
25913			editor.delegates[eventName] = delegate;
25914		}
25915	}
25916
25917	var EditorObservable = {
25918		/**
25919		 * Bind any pending event delegates. This gets executed after the target body/document is created.
25920		 *
25921		 * @private
25922		 */
25923		bindPendingEventDelegates: function() {
25924			var self = this;
25925
25926			Tools.each(self._pendingNativeEvents, function(name) {
25927				bindEventDelegate(self, name);
25928			});
25929		},
25930
25931		/**
25932		 * Toggles a native event on/off this is called by the EventDispatcher when
25933		 * the first native event handler is added and when the last native event handler is removed.
25934		 *
25935		 * @private
25936		 */
25937		toggleNativeEvent: function(name, state) {
25938			var self = this;
25939
25940			if (self.settings.readonly) {
25941				return;
25942			}
25943
25944			// Never bind focus/blur since the FocusManager fakes those
25945			if (name == "focus" || name == "blur") {
25946				return;
25947			}
25948
25949			if (state) {
25950				if (self.initialized) {
25951					bindEventDelegate(self, name);
25952				} else {
25953					if (!self._pendingNativeEvents) {
25954						self._pendingNativeEvents = [name];
25955					} else {
25956						self._pendingNativeEvents.push(name);
25957					}
25958				}
25959			} else if (self.initialized) {
25960				self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
25961				delete self.delegates[name];
25962			}
25963		},
25964
25965		/**
25966		 * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
25967		 *
25968		 * @private
25969		 */
25970		unbindAllNativeEvents: function() {
25971			var self = this, name;
25972
25973			if (self.delegates) {
25974				for (name in self.delegates) {
25975					self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
25976				}
25977
25978				delete self.delegates;
25979			}
25980
25981			if (!self.inline) {
25982				self.getBody().onload = null;
25983				self.dom.unbind(self.getWin());
25984				self.dom.unbind(self.getDoc());
25985			}
25986
25987			self.dom.unbind(self.getBody());
25988			self.dom.unbind(self.getContainer());
25989		}
25990	};
25991
25992	EditorObservable = Tools.extend({}, Observable, EditorObservable);
25993
25994	return EditorObservable;
25995});
25996
25997// Included from: js/tinymce/classes/Shortcuts.js
25998
25999/**
26000 * Shortcuts.js
26001 *
26002 * Copyright, Moxiecode Systems AB
26003 * Released under LGPL License.
26004 *
26005 * License: http://www.tinymce.com/license
26006 * Contributing: http://www.tinymce.com/contributing
26007 */
26008
26009/**
26010 * Contains all logic for handling of keyboard shortcuts.
26011 */
26012define("tinymce/Shortcuts", [
26013	"tinymce/util/Tools",
26014	"tinymce/Env"
26015], function(Tools, Env) {
26016	var each = Tools.each, explode = Tools.explode;
26017
26018	var keyCodeLookup = {
26019		"f9": 120,
26020		"f10": 121,
26021		"f11": 122
26022	};
26023
26024	return function(editor) {
26025		var self = this, shortcuts = {};
26026
26027		editor.on('keyup keypress keydown', function(e) {
26028			if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) {
26029				each(shortcuts, function(shortcut) {
26030					var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey;
26031
26032					if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
26033						return;
26034					}
26035
26036					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
26037						e.preventDefault();
26038
26039						if (e.type == "keydown") {
26040							shortcut.func.call(shortcut.scope);
26041						}
26042
26043						return true;
26044					}
26045				});
26046			}
26047		});
26048
26049		/**
26050		 * Adds a keyboard shortcut for some command or function.
26051		 *
26052		 * @method addShortcut
26053		 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
26054		 * @param {String} desc Text description for the command.
26055		 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
26056		 * @param {Object} sc Optional scope to execute the function in.
26057		 * @return {Boolean} true/false state if the shortcut was added or not.
26058		 */
26059		self.add = function(pattern, desc, cmdFunc, scope) {
26060			var cmd;
26061
26062			cmd = cmdFunc;
26063
26064			if (typeof(cmdFunc) === 'string') {
26065				cmdFunc = function() {
26066					editor.execCommand(cmd, false, null);
26067				};
26068			} else if (Tools.isArray(cmd)) {
26069				cmdFunc = function() {
26070					editor.execCommand(cmd[0], cmd[1], cmd[2]);
26071				};
26072			}
26073
26074			each(explode(pattern.toLowerCase()), function(pattern) {
26075				var shortcut = {
26076					func: cmdFunc,
26077					scope: scope || editor,
26078					desc: editor.translate(desc),
26079					alt: false,
26080					ctrl: false,
26081					shift: false
26082				};
26083
26084				each(explode(pattern, '+'), function(value) {
26085					switch (value) {
26086						case 'alt':
26087						case 'ctrl':
26088						case 'shift':
26089							shortcut[value] = true;
26090							break;
26091
26092						default:
26093							// Allow numeric keycodes like ctrl+219 for ctrl+[
26094							if (/^[0-9]{2,}$/.test(value)) {
26095								shortcut.keyCode = parseInt(value, 10);
26096							} else {
26097								shortcut.charCode = value.charCodeAt(0);
26098								shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
26099							}
26100					}
26101				});
26102
26103				shortcuts[
26104					(shortcut.ctrl ? 'ctrl' : '') + ',' +
26105					(shortcut.alt ? 'alt' : '') + ',' +
26106					(shortcut.shift ? 'shift' : '') + ',' +
26107					shortcut.keyCode
26108				] = shortcut;
26109			});
26110
26111			return true;
26112		};
26113	};
26114});
26115
26116// Included from: js/tinymce/classes/Editor.js
26117
26118/**
26119 * Editor.js
26120 *
26121 * Copyright, Moxiecode Systems AB
26122 * Released under LGPL License.
26123 *
26124 * License: http://www.tinymce.com/license
26125 * Contributing: http://www.tinymce.com/contributing
26126 */
26127
26128/*jshint scripturl:true */
26129
26130/**
26131 * Include the base event class documentation.
26132 *
26133 * @include ../../../tools/docs/tinymce.Event.js
26134 */
26135
26136/**
26137 * This class contains the core logic for a TinyMCE editor.
26138 *
26139 * @class tinymce.Editor
26140 * @mixes tinymce.util.Observable
26141 * @example
26142 * // Add a class to all paragraphs in the editor.
26143 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
26144 *
26145 * // Gets the current editors selection as text
26146 * tinymce.activeEditor.selection.getContent({format: 'text'});
26147 *
26148 * // Creates a new editor instance
26149 * var ed = new tinymce.Editor('textareaid', {
26150 *     some_setting: 1
26151 * }, tinymce.EditorManager);
26152 *
26153 * // Select each item the user clicks on
26154 * ed.on('click', function(e) {
26155 *     ed.selection.select(e.target);
26156 * });
26157 *
26158 * ed.render();
26159 */
26160define("tinymce/Editor", [
26161	"tinymce/dom/DOMUtils",
26162	"tinymce/dom/DomQuery",
26163	"tinymce/AddOnManager",
26164	"tinymce/NodeChange",
26165	"tinymce/html/Node",
26166	"tinymce/dom/Serializer",
26167	"tinymce/html/Serializer",
26168	"tinymce/dom/Selection",
26169	"tinymce/Formatter",
26170	"tinymce/UndoManager",
26171	"tinymce/EnterKey",
26172	"tinymce/ForceBlocks",
26173	"tinymce/EditorCommands",
26174	"tinymce/util/URI",
26175	"tinymce/dom/ScriptLoader",
26176	"tinymce/dom/EventUtils",
26177	"tinymce/WindowManager",
26178	"tinymce/html/Schema",
26179	"tinymce/html/DomParser",
26180	"tinymce/util/Quirks",
26181	"tinymce/Env",
26182	"tinymce/util/Tools",
26183	"tinymce/EditorObservable",
26184	"tinymce/Shortcuts"
26185], function(
26186	DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer,
26187	Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
26188	URI, ScriptLoader, EventUtils, WindowManager,
26189	Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts
26190) {
26191	// Shorten these names
26192	var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
26193	var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
26194	var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
26195	var Event = EventUtils.Event;
26196	var isGecko = Env.gecko, ie = Env.ie;
26197
26198	/**
26199	 * Include documentation for all the events.
26200	 *
26201	 * @include ../../../tools/docs/tinymce.Editor.js
26202	 */
26203
26204	/**
26205	 * Constructs a editor instance by id.
26206	 *
26207	 * @constructor
26208	 * @method Editor
26209	 * @param {String} id Unique id for the editor.
26210	 * @param {Object} settings Settings for the editor.
26211	 * @param {tinymce.EditorManager} editorManager EditorManager instance.
26212	 * @author Moxiecode
26213	 */
26214	function Editor(id, settings, editorManager) {
26215		var self = this, documentBaseUrl, baseUri;
26216
26217		documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
26218		baseUri = editorManager.baseURI;
26219
26220		/**
26221		 * Name/value collection with editor settings.
26222		 *
26223		 * @property settings
26224		 * @type Object
26225		 * @example
26226		 * // Get the value of the theme setting
26227		 * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
26228		 */
26229		self.settings = settings = extend({
26230			id: id,
26231			theme: 'modern',
26232			delta_width: 0,
26233			delta_height: 0,
26234			popup_css: '',
26235			plugins: '',
26236			document_base_url: documentBaseUrl,
26237			add_form_submit_trigger: true,
26238			submit_patch: true,
26239			add_unload_trigger: true,
26240			convert_urls: true,
26241			relative_urls: true,
26242			remove_script_host: true,
26243			object_resizing: true,
26244			doctype: '<!DOCTYPE html>',
26245			visual: true,
26246			font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
26247
26248			// See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
26249			font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
26250			forced_root_block: 'p',
26251			hidden_input: true,
26252			padd_empty_editor: true,
26253			render_ui: true,
26254			indentation: '30px',
26255			inline_styles: true,
26256			convert_fonts_to_spans: true,
26257			indent: 'simple',
26258			indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
26259				'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
26260			indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
26261				'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
26262			validate: true,
26263			entity_encoding: 'named',
26264			url_converter: self.convertURL,
26265			url_converter_scope: self,
26266			ie7_compat: true
26267		}, settings);
26268
26269		AddOnManager.language = settings.language || 'en';
26270		AddOnManager.languageLoad = settings.language_load;
26271
26272		AddOnManager.baseURL = editorManager.baseURL;
26273
26274		/**
26275		 * Editor instance id, normally the same as the div/textarea that was replaced.
26276		 *
26277		 * @property id
26278		 * @type String
26279		 */
26280		self.id = settings.id = id;
26281
26282		/**
26283		 * State to force the editor to return false on a isDirty call.
26284		 *
26285		 * @property isNotDirty
26286		 * @type Boolean
26287		 * @example
26288		 * function ajaxSave() {
26289		 *     var ed = tinymce.get('elm1');
26290		 *
26291		 *     // Save contents using some XHR call
26292		 *     alert(ed.getContent());
26293		 *
26294		 *     ed.isNotDirty = true; // Force not dirty state
26295		 * }
26296		 */
26297		self.isNotDirty = true;
26298
26299		/**
26300		 * Name/Value object containting plugin instances.
26301		 *
26302		 * @property plugins
26303		 * @type Object
26304		 * @example
26305		 * // Execute a method inside a plugin directly
26306		 * tinymce.activeEditor.plugins.someplugin.someMethod();
26307		 */
26308		self.plugins = {};
26309
26310		/**
26311		 * URI object to document configured for the TinyMCE instance.
26312		 *
26313		 * @property documentBaseURI
26314		 * @type tinymce.util.URI
26315		 * @example
26316		 * // Get relative URL from the location of document_base_url
26317		 * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
26318		 *
26319		 * // Get absolute URL from the location of document_base_url
26320		 * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
26321		 */
26322		self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
26323			base_uri: baseUri
26324		});
26325
26326		/**
26327		 * URI object to current document that holds the TinyMCE editor instance.
26328		 *
26329		 * @property baseURI
26330		 * @type tinymce.util.URI
26331		 * @example
26332		 * // Get relative URL from the location of the API
26333		 * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
26334		 *
26335		 * // Get absolute URL from the location of the API
26336		 * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
26337		 */
26338		self.baseURI = baseUri;
26339
26340		/**
26341		 * Array with CSS files to load into the iframe.
26342		 *
26343		 * @property contentCSS
26344		 * @type Array
26345		 */
26346		self.contentCSS = [];
26347
26348		/**
26349		 * Array of CSS styles to add to head of document when the editor loads.
26350		 *
26351		 * @property contentStyles
26352		 * @type Array
26353		 */
26354		self.contentStyles = [];
26355
26356		// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
26357		self.shortcuts = new Shortcuts(self);
26358
26359		// Internal command handler objects
26360		self.execCommands = {};
26361		self.queryStateCommands = {};
26362		self.queryValueCommands = {};
26363		self.loadedCSS = {};
26364
26365		if (settings.target) {
26366			self.targetElm = settings.target;
26367		}
26368
26369		self.suffix = editorManager.suffix;
26370		self.editorManager = editorManager;
26371		self.inline = settings.inline;
26372
26373		if (settings.cache_suffix) {
26374			Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, '');
26375		}
26376
26377		// Call setup
26378		editorManager.fire('SetupEditor', self);
26379		self.execCallback('setup', self);
26380
26381		/**
26382		 * Dom query instance with default scope to the editor document and default element is the body of the editor.
26383		 *
26384		 * @property $
26385		 * @type tinymce.dom.DomQuery
26386		 * @example
26387		 * tinymce.activeEditor.$('p').css('color', 'red');
26388		 * tinymce.activeEditor.$().append('<p>new</p>');
26389		 */
26390		self.$ = DomQuery.overrideDefaults(function() {
26391			return {
26392				context: self.inline ? self.getBody() : self.getDoc(),
26393				element: self.getBody()
26394			};
26395		});
26396	}
26397
26398	Editor.prototype = {
26399		/**
26400		 * Renderes the editor/adds it to the page.
26401		 *
26402		 * @method render
26403		 */
26404		render: function() {
26405			var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
26406
26407			function readyHandler() {
26408				DOM.unbind(window, 'ready', readyHandler);
26409				self.render();
26410			}
26411
26412			// Page is not loaded yet, wait for it
26413			if (!Event.domLoaded) {
26414				DOM.bind(window, 'ready', readyHandler);
26415				return;
26416			}
26417
26418			// Element not found, then skip initialization
26419			if (!self.getElement()) {
26420				return;
26421			}
26422
26423			// No editable support old iOS versions etc
26424			if (!Env.contentEditable) {
26425				return;
26426			}
26427
26428			// Hide target element early to prevent content flashing
26429			if (!settings.inline) {
26430				self.orgVisibility = self.getElement().style.visibility;
26431				self.getElement().style.visibility = 'hidden';
26432			} else {
26433				self.inline = true;
26434			}
26435
26436			var form = self.getElement().form || DOM.getParent(id, 'form');
26437			if (form) {
26438				self.formElement = form;
26439
26440				// Add hidden input for non input elements inside form elements
26441				if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
26442					DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
26443					self.hasHiddenInput = true;
26444				}
26445
26446				// Pass submit/reset from form to editor instance
26447				self.formEventDelegate = function(e) {
26448					self.fire(e.type, e);
26449				};
26450
26451				DOM.bind(form, 'submit reset', self.formEventDelegate);
26452
26453				// Reset contents in editor when the form is reset
26454				self.on('reset', function() {
26455					self.setContent(self.startContent, {format: 'raw'});
26456				});
26457
26458				// Check page uses id="submit" or name="submit" for it's submit button
26459				if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
26460					form._mceOldSubmit = form.submit;
26461					form.submit = function() {
26462						self.editorManager.triggerSave();
26463						self.isNotDirty = true;
26464
26465						return form._mceOldSubmit(form);
26466					};
26467				}
26468			}
26469
26470			/**
26471			 * Window manager reference, use this to open new windows and dialogs.
26472			 *
26473			 * @property windowManager
26474			 * @type tinymce.WindowManager
26475			 * @example
26476			 * // Shows an alert message
26477			 * tinymce.activeEditor.windowManager.alert('Hello world!');
26478			 *
26479			 * // Opens a new dialog with the file.htm file and the size 320x240
26480			 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
26481			 * tinymce.activeEditor.windowManager.open({
26482			 *    url: 'file.htm',
26483			 *    width: 320,
26484			 *    height: 240
26485			 * }, {
26486			 *    custom_param: 1
26487			 * });
26488			 */
26489			self.windowManager = new WindowManager(self);
26490
26491			if (settings.encoding == 'xml') {
26492				self.on('GetContent', function(e) {
26493					if (e.save) {
26494						e.content = DOM.encode(e.content);
26495					}
26496				});
26497			}
26498
26499			if (settings.add_form_submit_trigger) {
26500				self.on('submit', function() {
26501					if (self.initialized) {
26502						self.save();
26503					}
26504				});
26505			}
26506
26507			if (settings.add_unload_trigger) {
26508				self._beforeUnload = function() {
26509					if (self.initialized && !self.destroyed && !self.isHidden()) {
26510						self.save({format: 'raw', no_events: true, set_dirty: false});
26511					}
26512				};
26513
26514				self.editorManager.on('BeforeUnload', self._beforeUnload);
26515			}
26516
26517			// Load scripts
26518			function loadScripts() {
26519				var scriptLoader = ScriptLoader.ScriptLoader;
26520
26521				if (settings.language && settings.language != 'en' && !settings.language_url) {
26522					settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
26523				}
26524
26525				if (settings.language_url) {
26526					scriptLoader.add(settings.language_url);
26527				}
26528
26529				if (settings.theme && typeof settings.theme != "function" &&
26530					settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
26531					var themeUrl = settings.theme_url;
26532
26533					if (themeUrl) {
26534						themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
26535					} else {
26536						themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
26537					}
26538
26539					ThemeManager.load(settings.theme, themeUrl);
26540				}
26541
26542				if (Tools.isArray(settings.plugins)) {
26543					settings.plugins = settings.plugins.join(' ');
26544				}
26545
26546				each(settings.external_plugins, function(url, name) {
26547					PluginManager.load(name, url);
26548					settings.plugins += ' ' + name;
26549				});
26550
26551				each(settings.plugins.split(/[ ,]/), function(plugin) {
26552					plugin = trim(plugin);
26553
26554					if (plugin && !PluginManager.urls[plugin]) {
26555						if (plugin.charAt(0) == '-') {
26556							plugin = plugin.substr(1, plugin.length);
26557
26558							var dependencies = PluginManager.dependencies(plugin);
26559
26560							each(dependencies, function(dep) {
26561								var defaultSettings = {
26562									prefix: 'plugins/',
26563									resource: dep,
26564									suffix: '/plugin' + suffix + '.js'
26565								};
26566
26567								dep = PluginManager.createUrl(defaultSettings, dep);
26568								PluginManager.load(dep.resource, dep);
26569							});
26570						} else {
26571							PluginManager.load(plugin, {
26572								prefix: 'plugins/',
26573								resource: plugin,
26574								suffix: '/plugin' + suffix + '.js'
26575							});
26576						}
26577					}
26578				});
26579
26580				scriptLoader.loadQueue(function() {
26581					if (!self.removed) {
26582						self.init();
26583					}
26584				});
26585			}
26586
26587			loadScripts();
26588		},
26589
26590		/**
26591		 * Initializes the editor this will be called automatically when
26592		 * all plugins/themes and language packs are loaded by the rendered method.
26593		 * This method will setup the iframe and create the theme and plugin instances.
26594		 *
26595		 * @method init
26596		 */
26597		init: function() {
26598			var self = this, settings = self.settings, elm = self.getElement();
26599			var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
26600
26601			self.rtl = this.editorManager.i18n.rtl;
26602			self.editorManager.add(self);
26603
26604			settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
26605
26606			/**
26607			 * Reference to the theme instance that was used to generate the UI.
26608			 *
26609			 * @property theme
26610			 * @type tinymce.Theme
26611			 * @example
26612			 * // Executes a method on the theme directly
26613			 * tinymce.activeEditor.theme.someMethod();
26614			 */
26615			if (settings.theme) {
26616				if (typeof settings.theme != "function") {
26617					settings.theme = settings.theme.replace(/-/, '');
26618					Theme = ThemeManager.get(settings.theme);
26619					self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
26620
26621					if (self.theme.init) {
26622						self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$);
26623					}
26624				} else {
26625					self.theme = settings.theme;
26626				}
26627			}
26628
26629			function initPlugin(plugin) {
26630				var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
26631
26632				pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
26633				plugin = trim(plugin);
26634				if (Plugin && inArray(initializedPlugins, plugin) === -1) {
26635					each(PluginManager.dependencies(plugin), function(dep) {
26636						initPlugin(dep);
26637					});
26638
26639					pluginInstance = new Plugin(self, pluginUrl, self.$);
26640
26641					self.plugins[plugin] = pluginInstance;
26642
26643					if (pluginInstance.init) {
26644						pluginInstance.init(self, pluginUrl);
26645						initializedPlugins.push(plugin);
26646					}
26647				}
26648			}
26649
26650			// Create all plugins
26651			each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
26652
26653			// Measure box
26654			if (settings.render_ui && self.theme) {
26655				self.orgDisplay = elm.style.display;
26656
26657				if (typeof settings.theme != "function") {
26658					w = settings.width || elm.style.width || elm.offsetWidth;
26659					h = settings.height || elm.style.height || elm.offsetHeight;
26660					minHeight = settings.min_height || 100;
26661					re = /^[0-9\.]+(|px)$/i;
26662
26663					if (re.test('' + w)) {
26664						w = Math.max(parseInt(w, 10), 100);
26665					}
26666
26667					if (re.test('' + h)) {
26668						h = Math.max(parseInt(h, 10), minHeight);
26669					}
26670
26671					// Render UI
26672					o = self.theme.renderUI({
26673						targetNode: elm,
26674						width: w,
26675						height: h,
26676						deltaWidth: settings.delta_width,
26677						deltaHeight: settings.delta_height
26678					});
26679
26680					// Resize editor
26681					if (!settings.content_editable) {
26682						h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0
26682) : '');
26683						if (h < minHeight) {
26684							h = minHeight;
26685						}
26686					}
26687				} else {
26688					o = settings.theme(self, elm);
26689
26690					// Convert element type to id:s
26691					if (o.editorContainer.nodeType) {
26692						o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
26693					}
26694
26695					// Convert element type to id:s
26696					if (o.iframeContainer.nodeType) {
26697						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
26698					}
26699
26700					// Use specified iframe height or the targets offsetHeight
26701					h = o.iframeHeight || elm.offsetHeight;
26702				}
26703
26704				self.editorContainer = o.editorContainer;
26705			}
26706
26707			// Load specified content CSS last
26708			if (settings.content_css) {
26709				each(explode(settings.content_css), function(u) {
26710					self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
26711				});
26712			}
26713
26714			// Load specified content CSS last
26715			if (settings.content_style) {
26716				self.contentStyles.push(settings.content_style);
26717			}
26718
26719			// Content editable mode ends here
26720			if (settings.content_editable) {
26721				elm = n = o = null; // Fix IE leak
26722				return self.initContentBody();
26723			}
26724
26725			self.iframeHTML = settings.doctype + '<html><head>';
26726
26727			// We only need to override paths if we have to
26728			// IE has a bug where it remove site absolute urls to relative ones if this is specified
26729			if (settings.document_base_url != self.documentBaseUrl) {
26730				self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
26731			}
26732
26733			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
26734			if (!Env.caretAfter && settings.ie7_compat) {
26735				self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
26736			}
26737
26738			self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
26739
26740			// Load the CSS by injecting them into the HTML this will reduce "flicker"
26741			for (i = 0; i < self.contentCSS.length; i++) {
26742				var cssUrl = self.contentCSS[i];
26743				self.iframeHTML += (
26744					'<link type="text/css" ' +
26745						'rel="stylesheet" ' +
26746						'href="' + Tools._addCacheSuffix(cssUrl) + '" />'
26747				);
26748				self.loadedCSS[cssUrl] = true;
26749			}
26750
26751			bodyId = settings.body_id || 'tinymce';
26752			if (bodyId.indexOf('=') != -1) {
26753				bodyId = self.getParam('body_id', '', 'hash');
26754				bodyId = bodyId[self.id] || bodyId;
26755			}
26756
26757			bodyClass = settings.body_class || '';
26758			if (bodyClass.indexOf('=') != -1) {
26759				bodyClass = self.getParam('body_class', '', 'hash');
26760				bodyClass = bodyClass[self.id] || '';
26761			}
26762
26763			if (settings.content_security_policy) {
26764				self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />';
26765			}
26766
26767			self.iframeHTML += '</head><body id="' + bodyId +
26768				'" class="mce-content-body ' + bodyClass +
26769				'" data-id="' + self.id + '"><br></body></html>';
26770
26771			/*eslint no-script-url:0 */
26772			var domainRelaxUrl = 'javascript:(function(){' +
26773				'document.open();document.domain="' + document.domain + '";' +
26774				'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
26775				'document.close();ed.initContentBody(true);})()';
26776
26777			// Domain relaxing is required since the user has messed around with document.domain
26778			if (document.domain != location.hostname) {
26779				url = domainRelaxUrl;
26780			}
26781
26782			// Create iframe
26783			// TODO: ACC add the appropriate description on this.
26784			var ifr = DOM.create('iframe', {
26785				id: self.id + "_ifr",
26786				//src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
26787				frameBorder: '0',
26788				allowTransparency: "true",
26789				title: self.editorManager.translate(
26790						"Rich Text Area. Press ALT-F9 for menu. " +
26791						"Press ALT-F10 for toolbar. Press ALT-0 for help"
26792				),
26793				style: {
26794					width: '100%',
26795					height: h,
26796					display: 'block' // Important for Gecko to render the iframe correctly
26797				}
26798			});
26799
26800			ifr.onload = function() {
26801				ifr.onload = null;
26802				self.fire("load");
26803			};
26804
26805			DOM.setAttrib(ifr, "src", url || 'javascript:""');
26806
26807			self.contentAreaContainer = o.iframeContainer;
26808			self.iframeElement = ifr;
26809
26810			n = DOM.add(o.iframeContainer, ifr);
26811
26812			// Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
26813			// Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
26814			if (ie) {
26815				try {
26816					self.getDoc();
26817				} catch (e) {
26818					n.src = url = domainRelaxUrl;
26819				}
26820			}
26821
26822			if (o.editorContainer) {
26823				DOM.get(o.editorContainer).style.display = self.orgDisplay;
26824				self.hidden = DOM.isHidden(o.editorContainer);
26825			}
26826
26827			self.getElement().style.display = 'none';
26828			DOM.setAttrib(self.id, 'aria-hidden', true);
26829
26830			if (!url) {
26831				self.initContentBody();
26832			}
26833
26834			elm = n = o = null; // Cleanup
26835		},
26836
26837		/**
26838		 * This method get called by the init method ones the iframe is loaded.
26839		 * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
26840		 *
26841		 * @method initContentBody
26842		 * @private
26843		 */
26844		initContentBody: function(skipWrite) {
26845			var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText;
26846
26847			// Restore visibility on target element
26848			if (!settings.inline) {
26849				self.getElement().style.visibility = self.orgVisibility;
26850			}
26851
26852			// Setup iframe body
26853			if (!skipWrite && !settings.content_editable) {
26854				doc.open();
26855				doc.write(self.iframeHTML);
26856				doc.close();
26857			}
26858
26859			if (settings.content_editable) {
26860				self.on('remove', function() {
26861					var bodyEl = this.getBody();
26862
26863					DOM.removeClass(bodyEl, 'mce-content-body');
26864					DOM.removeClass(bodyEl, 'mce-edit-focus');
26865					DOM.setAttrib(bodyEl, 'contentEditable', null);
26866				});
26867
26868				DOM.addClass(targetElm, 'mce-content-body');
26869				self.contentDocument = doc = settings.content_document || document;
26870				self.contentWindow = settings.content_window || window;
26871				self.bodyElement = targetElm;
26872
26873				// Prevent leak in IE
26874				settings.content_document = settings.content_window = null;
26875
26876				// TODO: Fix this
26877				settings.root_name = targetElm.nodeName.toLowerCase();
26878			}
26879
26880			// It will not steal focus while setting contentEditable
26881			body = self.getBody();
26882			body.disabled = true;
26883
26884			if (!settings.readonly) {
26885				if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
26886					body.style.position = 'relative';
26887				}
26888
26889				body.contentEditable = self.getParam('content_editable_state', true);
26890			}
26891
26892			body.disabled = false;
26893
26894			/**
26895			 * Schema instance, enables you to validate elements and it's children.
26896			 *
26897			 * @property schema
26898			 * @type tinymce.html.Schema
26899			 */
26900			self.schema = new Schema(settings);
26901
26902			/**
26903			 * DOM instance for the editor.
26904			 *
26905			 * @property dom
26906			 * @type tinymce.dom.DOMUtils
26907			 * @example
26908			 * // Adds a class to all paragraphs within the editor
26909			 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
26910			 */
26911			self.dom = new DOMUtils(doc, {
26912				keep_values: true,
26913				url_converter: self.convertURL,
26914				url_converter_scope: self,
26915				hex_colors: settings.force_hex_style_colors,
26916				class_filter: settings.class_filter,
26917				update_styles: true,
26918				root_element: self.inline ? self.getBody() : null,
26919				collect: settings.content_editable,
26920				schema: self.schema,
26921				onSetAttrib: function(e) {
26922					self.fire('SetAttrib', e);
26923				}
26924			});
26925
26926			/**
26927			 * HTML parser will be used when contents is inserted into the editor.
26928			 *
26929			 * @property parser
26930			 * @type tinymce.html.DomParser
26931			 */
26932			self.parser = new DomParser(settings, self.schema);
26933
26934			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
26935			self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
26936				var i = nodes.length, node, dom = self.dom, value, internalName;
26937
26938				while (i--) {
26939					node = nodes[i];
26940					value = node.attr(name);
26941					internalName = 'data-mce-' + name;
26942
26943					// Add internal attribute if we need to we don't on a refresh of the document
26944					if (!node.attributes.map[internalName]) {
26945						if (name === "style") {
26946							value = dom.serializeStyle(dom.parseStyle(value), node.name);
26947
26948							if (!value.length) {
26949								value = null;
26950							}
26951
26952							node.attr(internalName, value);
26953							node.attr(name, value);
26954						} else if (name === "tabindex") {
26955							node.attr(internalName, value);
26956							node.attr(name, null);
26957						} else {
26958							node.attr(internalName, self.convertURL(value, name, node.name));
26959						}
26960					}
26961				}
26962			});
26963
26964			// Keep scripts from executing
26965			self.parser.addNodeFilter('script', function(nodes) {
26966				var i = nodes.length, node;
26967
26968				while (i--) {
26969					node = nodes[i];
26970					node.attr('type', 'mce-' + (node.attr('type') || 'no/type'));
26971				}
26972			});
26973
26974			self.parser.addNodeFilter('#cdata', function(nodes) {
26975				var i = nodes.length, node;
26976
26977				while (i--) {
26978					node = nodes[i];
26979					node.type = 8;
26980					node.name = '#comment';
26981					node.value = '[CDATA[' + node.value + ']]';
26982				}
26983			});
26984
26985			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
26986				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
26987
26988				while (i--) {
26989					node = nodes[i];
26990
26991					if (node.isEmpty(nonEmptyElements)) {
26992						node.append(new Node('br', 1)).shortEnded = true;
26993					}
26994				}
26995			});
26996
26997			/**
26998			 * DOM serializer for the editor. Will be used when contents is extracted from the editor.
26999			 *
27000			 * @property serializer
27001			 * @type tinymce.dom.Serializer
27002			 * @example
27003			 * // Serializes the first paragraph in the editor into a string
27004			 * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
27005			 */
27006			self.serializer = new DomSerializer(settings, self);
27007
27008			/**
27009			 * Selection instance for the editor.
27010			 *
27011			 * @property selection
27012			 * @type tinymce.dom.Selection
27013			 * @example
27014			 * // Sets some contents to the current selection in the editor
27015			 * tinymce.activeEditor.selection.setContent('Some contents');
27016			 *
27017			 * // Gets the current selection
27018			 * alert(tinymce.activeEditor.selection.getContent());
27019			 *
27020			 * // Selects the first paragraph found
27021			 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
27022			 */
27023			self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
27024
27025			/**
27026			 * Formatter instance.
27027			 *
27028			 * @property formatter
27029			 * @type tinymce.Formatter
27030			 */
27031			self.formatter = new Formatter(self);
27032
27033			/**
27034			 * Undo manager instance, responsible for handling undo levels.
27035			 *
27036			 * @property undoManager
27037			 * @type tinymce.UndoManager
27038			 * @example
27039			 * // Undoes the last modification to the editor
27040			 * tinymce.activeEditor.undoManager.undo();
27041			 */
27042			self.undoManager = new UndoManager(self);
27043
27044			self.forceBlocks = new ForceBlocks(self);
27045			self.enterKey = new EnterKey(self);
27046			self.editorCommands = new EditorCommands(self);
27047			self._nodeChangeDispatcher = new NodeChange(self);
27048
27049			self.fire('PreInit');
27050
27051			if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
27052				doc.body.spellcheck = false; // Gecko
27053				DOM.setAttrib(body, "spellcheck", "false");
27054			}
27055
27056			self.fire('PostRender');
27057
27058			self.quirks = new Quirks(self);
27059
27060			if (settings.directionality) {
27061				body.dir = settings.directionality;
27062			}
27063
27064			if (settings.nowrap) {
27065				body.style.whiteSpace = "nowrap";
27066			}
27067
27068			if (settings.protect) {
27069				self.on('BeforeSetContent', function(e) {
27070					each(settings.protect, function(pattern) {
27071						e.content = e.content.replace(pattern, function(str) {
27072							return '<!--mce:protected ' + escape(str) + '-->';
27073						});
27074					});
27075				});
27076			}
27077
27078			self.on('SetContent', function() {
27079				self.addVisual(self.getBody());
27080			});
27081
27082			// Remove empty contents
27083			if (settings.padd_empty_editor) {
27084				self.on('PostProcess', function(e) {
27085					e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
27086				});
27087			}
27088
27089			self.load({initial: true, format: 'html'});
27090			self.startContent = self.getContent({format: 'raw'});
27091
27092			/**
27093			 * Is set to true after the editor instance has been initialized
27094			 *
27095			 * @property initialized
27096			 * @type Boolean
27097			 * @example
27098			 * function isEditorInitialized(editor) {
27099			 *     return editor && editor.initialized;
27100			 * }
27101			 */
27102			self.initialized = true;
27103			self.bindPendingEventDelegates();
27104
27105			self.fire('init');
27106			self.focus(true);
27107			self.nodeChanged({initial: true});
27108			self.execCallback('init_instance_callback', self);
27109
27110			// Add editor specific CSS styles
27111			if (self.contentStyles.length > 0) {
27112				contentCssText = '';
27113
27114				each(self.contentStyles, function(style) {
27115					contentCssText += style + "\r\n";
27116				});
27117
27118				self.dom.addStyle(contentCssText);
27119			}
27120
27121			// Load specified content CSS last
27122			each(self.contentCSS, function(cssUrl) {
27123				if (!self.loadedCSS[cssUrl]) {
27124					self.dom.loadCSS(cssUrl);
27125					self.loadedCSS[cssUrl] = true;
27126				}
27127			});
27128
27129			// Handle auto focus
27130			if (settings.auto_focus) {
27131				setTimeout(function() {
27132					var editor;
27133
27134					if (settings.auto_focus === true) {
27135						editor = self;
27136					} else {
27137						editor = self.editorManager.get(settings.auto_focus);
27138					}
27139
27140					editor.focus();
27141				}, 100);
27142			}
27143
27144			// Clean up references for IE
27145			targetElm = doc = body = null;
27146		},
27147
27148		/**
27149		 * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
27150		 * it will also place DOM focus inside the editor.
27151		 *
27152		 * @method focus
27153		 * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
27154		 */
27155		focus: function(skipFocus) {
27156			var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
27157			var controlElm, doc = self.getDoc(), body;
27158
27159			if (!skipFocus) {
27160				// Get selected control element
27161				rng = selection.getRng();
27162				if (rng.item) {
27163					controlElm = rng.item(0);
27164				}
27165
27166				self._refreshContentEditable();
27167
27168				// Focus the window iframe
27169				if (!contentEditable) {
27170					// WebKit needs this call to fire focusin event properly see #5948
27171					// But Opera pre Blink engine will produce an empty selection so skip Opera
27172					if (!Env.opera) {
27173						self.getBody().focus();
27174					}
27175
27176					self.getWin().focus();
27177				}
27178
27179				// Focus the body as well since it's contentEditable
27180				if (isGecko || contentEditable) {
27181					body = self.getBody();
27182
27183					// Check for setActive since it doesn't scroll to the element
27184					if (body.setActive) {
27185						// IE 11 sometimes throws "Invalid function" then fallback to focus
27186						try {
27187							body.setActive();
27188						} catch (ex) {
27189							body.focus();
27190						}
27191					} else {
27192						body.focus();
27193					}
27194
27195					if (contentEditable) {
27196						selection.normalize();
27197					}
27198				}
27199
27200				// Restore selected control element
27201				// This is needed when for example an image is selected within a
27202				// layer a call to focus will then remove the control selection
27203				if (controlElm && controlElm.ownerDocument == doc) {
27204					rng = doc.body.createControlRange();
27205					rng.addElement(controlElm);
27206					rng.select();
27207				}
27208			}
27209
27210			self.editorManager.setActive(self);
27211		},
27212
27213		/**
27214		 * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
27215		 * There new event model is a better way to add callback so this method might be removed in the future.
27216		 *
27217		 * @method execCallback
27218		 * @param {String} name Name of the callback to execute.
27219		 * @return {Object} Return value passed from callback function.
27220		 */
27221		execCallback: function(name) {
27222			var self = this, callback = self.settings[name], scope;
27223
27224			if (!callback) {
27225				return;
27226			}
27227
27228			// Look through lookup
27229			if (self.callbackLookup && (scope = self.callbackLookup[name])) {
27230				callback = scope.func;
27231				scope = scope.scope;
27232			}
27233
27234			if (typeof(callback) === 'string') {
27235				scope = callback.replace(/\.\w+$/, '');
27236				scope = scope ? resolve(scope) : 0;
27237				callback = resolve(callback);
27238				self.callbackLookup = self.callbackLookup || {};
27239				self.callbackLookup[name] = {func: callback, scope: scope};
27240			}
27241
27242			return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
27243		},
27244
27245		/**
27246		 * Translates the specified string by replacing variables with language pack items it will also check if there is
27247		 * a key mathcin the input.
27248		 *
27249		 * @method translate
27250		 * @param {String} text String to translate by the language pack data.
27251		 * @return {String} Translated string.
27252		 */
27253		translate: function(text) {
27254			var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
27255
27256			if (!text) {
27257				return '';
27258			}
27259
27260			return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
27261				return i18n.data[lang + '.' + b] || '{#' + b + '}';
27262			});
27263		},
27264
27265		/**
27266		 * Returns a language pack item by name/key.
27267		 *
27268		 * @method getLang
27269		 * @param {String} name Name/key to get from the language pack.
27270		 * @param {String} defaultVal Optional default value to retrive.
27271		 */
27272		getLang: function(name, defaultVal) {
27273			return (
27274				this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
27275				(defaultVal !== undefined ? defaultVal : '{#' + name + '}')
27276			);
27277		},
27278
27279		/**
27280		 * Returns a configuration parameter by name.
27281		 *
27282		 * @method getParam
27283		 * @param {String} name Configruation parameter to retrive.
27284		 * @param {String} defaultVal Optional default value to return.
27285		 * @param {String} type Optional type parameter.
27286		 * @return {String} Configuration parameter value or default value.
27287		 * @example
27288		 * // Returns a specific config value from the currently active editor
27289		 * var someval = tinymce.activeEditor.getParam('myvalue');
27290		 *
27291		 * // Returns a specific config value from a specific editor instance by id
27292		 * var someval2 = tinymce.get('my_editor').getParam('myvalue');
27293		 */
27294		getParam: function(name, defaultVal, type) {
27295			var value = name in this.settings ? this.settings[name] : defaultVal, output;
27296
27297			if (type === 'hash') {
27298				output = {};
27299
27300				if (typeof(value) === 'string') {
27301					each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
27302						value = value.split('=');
27303
27304						if (value.length > 1) {
27305							output[trim(value[0])] = trim(value[1]);
27306						} else {
27307							output[trim(value[0])] = trim(value);
27308						}
27309					});
27310				} else {
27311					output = value;
27312				}
27313
27314				return output;
27315			}
27316
27317			return value;
27318		},
27319
27320		/**
27321		 * Distpaches out a onNodeChange event to all observers. This method should be called when you
27322		 * need to update the UI states or element path etc.
27323		 *
27324		 * @method nodeChanged
27325		 * @param {Object} args Optional args to pass to NodeChange event handlers.
27326		 */
27327		nodeChanged: function(args) {
27328			this._nodeChangeDispatcher.nodeChanged(args);
27329		},
27330
27331		/**
27332		 * Adds a button that later gets created by the theme in the editors toolbars.
27333		 *
27334		 * @method addButton
27335		 * @param {String} name Button name to add.
27336		 * @param {Object} settings Settings object with title, cmd etc.
27337		 * @example
27338		 * // Adds a custom button to the editor that inserts contents when clicked
27339		 * tinymce.init({
27340		 *    ...
27341		 *
27342		 *    toolbar: 'example'
27343		 *
27344		 *    setup: function(ed) {
27345		 *       ed.addButton('example', {
27346		 *          title: 'My title',
27347		 *          image: '../js/tinymce/plugins/example/img/example.gif',
27348		 *          onclick: function() {
27349		 *             ed.insertContent('Hello world!!');
27350		 *          }
27351		 *       });
27352		 *    }
27353		 * });
27354		 */
27355		addButton: function(name, settings) {
27356			var self = this;
27357
27358			if (settings.cmd) {
27359				settings.onclick = function() {
27360					self.execCommand(settings.cmd);
27361				};
27362			}
27363
27364			if (!settings.text && !settings.icon) {
27365				settings.icon = name;
27366			}
27367
27368			self.buttons = self.buttons || {};
27369			settings.tooltip = settings.tooltip || settings.title;
27370			self.buttons[name] = settings;
27371		},
27372
27373		/**
27374		 * Adds a menu item to be used in the menus of the theme. There might be multiple instances
27375		 * of this menu item for example it might be used in the main menus of the theme but also in
27376		 * the context menu so make sure that it's self contained and supports multiple instances.
27377		 *
27378		 * @method addMenuItem
27379		 * @param {String} name Menu item name to add.
27380		 * @param {Object} settings Settings object with title, cmd etc.
27381		 * @example
27382		 * // Adds a custom menu item to the editor that inserts contents when clicked
27383		 * // The context option allows you to add the menu item to an existing default menu
27384		 * tinymce.init({
27385		 *    ...
27386		 *
27387		 *    setup: function(ed) {
27388		 *       ed.addMenuItem('example', {
27389		 *          text: 'My menu item',
27390		 *          context: 'tools',
27391		 *          onclick: function() {
27392		 *             ed.insertContent('Hello world!!');
27393		 *          }
27394		 *       });
27395		 *    }
27396		 * });
27397		 */
27398		addMenuItem: function(name, settings) {
27399			var self = this;
27400
27401			if (settings.cmd) {
27402				settings.onclick = function() {
27403					self.execCommand(settings.cmd);
27404				};
27405			}
27406
27407			self.menuItems = self.menuItems || {};
27408			self.menuItems[name] = settings;
27409		},
27410
27411		/**
27412		 * Adds a custom command to the editor, you can also override existing commands with this method.
27413		 * The command that you add can be executed with execCommand.
27414		 *
27415		 * @method addCommand
27416		 * @param {String} name Command name to add/override.
27417		 * @param {addCommandCallback} callback Function to execute when the command occurs.
27418		 * @param {Object} scope Optional scope to execute the function in.
27419		 * @example
27420		 * // Adds a custom command that later can be executed using execCommand
27421		 * tinymce.init({
27422		 *    ...
27423		 *
27424		 *    setup: function(ed) {
27425		 *       // Register example command
27426		 *       ed.addCommand('mycommand', function(ui, v) {
27427		 *          ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
27428		 *       });
27429		 *    }
27430		 * });
27431		 */
27432		addCommand: function(name, callback, scope) {
27433			/**
27434			 * Callback function that gets called when a command is executed.
27435			 *
27436			 * @callback addCommandCallback
27437			 * @param {Boolean} ui Display UI state true/false.
27438			 * @param {Object} value Optional value for command.
27439			 * @return {Boolean} True/false state if the command was handled or not.
27440			 */
27441			this.execCommands[name] = {func: callback, scope: scope || this};
27442		},
27443
27444		/**
27445		 * Adds a custom query state command to the editor, you can also override existing commands with this method.
27446		 * The command that you add can be executed with queryCommandState function.
27447		 *
27448		 * @method addQueryStateHandler
27449		 * @param {String} name Command name to add/override.
27450		 * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
27451		 * @param {Object} scope Optional scope to execute the function in.
27452		 */
27453		addQueryStateHandler: function(name, callback, scope) {
27454			/**
27455			 * Callback function that gets called when a queryCommandState is executed.
27456			 *
27457			 * @callback addQueryStateHandlerCallback
27458			 * @return {Boolean} True/false state if the command is enabled or not like is it bold.
27459			 */
27460			this.queryStateCommands[name] = {func: callback, scope: scope || this};
27461		},
27462
27463		/**
27464		 * Adds a custom query value command to the editor, you can also override existing commands with this method.
27465		 * The command that you add can be executed with queryCommandValue function.
27466		 *
27467		 * @method addQueryValueHandler
27468		 * @param {String} name Command name to add/override.
27469		 * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
27470		 * @param {Object} scope Optional scope to execute the function in.
27471		 */
27472		addQueryValueHandler: function(name, callback, scope) {
27473			/**
27474			 * Callback function that gets called when a queryCommandValue is executed.
27475			 *
27476			 * @callback addQueryValueHandlerCallback
27477			 * @return {Object} Value of the command or undefined.
27478			 */
27479			this.queryValueCommands[name] = {func: callback, scope: scope || this};
27480		},
27481
27482		/**
27483		 * Adds a keyboard shortcut for some command or function.
27484		 *
27485		 * @method addShortcut
27486		 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
27487		 * @param {String} desc Text description for the command.
27488		 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
27489		 * @param {Object} sc Optional scope to execute the function in.
27490		 * @return {Boolean} true/false state if the shortcut was added or not.
27491		 */
27492		addShortcut: function(pattern, desc, cmdFunc, scope) {
27493			this.shortcuts.add(pattern, desc, cmdFunc, scope);
27494		},
27495
27496		/**
27497		 * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
27498		 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
27499		 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
27500		 * return true it will handle the command as a internal browser command.
27501		 *
27502		 * @method execCommand
27503		 * @param {String} cmd Command name to execute, for example mceLink or Bold.
27504		 * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
27505		 * @param {mixed} value Optional command value, this can be anything.
27506		 * @param {Object} a Optional arguments object.
27507		 */
27508		execCommand: function(cmd, ui, value, args) {
27509			var self = this, state = 0, cmdItem;
27510
27511			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
27512				self.focus();
27513			}
27514
27515			args = extend({}, args);
27516			args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
27517			if (args.isDefaultPrevented()) {
27518				return false;
27519			}
27520
27521			// Registred commands
27522			if ((cmdItem = self.execCommands[cmd])) {
27523				// Fall through on true
27524				if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
27525					self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27526					return true;
27527				}
27528			}
27529
27530			// Plugin commands
27531			each(self.plugins, function(p) {
27532				if (p.execCommand && p.execCommand(cmd, ui, value)) {
27533					self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27534					state = true;
27535					return false;
27536				}
27537			});
27538
27539			if (state) {
27540				return state;
27541			}
27542
27543			// Theme commands
27544			if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
27545				self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27546				return true;
27547			}
27548
27549			// Editor commands
27550			if (self.editorCommands.execCommand(cmd, ui, value)) {
27551				self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27552				return true;
27553			}
27554
27555			// Browser commands
27556			try {
27557				state = self.getDoc().execCommand(cmd, ui, value);
27558			} catch (ex) {
27559				// Ignore old IE errors
27560			}
27561
27562			if (state) {
27563				self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
27564				return true;
27565			}
27566
27567			return false;
27568		},
27569
27570		/**
27571		 * Returns a command specific state, for example if bold is enabled or not.
27572		 *
27573		 * @method queryCommandState
27574		 * @param {string} cmd Command to query state from.
27575		 * @return {Boolean} Command specific state, for example if bold is enabled or not.
27576		 */
27577		queryCommandState: function(cmd) {
27578			var self = this, queryItem, returnVal;
27579
27580			// Is hidden then return undefined
27581			if (self._isHidden()) {
27582				return;
27583			}
27584
27585			// Registred commands
27586			if ((queryItem = self.queryStateCommands[cmd])) {
27587				returnVal = queryItem.func.call(queryItem.scope);
27588
27589				// Fall though on non boolean returns
27590				if (returnVal === true || returnVal === false) {
27591					return returnVal;
27592				}
27593			}
27594
27595			// Editor commands
27596			returnVal = self.editorCommands.queryCommandState(cmd);
27597			if (returnVal !== -1) {
27598				return returnVal;
27599			}
27600
27601			// Browser commands
27602			try {
27603				return self.getDoc().queryCommandState(cmd);
27604			} catch (ex) {
27605				// Fails sometimes see bug: 1896577
27606			}
27607		},
27608
27609		/**
27610		 * Returns a command specific value, for example the current font size.
27611		 *
27612		 * @method queryCommandValue
27613		 * @param {string} cmd Command to query value from.
27614		 * @return {Object} Command specific value, for example the current font size.
27615		 */
27616		queryCommandValue: function(cmd) {
27617			var self = this, queryItem, returnVal;
27618
27619			// Is hidden then return undefined
27620			if (self._isHidden()) {
27621				return;
27622			}
27623
27624			// Registred commands
27625			if ((queryItem = self.queryValueCommands[cmd])) {
27626				returnVal = queryItem.func.call(queryItem.scope);
27627
27628				// Fall though on true
27629				if (returnVal !== true) {
27630					return returnVal;
27631				}
27632			}
27633
27634			// Editor commands
27635			returnVal = self.editorCommands.queryCommandValue(cmd);
27636			if (returnVal !== undefined) {
27637				return returnVal;
27638			}
27639
27640			// Browser commands
27641			try {
27642				return self.getDoc().queryCommandValue(cmd);
27643			} catch (ex) {
27644				// Fails sometimes see bug: 1896577
27645			}
27646		},
27647
27648		/**
27649		 * Shows the editor and hides any textarea/div that the editor is supposed to replace.
27650		 *
27651		 * @method show
27652		 */
27653		show: function() {
27654			var self = this;
27655
27656			if (self.hidden) {
27657				self.hidden = false;
27658
27659				if (self.inline) {
27660					self.getBody().contentEditable = true;
27661				} else {
27662					DOM.show(self.getContainer());
27663					DOM.hide(self.id);
27664				}
27665
27666				self.load();
27667				self.fire('show');
27668			}
27669		},
27670
27671		/**
27672		 * Hides the editor and shows any textarea/div that the editor is supposed to replace.
27673		 *
27674		 * @method hide
27675		 */
27676		hide: function() {
27677			var self = this, doc = self.getDoc();
27678
27679			if (!self.hidden) {
27680				// Fixed bug where IE has a blinking cursor left from the editor
27681				if (ie && doc && !self.inline) {
27682					doc.execCommand('SelectAll');
27683				}
27684
27685				// We must save before we hide so Safari doesn't crash
27686				self.save();
27687
27688				if (self.inline) {
27689					self.getBody().contentEditable = false;
27690
27691					// Make sure the editor gets blurred
27692					if (self == self.editorManager.focusedEditor) {
27693						self.editorManager.focusedEditor = null;
27694					}
27695				} else {
27696					DOM.hide(self.getContainer());
27697					DOM.setStyle(self.id, 'display', self.orgDisplay);
27698				}
27699
27700				self.hidden = true;
27701				self.fire('hide');
27702			}
27703		},
27704
27705		/**
27706		 * Returns true/false if the editor is hidden or not.
27707		 *
27708		 * @method isHidden
27709		 * @return {Boolean} True/false if the editor is hidden or not.
27710		 */
27711		isHidden: function() {
27712			return !!this.hidden;
27713		},
27714
27715		/**
27716		 * Sets the progress state, this will display a throbber/progess for the editor.
27717		 * This is ideal for asycronous operations like an AJAX save call.
27718		 *
27719		 * @method setProgressState
27720		 * @param {Boolean} state Boolean state if the progress should be shown or hidden.
27721		 * @param {Number} time Optional time to wait before the progress gets shown.
27722		 * @return {Boolean} Same as the input state.
27723		 * @example
27724		 * // Show progress for the active editor
27725		 * tinymce.activeEditor.setProgressState(true);
27726		 *
27727		 * // Hide progress for the active editor
27728		 * tinymce.activeEditor.setProgressState(false);
27729		 *
27730		 * // Show progress after 3 seconds
27731		 * tinymce.activeEditor.setProgressState(true, 3000);
27732		 */
27733		setProgressState: function(state, time) {
27734			this.fire('ProgressState', {state: state, time: time});
27735		},
27736
27737		/**
27738		 * Loads contents from the textarea or div element that got converted into an editor instance.
27739		 * This method will move the contents from that textarea or div into the editor by using setContent
27740		 * so all events etc that method has will get dispatched as well.
27741		 *
27742		 * @method load
27743		 * @param {Object} args Optional content object, this gets passed around through the whole load process.
27744		 * @return {String} HTML string that got set into the editor.
27745		 */
27746		load: function(args) {
27747			var self = this, elm = self.getElement(), html;
27748
27749			if (elm) {
27750				args = args || {};
27751				args.load = true;
27752
27753				html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
27754				args.element = elm;
27755
27756				if (!args.no_events) {
27757					self.fire('LoadContent', args);
27758				}
27759
27760				args.element = elm = null;
27761
27762				return html;
27763			}
27764		},
27765
27766		/**
27767		 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
27768		 * This method will move the HTML contents from the editor into that textarea or div by getContent
27769		 * so all events etc that method has will get dispatched as well.
27770		 *
27771		 * @method save
27772		 * @param {Object} args Optional content object, this gets passed around through the whole save process.
27773		 * @return {String} HTML string that got set into the textarea/div.
27774		 */
27775		save: function(args) {
27776			var self = this, elm = self.getElement(), html, form;
27777
27778			if (!elm || !self.initialized) {
27779				return;
27780			}
27781
27782			args = args || {};
27783			args.save = true;
27784
27785			args.element = elm;
27786			html = args.content = self.getContent(args);
27787
27788			if (!args.no_events) {
27789				self.fire('SaveContent', args);
27790			}
27791
27792			html = args.content;
27793
27794			if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
27795				// Update DIV element when not in inline mode
27796				if (!self.inline) {
27797					elm.innerHTML = html;
27798				}
27799
27800				// Update hidden form element
27801				if ((form = DOM.getParent(self.id, 'form'))) {
27802					each(form.elements, function(elm) {
27803						if (elm.name == self.id) {
27804							elm.value = html;
27805							return false;
27806						}
27807					});
27808				}
27809			} else {
27810				elm.value = html;
27811			}
27812
27813			args.element = elm = null;
27814
27815			if (args.set_dirty !== false) {
27816				self.isNotDirty = true;
27817			}
27818
27819			return html;
27820		},
27821
27822		/**
27823		 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
27824		 * the different cleanup rules options.
27825		 *
27826		 * @method setContent
27827		 * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
27828		 * @param {Object} args Optional content object, this gets passed around through the whole set process.
27829		 * @return {String} HTML string that got set into the editor.
27830		 * @example
27831		 * // Sets the HTML contents of the activeEditor editor
27832		 * tinymce.activeEditor.setContent('<span>some</span> html');
27833		 *
27834		 * // Sets the raw contents of the activeEditor editor
27835		 * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
27836		 *
27837		 * // Sets the content of a specific editor (my_editor in this example)
27838		 * tinymce.get('my_editor').setContent(data);
27839		 *
27840		 * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
27841		 * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
27842		 */
27843		setContent: function(content, args) {
27844			var self = this, body = self.getBody(), forcedRootBlockName;
27845
27846			// Setup args object
27847			args = args || {};
27848			args.format = args.format || 'html';
27849			args.set = true;
27850			args.content = content;
27851
27852			// Do preprocessing
27853			if (!args.no_events) {
27854				self.fire('BeforeSetContent', args);
27855			}
27856
27857			content = args.content;
27858
27859			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
27860			// It will also be impossible to place the caret in the editor unless there is a BR element present
27861			if (content.length === 0 || /^\s+$/.test(content)) {
27862				forcedRootBlockName = self.settings.forced_root_block;
27863
27864				// Check if forcedRootBlock is configured and that the block is a valid child of the body
27865				if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
27866					// Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
27867					content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
27868					content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
27869				} else if (!ie) {
27870					// We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
27871					content = '<br data-mce-bogus="1">';
27872				}
27873
27874				self.dom.setHTML(body, content);
27875
27876				self.fire('SetContent', args);
27877			} else {
27878				// Parse and serialize the html
27879				if (args.format !== 'raw') {
27880					content = new Serializer({}, self.schema).serialize(
27881						self.parser.parse(content, {isRootContent: true})
27882					);
27883				}
27884
27885				// Set the new cleaned contents to the editor
27886				args.content = trim(content);
27887				self.dom.setHTML(body, args.content);
27888
27889				// Do post processing
27890				if (!args.no_events) {
27891					self.fire('SetContent', args);
27892				}
27893
27894				// Don't normalize selection if the focused element isn't the body in
27895				// content editable mode since it will steal focus otherwise
27896				/*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
27897					self.selection.normalize();
27898				}*/
27899			}
27900
27901			return args.content;
27902		},
27903
27904		/**
27905		 * Gets the content from the editor instance, this will cleanup the content before it gets returned using
27906		 * the different cleanup rules options.
27907		 *
27908		 * @method getContent
27909		 * @param {Object} args Optional content object, this gets passed around through the whole get process.
27910		 * @return {String} Cleaned content string, normally HTML contents.
27911		 * @example
27912		 * // Get the HTML contents of the currently active editor
27913		 * console.debug(tinymce.activeEditor.getContent());
27914		 *
27915		 * // Get the raw contents of the currently active editor
27916		 * tinymce.activeEditor.getContent({format: 'raw'});
27917		 *
27918		 * // Get content of a specific editor:
27919		 * tinymce.get('content id').getContent()
27920		 */
27921		getContent: function(args) {
27922			var self = this, content, body = self.getBody();
27923
27924			// Setup args object
27925			args = args || {};
27926			args.format = args.format || 'html';
27927			args.get = true;
27928			args.getInner = true;
27929
27930			// Do preprocessing
27931			if (!args.no_events) {
27932				self.fire('BeforeGetContent', args);
27933			}
27934
27935			// Get raw contents or by default the cleaned contents
27936			if (args.format == 'raw') {
27937				content = body.innerHTML;
27938			} else if (args.format == 'text') {
27939				content = body.innerText || body.textContent;
27940			} else {
27941				content = self.serializer.serialize(body, args);
27942			}
27943
27944			// Trim whitespace in beginning/end of HTML
27945			if (args.format != 'text') {
27946				args.content = trim(content);
27947			} else {
27948				args.content = content;
27949			}
27950
27951			// Do post processing
27952			if (!args.no_events) {
27953				self.fire('GetContent', args);
27954			}
27955
27956			return args.content;
27957		},
27958
27959		/**
27960		 * Inserts content at caret position.
27961		 *
27962		 * @method insertContent
27963		 * @param {String} content Content to insert.
27964		 * @param {Object} args Optional args to pass to insert call.
27965		 */
27966		insertContent: function(content, args) {
27967			if (args) {
27968				content = extend({content: content}, args);
27969			}
27970
27971			this.execCommand('mceInsertContent', false, content);
27972		},
27973
27974		/**
27975		 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
27976		 *
27977		 * @method isDirty
27978		 * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
27979		 * @example
27980		 * if (tinymce.activeEditor.isDirty())
27981		 *     alert("You must save your contents.");
27982		 */
27983		isDirty: function() {
27984			return !this.isNotDirty;
27985		},
27986
27987		/**
27988		 * Returns the editors container element. The container element wrappes in
27989		 * all the elements added to the page for the editor. Such as UI, iframe etc.
27990		 *
27991		 * @method getContainer
27992		 * @return {Element} HTML DOM element for the editor container.
27993		 */
27994		getContainer: function() {
27995			var self = this;
27996
27997			if (!self.container) {
27998				self.container = DOM.get(self.editorContainer || self.id + '_parent');
27999			}
28000
28001			return self.container;
28002		},
28003
28004		/**
28005		 * Returns the editors content area container element. The this element is the one who
28006		 * holds the iframe or the editable element.
28007		 *
28008		 * @method getContentAreaContainer
28009		 * @return {Element} HTML DOM element for the editor area container.
28010		 */
28011		getContentAreaContainer: function() {
28012			return this.contentAreaContainer;
28013		},
28014
28015		/**
28016		 * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
28017		 *
28018		 * @method getElement
28019		 * @return {Element} HTML DOM element for the replaced element.
28020		 */
28021		getElement: function() {
28022			if (!this.targetElm) {
28023				this.targetElm = DOM.get(this.id);
28024			}
28025
28026			return this.targetElm;
28027		},
28028
28029		/**
28030		 * Returns the iframes window object.
28031		 *
28032		 * @method getWin
28033		 * @return {Window} Iframe DOM window object.
28034		 */
28035		getWin: function() {
28036			var self = this, elm;
28037
28038			if (!self.contentWindow) {
28039				elm = self.iframeElement;
28040
28041				if (elm) {
28042					self.contentWindow = elm.contentWindow;
28043				}
28044			}
28045
28046			return self.contentWindow;
28047		},
28048
28049		/**
28050		 * Returns the iframes document object.
28051		 *
28052		 * @method getDoc
28053		 * @return {Document} Iframe DOM document object.
28054		 */
28055		getDoc: function() {
28056			var self = this, win;
28057
28058			if (!self.contentDocument) {
28059				win = self.getWin();
28060
28061				if (win) {
28062					self.contentDocument = win.document;
28063				}
28064			}
28065
28066			return self.contentDocument;
28067		},
28068
28069		/**
28070		 * Returns the iframes body element.
28071		 *
28072		 * @method getBody
28073		 * @return {Element} Iframe body element.
28074		 */
28075		getBody: function() {
28076			return this.bodyElement || this.getDoc().body;
28077		},
28078
28079		/**
28080		 * URL converter function this gets executed each time a user adds an img, a or
28081		 * any other element that has a URL in it. This will be called both by the DOM and HTML
28082		 * manipulation functions.
28083		 *
28084		 * @method convertURL
28085		 * @param {string} url URL to convert.
28086		 * @param {string} name Attribute name src, href etc.
28087		 * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
28088		 * @return {string} Converted URL string.
28089		 */
28090		convertURL: function(url, name, elm) {
28091			var self = this, settings = self.settings;
28092
28093			// Use callback instead
28094			if (settings.urlconverter_callback) {
28095				return self.execCallback('urlconverter_callback', url, elm, true, name);
28096			}
28097
28098			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
28099			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
28100				return url;
28101			}
28102
28103			// Convert to relative
28104			if (settings.relative_urls) {
28105				return self.documentBaseURI.toRelative(url);
28106			}
28107
28108			// Convert to absolute
28109			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
28110
28111			return url;
28112		},
28113
28114		/**
28115		 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
28116		 *
28117		 * @method addVisual
28118		 * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
28119		 */
28120		addVisual: function(elm) {
28121			var self = this, settings = self.settings, dom = self.dom, cls;
28122
28123			elm = elm || self.getBody();
28124
28125			if (self.hasVisual === undefined) {
28126				self.hasVisual = settings.visual;
28127			}
28128
28129			each(dom.select('table,a', elm), function(elm) {
28130				var value;
28131
28132				switch (elm.nodeName) {
28133					case 'TABLE':
28134						cls = settings.visual_table_class || 'mce-item-table';
28135						value = dom.getAttrib(elm, 'border');
28136
28137						if ((!value || value == '0') && self.hasVisual) {
28138							dom.addClass(elm, cls);
28139						} else {
28140							dom.removeClass(elm, cls);
28141						}
28142
28143						return;
28144
28145					case 'A':
28146						if (!dom.getAttrib(elm, 'href', false)) {
28147							value = dom.getAttrib(elm, 'name') || elm.id;
28148							cls = settings.visual_anchor_class || 'mce-item-anchor';
28149
28150							if (value && self.hasVisual) {
28151								dom.addClass(elm, cls);
28152							} else {
28153								dom.removeClass(elm, cls);
28154							}
28155						}
28156
28157						return;
28158				}
28159			});
28160
28161			self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
28162		},
28163
28164		/**
28165		 * Removes the editor from the dom and tinymce collection.
28166		 *
28167		 * @method remove
28168		 */
28169		remove: function() {
28170			var self = this;
28171
28172			if (!self.removed) {
28173				self.save();
28174				self.removed = 1;
28175				self.unbindAllNativeEvents();
28176
28177				// Remove any hidden input
28178				if (self.hasHiddenInput) {
28179					DOM.remove(self.getElement().nextSibling);
28180				}
28181
28182				if (!self.inline) {
28183					// IE 9 has a bug where the selection stops working if you place the
28184					// caret inside the editor then remove the iframe
28185					if (ie && ie < 10) {
28186						self.getDoc().execCommand('SelectAll', false, null);
28187					}
28188
28189					DOM.setStyle(self.id, 'display', self.orgDisplay);
28190					self.getBody().onload = null; // Prevent #6816
28191				}
28192
28193				self.fire('remove');
28194
28195				self.editorManager.remove(self);
28196				DOM.remove(self.getContainer());
28197				self.destroy();
28198			}
28199		},
28200
28201		/**
28202		 * Destroys the editor instance by removing all events, element references or other resources
28203		 * that could leak memory. This method will be called automatically when the page is unloaded
28204		 * but you can also call it directly if you know what you are doing.
28205		 *
28206		 * @method destroy
28207		 * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
28208		 */
28209		destroy: function(automatic) {
28210			var self = this, form;
28211
28212			// One time is enough
28213			if (self.destroyed) {
28214				return;
28215			}
28216
28217			// If user manually calls destroy and not remove
28218			// Users seems to have logic that calls destroy instead of remove
28219			if (!automatic && !self.removed) {
28220				self.remove();
28221				return;
28222			}
28223
28224			if (!automatic) {
28225				self.editorManager.off('beforeunload', self._beforeUnload);
28226
28227				// Manual destroy
28228				if (self.theme && self.theme.destroy) {
28229					self.theme.destroy();
28230				}
28231
28232				// Destroy controls, selection and dom
28233				self.selection.destroy();
28234				self.dom.destroy();
28235			}
28236
28237			form = self.formElement;
28238			if (form) {
28239				if (form._mceOldSubmit) {
28240					form.submit = form._mceOldSubmit;
28241					form._mceOldSubmit = null;
28242				}
28243
28244				DOM.unbind(form, 'submit reset', self.formEventDelegate);
28245			}
28246
28247			self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
28248			self.bodyElement = self.contentDocument = self.contentWindow = null;
28249			self.iframeElement = self.targetElm = null;
28250
28251			if (self.selection) {
28252				self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
28253			}
28254
28255			self.destroyed = 1;
28256		},
28257
28258		// Internal functions
28259
28260		_refreshContentEditable: function() {
28261			var self = this, body, parent;
28262
28263			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
28264			if (self._isHidden()) {
28265				body = self.getBody();
28266				parent = body.parentNode;
28267
28268				parent.removeChild(body);
28269				parent.appendChild(body);
28270
28271				body.focus();
28272			}
28273		},
28274
28275		_isHidden: function() {
28276			var sel;
28277
28278			if (!isGecko) {
28279				return 0;
28280			}
28281
28282			// Weird, wheres that cursor selection?
28283			sel = this.selection.getSel();
28284			return (!sel || !sel.rangeCount || sel.rangeCount === 0);
28285		}
28286	};
28287
28288	extend(Editor.prototype, EditorObservable);
28289
28290	return Editor;
28291});
28292
28293// Included from: js/tinymce/classes/util/I18n.js
28294
28295/**
28296 * I18n.js
28297 *
28298 * Copyright, Moxiecode Systems AB
28299 * Released under LGPL License.
28300 *
28301 * License: http://www.tinymce.com/license
28302 * Contributing: http://www.tinymce.com/contributing
28303 */
28304
28305/**
28306 * I18n class that handles translation of TinyMCE UI.
28307 * Uses po style with csharp style parameters.
28308 *
28309 * @class tinymce.util.I18n
28310 */
28311define("tinymce/util/I18n", [], function() {
28312	"use strict";
28313
28314	var data = {};
28315
28316	return {
28317		/**
28318		 * Property gets set to true if a RTL language pack was loaded.
28319		 *
28320		 * @property rtl
28321		 * @type Boolean
28322		 */
28323		rtl: false,
28324
28325		/**
28326		 * Adds translations for a specific language code.
28327		 *
28328		 * @method add
28329		 * @param {String} code Language code like sv_SE.
28330		 * @param {Array} items Name/value array with English en_US to sv_SE.
28331		 */
28332		add: function(code, items) {
28333			for (var name in items) {
28334				data[name] = items[name];
28335			}
28336
28337			this.rtl = this.rtl || data._dir === 'rtl';
28338		},
28339
28340		/**
28341		 * Translates the specified text.
28342		 *
28343		 * It has a few formats:
28344		 * I18n.translate("Text");
28345		 * I18n.translate(["Text {0}/{1}", 0, 1]);
28346		 * I18n.translate({raw: "Raw string"});
28347		 *
28348		 * @method translate
28349		 * @param {String/Object/Array} text Text to translate.
28350		 * @return {String} String that got translated.
28351		 */
28352		translate: function(text) {
28353			if (typeof(text) == "undefined") {
28354				return text;
28355			}
28356
28357			if (typeof(text) != "string" && text.raw) {
28358				return text.raw;
28359			}
28360
28361			if (text.push) {
28362				var values = text.slice(1);
28363
28364				text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) {
28365					return values[match2];
28366				});
28367			}
28368
28369			return data[text] || text;
28370		},
28371
28372		data: data
28373	};
28374});
28375
28376// Included from: js/tinymce/classes/FocusManager.js
28377
28378/**
28379 * FocusManager.js
28380 *
28381 * Copyright, Moxiecode Systems AB
28382 * Released under LGPL License.
28383 *
28384 * License: http://www.tinymce.com/license
28385 * Contributing: http://www.tinymce.com/contributing
28386 */
28387
28388/**
28389 * This class manages the focus/blur state of the editor. This class is needed since some
28390 * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
28391 *
28392 * This class will fire two events focus and blur on the editor instances that got affected.
28393 * It will also handle the restore of selection when the focus is lost and returned.
28394 *
28395 * @class tinymce.FocusManager
28396 */
28397define("tinymce/FocusManager", [
28398	"tinymce/dom/DOMUtils",
28399	"tinymce/Env"
28400], function(DOMUtils, Env) {
28401	var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
28402
28403	/**
28404	 * Constructs a new focus manager instance.
28405	 *
28406	 * @constructor FocusManager
28407	 * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
28408	 */
28409	function FocusManager(editorManager) {
28410		function getActiveElement() {
28411			try {
28412				return document.activeElement;
28413			} catch (ex) {
28414				// IE sometimes fails to get the activeElement when resizing table
28415				// TODO: Investigate this
28416				return document.body;
28417			}
28418		}
28419
28420		// We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
28421		// TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
28422		function createBookmark(dom, rng) {
28423			if (rng && rng.startContainer) {
28424				// Verify that the range is within the root of the editor
28425				if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
28426					return;
28427				}
28428
28429				return {
28430					startContainer: rng.startContainer,
28431					startOffset: rng.startOffset,
28432					endContainer: rng.endContainer,
28433					endOffset: rng.endOffset
28434				};
28435			}
28436
28437			return rng;
28438		}
28439
28440		function bookmarkToRng(editor, bookmark) {
28441			var rng;
28442
28443			if (bookmark.startContainer) {
28444				rng = editor.getDoc().createRange();
28445				rng.setStart(bookmark.startContainer, bookmark.startOffset);
28446				rng.setEnd(bookmark.endContainer, bookmark.endOffset);
28447			} else {
28448				rng = bookmark;
28449			}
28450
28451			return rng;
28452		}
28453
28454		function isUIElement(elm) {
28455			return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
28456		}
28457
28458		function registerEvents(e) {
28459			var editor = e.editor;
28460
28461			editor.on('init', function() {
28462				// Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
28463				if (editor.inline || Env.ie) {
28464					// Use the onbeforedeactivate event when available since it works better see #7023
28465					if ("onbeforedeactivate" in document && Env.ie < 9) {
28466						editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
28467							if (e.target != editor.getBody()) {
28468								return;
28469							}
28470
28471							try {
28472								editor.lastRng = editor.selection.getRng();
28473							} catch (ex) {
28474								// IE throws "Unexcpected call to method or property access" some times so lets ignore it
28475							}
28476						});
28477					} else {
28478						// On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
28479						editor.on('nodechange mouseup keyup', function(e) {
28480							var node = getActiveElement();
28481
28482							// Only act on manual nodechanges
28483							if (e.type == 'nodechange' && e.selectionChange) {
28484								return;
28485							}
28486
28487							// IE 11 reports active element as iframe not body of iframe
28488							if (node && node.id == editor.id + '_ifr') {
28489								node = editor.getBody();
28490							}
28491
28492							if (editor.dom.isChildOf(node, editor.getBody())) {
28493								editor.lastRng = editor.selection.getRng();
28494							}
28495						});
28496					}
28497
28498					// Handles the issue with WebKit not retaining selection within inline document
28499					// If the user releases the mouse out side the body since a mouse up event wont occur on the body
28500					if (Env.webkit && !selectionChangeHandler) {
28501						selectionChangeHandler = function() {
28502							var activeEditor = editorManager.activeEditor;
28503
28504							if (activeEditor && activeEditor.selection) {
28505								var rng = activeEditor.selection.getRng();
28506
28507								// Store when it's non collapsed
28508								if (rng && !rng.collapsed) {
28509									editor.lastRng = rng;
28510								}
28511							}
28512						};
28513
28514						DOM.bind(document, 'selectionchange', selectionChangeHandler);
28515					}
28516				}
28517			});
28518
28519			editor.on('setcontent', function() {
28520				editor.lastRng = null;
28521			});
28522
28523			// Remove last selection bookmark on mousedown see #6305
28524			editor.on('mousedown', function() {
28525				editor.selection.lastFocusBookmark = null;
28526			});
28527
28528			editor.on('focusin', function() {
28529				var focusedEditor = editorManager.focusedEditor;
28530
28531				if (editor.selection.lastFocusBookmark) {
28532					editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
28533					editor.selection.lastFocusBookmark = null;
28534				}
28535
28536				if (focusedEditor != editor) {
28537					if (focusedEditor) {
28538						focusedEditor.fire('blur', {focusedEditor: editor});
28539					}
28540
28541					editorManager.setActive(editor);
28542					editorManager.focusedEditor = editor;
28543					editor.fire('focus', {blurredEditor: focusedEditor});
28544					editor.focus(true);
28545				}
28546
28547				editor.lastRng = null;
28548			});
28549
28550			editor.on('focusout', function() {
28551				window.setTimeout(function() {
28552					var focusedEditor = editorManager.focusedEditor;
28553
28554					// Still the same editor the the blur was outside any editor UI
28555					if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
28556						editor.fire('blur', {focusedEditor: null});
28557						editorManager.focusedEditor = null;
28558
28559						// Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
28560						if (editor.selection) {
28561							editor.selection.lastFocusBookmark = null;
28562						}
28563					}
28564				}, 0);
28565			});
28566
28567			// Check if focus is moved to an element outside the active editor by checking if the target node
28568			// isn't within the body of the activeEditor nor a UI element such as a dialog child control
28569			if (!documentFocusInHandler) {
28570				documentFocusInHandler = function(e) {
28571					var activeEditor = editorManager.activeEditor;
28572
28573					if (activeEditor && e.target.ownerDocument == document) {
28574						// Check to make sure we have a valid selection don't update the bookmark if it's
28575						// a focusin to the body of the editor see #7025
28576						if (activeEditor.selection && e.target != activeEditor.getBody()) {
28577							activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
28578						}
28579
28580						// Fire a blur event if the element isn't a UI element
28581						if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
28582							activeEditor.fire('blur', {focusedEditor: null});
28583							editorManager.focusedEditor = null;
28584						}
28585					}
28586				};
28587
28588				DOM.bind(document, 'focusin', documentFocusInHandler);
28589			}
28590
28591			// Handle edge case when user starts the selection inside the editor and releases
28592			// the mouse outside the editor producing a new selection. This weird workaround is needed since
28593			// Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
28594			if (editor.inline && !documentMouseUpHandler) {
28595				documentMouseUpHandler = function(e) {
28596					var activeEditor = editorManager.activeEditor;
28597
28598					if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
28599						var rng = activeEditor.selection.getRng();
28600
28601						if (!rng.collapsed) {
28602							activeEditor.lastRng = rng;
28603						}
28604					}
28605				};
28606
28607				DOM.bind(document, 'mouseup', documentMouseUpHandler);
28608			}
28609		}
28610
28611		function unregisterDocumentEvents(e) {
28612			if (editorManager.focusedEditor == e.editor) {
28613				editorManager.focusedEditor = null;
28614			}
28615
28616			if (!editorManager.activeEditor) {
28617				DOM.unbind(document, 'selectionchange', selectionChangeHandler);
28618				DOM.unbind(document, 'focusin', documentFocusInHandler);
28619				DOM.unbind(document, 'mouseup', documentMouseUpHandler);
28620				selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
28621			}
28622		}
28623
28624		editorManager.on('AddEditor', registerEvents);
28625		editorManager.on('RemoveEditor', unregisterDocumentEvents);
28626	}
28627
28628	/**
28629	 * Returns true if the specified element is part of the UI for example an button or text input.
28630	 *
28631	 * @method isEditorUIElement
28632	 * @param  {Element} elm Element to check if it's part of the UI or not.
28633	 * @return {Boolean} True/false state if the element is part of the UI or not.
28634	 */
28635	FocusManager.isEditorUIElement = function(elm) {
28636		// Needs to be converted to string since svg can have focus: #6776
28637		return elm.className.toString().indexOf('mce-') !== -1;
28638	};
28639
28640	return FocusManager;
28641});
28642
28643// Included from: js/tinymce/classes/EditorManager.js
28644
28645/**
28646 * EditorManager.js
28647 *
28648 * Copyright, Moxiecode Systems AB
28649 * Released under LGPL License.
28650 *
28651 * License: http://www.tinymce.com/license
28652 * Contributing: http://www.tinymce.com/contributing
28653 */
28654
28655/**
28656 * This class used as a factory for manager for tinymce.Editor instances.
28657 *
28658 * @example
28659 * tinymce.EditorManager.init({});
28660 *
28661 * @class tinymce.EditorManager
28662 * @mixes tinymce.util.Observable
28663 * @static
28664 */
28665define("tinymce/EditorManager", [
28666	"tinymce/Editor",
28667	"tinymce/dom/DomQuery",
28668	"tinymce/dom/DOMUtils",
28669	"tinymce/util/URI",
28670	"tinymce/Env",
28671	"tinymce/util/Tools",
28672	"tinymce/util/Observable",
28673	"tinymce/util/I18n",
28674	"tinymce/FocusManager"
28675], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
28676	var DOM = DOMUtils.DOM;
28677	var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
28678	var instanceCounter = 0, beforeUnloadDelegate, EditorManager;
28679
28680	function removeEditorFromList(editor) {
28681		var editors = EditorManager.editors, removedFromList;
28682
28683		delete editors[editor.id];
28684
28685		for (var i = 0; i < editors.length; i++) {
28686			if (editors[i] == editor) {
28687				editors.splice(i, 1);
28688				removedFromList = true;
28689				break;
28690			}
28691		}
28692
28693		// Select another editor since the active one was removed
28694		if (EditorManager.activeEditor == editor) {
28695			EditorManager.activeEditor = editors[0];
28696		}
28697
28698		// Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
28699		if (EditorManager.focusedEditor == editor) {
28700			EditorManager.focusedEditor = null;
28701		}
28702
28703		return removedFromList;
28704	}
28705
28706	function purgeDestroyedEditor(editor) {
28707		// User has manually destroyed the editor lets clean up the mess
28708		if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
28709			removeEditorFromList(editor);
28710			editor.unbindAllNativeEvents();
28711			editor.destroy(true);
28712			editor = null;
28713		}
28714
28715		return editor;
28716	}
28717
28718	EditorManager = {
28719		/**
28720		 * Dom query instance.
28721		 *
28722		 * @property $
28723		 * @type tinymce.dom.DomQuery
28724		 */
28725		$: DomQuery,
28726
28727		/**
28728		 * Major version of TinyMCE build.
28729		 *
28730		 * @property majorVersion
28731		 * @type String
28732		 */
28733		majorVersion: '4',
28734
28735		/**
28736		 * Minor version of TinyMCE build.
28737		 *
28738		 * @property minorVersion
28739		 * @type String
28740		 */
28741		minorVersion: '1.7',
28742
28743		/**
28744		 * Release date of TinyMCE build.
28745		 *
28746		 * @property releaseDate
28747		 * @type String
28748		 */
28749		releaseDate: '2014-11-27',
28750
28751		/**
28752		 * Collection of editor instances.
28753		 *
28754		 * @property editors
28755		 * @type Object
28756		 * @example
28757		 * for (edId in tinymce.editors)
28758		 *     tinymce.editors[edId].save();
28759		 */
28760		editors: [],
28761
28762		/**
28763		 * Collection of language pack data.
28764		 *
28765		 * @property i18n
28766		 * @type Object
28767		 */
28768		i18n: I18n,
28769
28770		/**
28771		 * Currently active editor instance.
28772		 *
28773		 * @property activeEditor
28774		 * @type tinymce.Editor
28775		 * @example
28776		 * tinyMCE.activeEditor.selection.getContent();
28777		 * tinymce.EditorManager.activeEditor.selection.getContent();
28778		 */
28779		activeEditor: null,
28780
28781		setup: function() {
28782			var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
28783
28784			// Get base URL for the current document
28785			documentBaseURL = document.location.href;
28786
28787			// Check if the URL is a document based format like: http://site/dir/file and file:///
28788			// leave other formats like applewebdata://... intact
28789			if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
28790				documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
28791
28792				if (!/[\/\\]$/.test(documentBaseURL)) {
28793					documentBaseURL += '/';
28794				}
28795			}
28796
28797			// If tinymce is defined and has a base use that or use the old tinyMCEPreInit
28798			preInit = window.tinymce || window.tinyMCEPreInit;
28799			if (preInit) {
28800				baseURL = preInit.base || preInit.baseURL;
28801				suffix = preInit.suffix;
28802			} else {
28803
28804				// Get base where the tinymce script is located
28805				var scripts = document.getElementsByTagName('script');
28806				for (var i = 0; i < scripts.length; i++) {
28807					src = scripts[i].src;
28808                    var srcScript = src.substring(src.lastIndexOf('/'));
28809
28810					// Script types supported:
28811					// tinymce.js tinymce.min.js tinymce.dev.js
28812					// tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
28813					// tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
28814					if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
28815						if (srcScript.indexOf('.min') != -1) {
28816							suffix = '.min';
28817						}
28818
28819						baseURL = src.substring(0, src.lastIndexOf('/'));
28820						break;
28821					}
28822				}
28823
28824				// We didn't find any baseURL by looking at the script elements
28825				// Try to use the document.currentScript as a fallback
28826				if (!baseURL && document.currentScript) {
28827					src = document.currentScript.src;
28828
28829					if (src.indexOf('.min') != -1) {
28830						suffix = '.min';
28831					}
28832
28833					baseURL = src.substring(0, src.lastIndexOf('/'));
28834				}
28835			}
28836
28837			/**
28838			 * Base URL where the root directory if TinyMCE is located.
28839			 *
28840			 * @property baseURL
28841			 * @type String
28842			 */
28843			self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
28844
28845			/**
28846			 * Document base URL where the current document is located.
28847			 *
28848			 * @property documentBaseURL
28849			 * @type String
28850			 */
28851			self.documentBaseURL = documentBaseURL;
28852
28853			/**
28854			 * Absolute baseURI for the installation path of TinyMCE.
28855			 *
28856			 * @property baseURI
28857			 * @type tinymce.util.URI
28858			 */
28859			self.baseURI = new URI(self.baseURL);
28860
28861			/**
28862			 * Current suffix to add to each plugin/theme that gets loaded for example ".min".
28863			 *
28864			 * @property suffix
28865			 * @type String
28866			 */
28867			self.suffix = suffix;
28868
28869			self.focusManager = new FocusManager(self);
28870		},
28871
28872		/**
28873		 * Initializes a set of editors. This method will create editors based on various settings.
28874		 *
28875		 * @method init
28876		 * @param {Object} settings Settings object to be passed to each editor instance.
28877		 * @example
28878		 * // Initializes a editor using the longer method
28879		 * tinymce.EditorManager.init({
28880		 *    some_settings : 'some value'
28881		 * });
28882		 *
28883		 * // Initializes a editor instance using the shorter version
28884		 * tinyMCE.init({
28885		 *    some_settings : 'some value'
28886		 * });
28887		 */
28888		init: function(settings) {
28889			var self = this, editors = [];
28890
28891			function createId(elm) {
28892				var id = elm.id;
28893
28894				// Use element id, or unique name or generate a unique id
28895				if (!id) {
28896					id = elm.name;
28897
28898					if (id && !DOM.get(id)) {
28899						id = elm.name;
28900					} else {
28901						// Generate unique name
28902						id = DOM.uniqueId();
28903					}
28904
28905					elm.setAttribute('id', id);
28906				}
28907
28908				return id;
28909			}
28910
28911			function createEditor(id, settings, targetElm) {
28912				if (!purgeDestroyedEditor(self.get(id))) {
28913					var editor = new Editor(id, settings, self);
28914
28915					editor.targetElm = editor.targetElm || targetElm;
28916					editors.push(editor);
28917					editor.render();
28918				}
28919			}
28920
28921			function execCallback(name) {
28922				var callback = settings[name];
28923
28924				if (!callback) {
28925					return;
28926				}
28927
28928				return callback.apply(self, Array.prototype.slice.call(arguments, 2));
28929			}
28930
28931			function hasClass(elm, className) {
28932				return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
28933			}
28934
28935			function readyHandler() {
28936				var l, co;
28937
28938				DOM.unbind(window, 'ready', readyHandler);
28939
28940				execCallback('onpageload');
28941
28942				if (settings.types) {
28943					// Process type specific selector
28944					each(settings.types, function(type) {
28945						each(DOM.select(type.selector), function(elm) {
28946							createEditor(createId(elm), extend({}, settings, type), elm);
28947						});
28948					});
28949
28950					return;
28951				} else if (settings.selector) {
28952					// Process global selector
28953					each(DOM.select(settings.selector), function(elm) {
28954						createEditor(createId(elm), settings, elm);
28955					});
28956
28957					return;
28958				} else if (settings.target) {
28959					createEditor(createId(settings.target), settings);
28960				}
28961
28962				// Fallback to old setting
28963				switch (settings.mode) {
28964					case "exact":
28965						l = settings.elements || '';
28966
28967						if (l.length > 0) {
28968							each(explode(l), function(id) {
28969								var elm;
28970
28971								if ((elm = DOM.get(id))) {
28972									createEditor(id, settings, elm);
28973								} else {
28974									each(document.forms, function(f) {
28975										each(f.elements, function(e) {
28976											if (e.name === id) {
28977												id = 'mce_editor_' + instanceCounter++;
28978												DOM.setAttrib(e, 'id', id);
28979												createEditor(id, settings, e);
28980											}
28981										});
28982									});
28983								}
28984							});
28985						}
28986						break;
28987
28988					case "textareas":
28989					case "specific_textareas":
28990						each(DOM.select('textarea'), function(elm) {
28991							if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
28992								return;
28993							}
28994
28995							if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
28996								createEditor(createId(elm), settings, elm);
28997							}
28998						});
28999						break;
29000				}
29001
29002				// Call onInit when all editors are initialized
29003				if (settings.oninit) {
29004					l = co = 0;
29005
29006					each(editors, function(ed) {
29007						co++;
29008
29009						if (!ed.initialized) {
29010							// Wait for it
29011							ed.on('init', function() {
29012								l++;
29013
29014								// All done
29015								if (l == co) {
29016									execCallback('oninit');
29017								}
29018							});
29019						} else {
29020							l++;
29021						}
29022
29023						// All done
29024						if (l == co) {
29025							execCallback('oninit');
29026						}
29027					});
29028				}
29029			}
29030
29031			self.settings = settings;
29032
29033			DOM.bind(window, 'ready', readyHandler);
29034		},
29035
29036		/**
29037		 * Returns a editor instance by id.
29038		 *
29039		 * @method get
29040		 * @param {String/Number} id Editor instance id or index to return.
29041		 * @return {tinymce.Editor} Editor instance to return.
29042		 * @example
29043		 * // Adds an onclick event to an editor by id (shorter version)
29044		 * tinymce.get('mytextbox').on('click', function(e) {
29045		 *    ed.windowManager.alert('Hello world!');
29046		 * });
29047		 *
29048		 * // Adds an onclick event to an editor by id (longer version)
29049		 * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
29050		 *    ed.windowManager.alert('Hello world!');
29051		 * });
29052		 */
29053		get: function(id) {
29054			if (!arguments.length) {
29055				return this.editors;
29056			}
29057
29058			return id in this.editors ? this.editors[id] : null;
29059		},
29060
29061		/**
29062		 * Adds an editor instance to the editor collection. This will also set it as the active editor.
29063		 *
29064		 * @method add
29065		 * @param {tinymce.Editor} editor Editor instance to add to the collection.
29066		 * @return {tinymce.Editor} The same instance that got passed in.
29067		 */
29068		add: function(editor) {
29069			var self = this, editors = self.editors;
29070
29071			// Add named and index editor instance
29072			editors[editor.id] = editor;
29073			editors.push(editor);
29074
29075			// Doesn't call setActive method since we don't want
29076			// to fire a bunch of activate/deactivate calls while initializing
29077			self.activeEditor = editor;
29078
29079			/**
29080			 * Fires when an editor is added to the EditorManager collection.
29081			 *
29082			 * @event AddEditor
29083			 * @param {Object} e Event arguments.
29084			 */
29085			self.fire('AddEditor', {editor: editor});
29086
29087			if (!beforeUnloadDelegate) {
29088				beforeUnloadDelegate = function() {
29089					self.fire('BeforeUnload');
29090				};
29091
29092				DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
29093			}
29094
29095			return editor;
29096		},
29097
29098		/**
29099		 * Creates an editor instance and adds it to the EditorManager collection.
29100		 *
29101		 * @method createEditor
29102		 * @param {String} id Instance id to use for editor.
29103		 * @param {Object} settings Editor instance settings.
29104		 * @return {tinymce.Editor} Editor instance that got created.
29105		 */
29106		createEditor: function(id, settings) {
29107			return this.add(new Editor(id, settings, this));
29108		},
29109
29110		/**
29111		 * Removes a editor or editors form page.
29112		 *
29113		 * @example
29114		 * // Remove all editors bound to divs
29115		 * tinymce.remove('div');
29116		 *
29117		 * // Remove all editors bound to textareas
29118		 * tinymce.remove('textarea');
29119		 *
29120		 * // Remove all editors
29121		 * tinymce.remove();
29122		 *
29123		 * // Remove specific instance by id
29124		 * tinymce.remove('#id');
29125		 *
29126		 * @method remove
29127		 * @param {tinymce.Editor/String/Object}
29127 [selector] CSS selector or editor instance to remove.
29128		 * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
29129		 */
29130		remove: function(selector) {
29131			var self = this, i, editors = self.editors, editor;
29132
29133			// Remove all editors
29134			if (!selector) {
29135				for (i = editors.length - 1; i >= 0; i--) {
29136					self.remove(editors[i]);
29137				}
29138
29139				return;
29140			}
29141
29142			// Remove editors by selector
29143			if (typeof(selector) == "string") {
29144				selector = selector.selector || selector;
29145
29146				each(DOM.select(selector), function(elm) {
29147					editor = editors[elm.id];
29148
29149					if (editor) {
29150						self.remove(editor);
29151					}
29152				});
29153
29154				return;
29155			}
29156
29157			// Remove specific editor
29158			editor = selector;
29159
29160			// Not in the collection
29161			if (!editors[editor.id]) {
29162				return null;
29163			}
29164
29165			/**
29166			 * Fires when an editor is removed from EditorManager collection.
29167			 *
29168			 * @event RemoveEditor
29169			 * @param {Object} e Event arguments.
29170			 */
29171			if (removeEditorFromList(editor)) {
29172				self.fire('RemoveEditor', {editor: editor});
29173			}
29174
29175			if (!editors.length) {
29176				DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
29177			}
29178
29179			editor.remove();
29180
29181			return editor;
29182		},
29183
29184		/**
29185		 * Executes a specific command on the currently active editor.
29186		 *
29187		 * @method execCommand
29188		 * @param {String} c Command to perform for example Bold.
29189		 * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
29190		 * @param {String} v Optional value parameter like for example an URL to a link.
29191		 * @return {Boolean} true/false if the command was executed or not.
29192		 */
29193		execCommand: function(cmd, ui, value) {
29194			var self = this, editor = self.get(value);
29195
29196			// Manager commands
29197			switch (cmd) {
29198				case "mceAddEditor":
29199					if (!self.get(value)) {
29200						new Editor(value, self.settings, self).render();
29201					}
29202
29203					return true;
29204
29205				case "mceRemoveEditor":
29206					if (editor) {
29207						editor.remove();
29208					}
29209
29210					return true;
29211
29212				case 'mceToggleEditor':
29213					if (!editor) {
29214						self.execCommand('mceAddEditor', 0, value);
29215						return true;
29216					}
29217
29218					if (editor.isHidden()) {
29219						editor.show();
29220					} else {
29221						editor.hide();
29222					}
29223
29224					return true;
29225			}
29226
29227			// Run command on active editor
29228			if (self.activeEditor) {
29229				return self.activeEditor.execCommand(cmd, ui, value);
29230			}
29231
29232			return false;
29233		},
29234
29235		/**
29236		 * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
29237		 *
29238		 * @method triggerSave
29239		 * @example
29240		 * // Saves all contents
29241		 * tinyMCE.triggerSave();
29242		 */
29243		triggerSave: function() {
29244			each(this.editors, function(editor) {
29245				editor.save();
29246			});
29247		},
29248
29249		/**
29250		 * Adds a language pack, this gets called by the loaded language files like en.js.
29251		 *
29252		 * @method addI18n
29253		 * @param {String} code Optional language code.
29254		 * @param {Object} items Name/value object with translations.
29255		 */
29256		addI18n: function(code, items) {
29257			I18n.add(code, items);
29258		},
29259
29260		/**
29261		 * Translates the specified string using the language pack items.
29262		 *
29263		 * @method translate
29264		 * @param {String/Array/Object} text String to translate
29265		 * @return {String} Translated string.
29266		 */
29267		translate: function(text) {
29268			return I18n.translate(text);
29269		},
29270
29271		/**
29272		 * Sets the active editor instance and fires the deactivate/activate events.
29273		 *
29274		 * @method setActive
29275		 * @param {tinymce.Editor} editor Editor instance to set as the active instance.
29276		 */
29277		setActive: function(editor) {
29278			var activeEditor = this.activeEditor;
29279
29280			if (this.activeEditor != editor) {
29281				if (activeEditor) {
29282					activeEditor.fire('deactivate', {relatedTarget: editor});
29283				}
29284
29285				editor.fire('activate', {relatedTarget: activeEditor});
29286			}
29287
29288			this.activeEditor = editor;
29289		}
29290	};
29291
29292	extend(EditorManager, Observable);
29293
29294	EditorManager.setup();
29295
29296	// Export EditorManager as tinymce/tinymce in global namespace
29297	window.tinymce = window.tinyMCE = EditorManager;
29298
29299	return EditorManager;
29300});
29301
29302// Included from: js/tinymce/classes/LegacyInput.js
29303
29304/**
29305 * LegacyInput.js
29306 *
29307 * Copyright, Moxiecode Systems AB
29308 * Released under LGPL License.
29309 *
29310 * License: http://www.tinymce.com/license
29311 * Contributing: http://www.tinymce.com/contributing
29312 */
29313
29314define("tinymce/LegacyInput", [
29315	"tinymce/EditorManager",
29316	"tinymce/util/Tools"
29317], function(EditorManager, Tools) {
29318	var each = Tools.each, explode = Tools.explode;
29319
29320	EditorManager.on('AddEditor', function(e) {
29321		var editor = e.editor;
29322
29323		editor.on('preInit', function() {
29324			var filters, fontSizes, dom, settings = editor.settings;
29325
29326			function replaceWithSpan(node, styles) {
29327				each(styles, function(value, name) {
29328					if (value) {
29329						dom.setStyle(node, name, value);
29330					}
29331				});
29332
29333				dom.rename(node, 'span');
29334			}
29335
29336			function convert(e) {
29337				dom = editor.dom;
29338
29339				if (settings.convert_fonts_to_spans) {
29340					each(dom.select('font,u,strike', e.node), function(node) {
29341						filters[node.nodeName.toLowerCase()](dom, node);
29342					});
29343				}
29344			}
29345
29346			if (settings.inline_styles) {
29347				fontSizes = explode(settings.font_size_legacy_values);
29348
29349				filters = {
29350					font: function(dom, node) {
29351						replaceWithSpan(node, {
29352							backgroundColor: node.style.backgroundColor,
29353							color: node.color,
29354							fontFamily: node.face,
29355							fontSize: fontSizes[parseInt(node.size, 10) - 1]
29356						});
29357					},
29358
29359					u: function(dom, node) {
29360						replaceWithSpan(node, {
29361							textDecoration: 'underline'
29362						});
29363					},
29364
29365					strike: function(dom, node) {
29366						replaceWithSpan(node, {
29367							textDecoration: 'line-through'
29368						});
29369					}
29370				};
29371
29372				editor.on('PreProcess SetContent', convert);
29373			}
29374		});
29375	});
29376});
29377
29378// Included from: js/tinymce/classes/util/XHR.js
29379
29380/**
29381 * XHR.js
29382 *
29383 * Copyright, Moxiecode Systems AB
29384 * Released under LGPL License.
29385 *
29386 * License: http://www.tinymce.com/license
29387 * Contributing: http://www.tinymce.com/contributing
29388 */
29389
29390/**
29391 * This class enables you to send XMLHTTPRequests cross browser.
29392 * @class tinymce.util.XHR
29393 * @mixes tinymce.util.Observable
29394 * @static
29395 * @example
29396 * // Sends a low level Ajax request
29397 * tinymce.util.XHR.send({
29398 *    url: 'someurl',
29399 *    success: function(text) {
29400 *       console.debug(text);
29401 *    }
29402 * });
29403 *
29404 * // Add custom header to XHR request
29405 * tinymce.util.XHR.on('beforeSend', function(e) {
29406 *     e.xhr.setRequestHeader('X-Requested-With', 'Something');
29407 * });
29408 */
29409define("tinymce/util/XHR", [
29410	"tinymce/util/Observable",
29411	"tinymce/util/Tools"
29412], function(Observable, Tools) {
29413	var XHR = {
29414		/**
29415		 * Sends a XMLHTTPRequest.
29416		 * Consult the Wiki for details on what settings this method takes.
29417		 *
29418		 * @method send
29419		 * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
29420		 */
29421		send: function(settings) {
29422			var xhr, count = 0;
29423
29424			function ready() {
29425				if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
29426					if (settings.success && count < 10000 && xhr.status == 200) {
29427						settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
29428					} else if (settings.error) {
29429						settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
29430					}
29431
29432					xhr = null;
29433				} else {
29434					setTimeout(ready, 10);
29435				}
29436			}
29437
29438			// Default settings
29439			settings.scope = settings.scope || this;
29440			settings.success_scope = settings.success_scope || settings.scope;
29441			settings.error_scope = settings.error_scope || settings.scope;
29442			settings.async = settings.async === false ? false : true;
29443			settings.data = settings.data || '';
29444
29445			xhr = new XMLHttpRequest();
29446
29447			if (xhr) {
29448				if (xhr.overrideMimeType) {
29449					xhr.overrideMimeType(settings.content_type);
29450				}
29451
29452				xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
29453
29454				if (settings.crossDomain) {
29455					xhr.withCredentials = true;
29456				}
29457
29458				if (settings.content_type) {
29459					xhr.setRequestHeader('Content-Type', settings.content_type);
29460				}
29461
29462				xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
29463
29464				xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr;
29465				xhr.send(settings.data);
29466
29467				// Syncronous request
29468				if (!settings.async) {
29469					return ready();
29470				}
29471
29472				// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
29473				setTimeout(ready, 10);
29474			}
29475		}
29476	};
29477
29478	Tools.extend(XHR, Observable);
29479
29480	return XHR;
29481});
29482
29483// Included from: js/tinymce/classes/util/JSON.js
29484
29485/**
29486 * JSON.js
29487 *
29488 * Copyright, Moxiecode Systems AB
29489 * Released under LGPL License.
29490 *
29491 * License: http://www.tinymce.com/license
29492 * Contributing: http://www.tinymce.com/contributing
29493 */
29494
29495/**
29496 * JSON parser and serializer class.
29497 *
29498 * @class tinymce.util.JSON
29499 * @static
29500 * @example
29501 * // JSON parse a string into an object
29502 * var obj = tinymce.util.JSON.parse(somestring);
29503 *
29504 * // JSON serialize a object into an string
29505 * var str = tinymce.util.JSON.serialize(obj);
29506 */
29507define("tinymce/util/JSON", [], function() {
29508	function serialize(o, quote) {
29509		var i, v, t, name;
29510
29511		quote = quote || '"';
29512
29513		if (o === null) {
29514			return 'null';
29515		}
29516
29517		t = typeof o;
29518
29519		if (t == 'string') {
29520			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
29521
29522			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
29523				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
29524				if (quote === '"' && a === "'") {
29525					return a;
29526				}
29527
29528				i = v.indexOf(b);
29529
29530				if (i + 1) {
29531					return '\\' + v.charAt(i + 1);
29532				}
29533
29534				a = b.charCodeAt().toString(16);
29535
29536				return '\\u' + '0000'.substring(a.length) + a;
29537			}) + quote;
29538		}
29539
29540		if (t == 'object') {
29541			if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
29542				for (i = 0, v = '['; i < o.length; i++) {
29543					v += (i > 0 ? ',' : '') + serialize(o[i], quote);
29544				}
29545
29546				return v + ']';
29547			}
29548
29549			v = '{';
29550
29551			for (name in o) {
29552				if (o.hasOwnProperty(name)) {
29553					v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
29554						quote + ':' + serialize(o[name], quote) : '';
29555				}
29556			}
29557
29558			return v + '}';
29559		}
29560
29561		return '' + o;
29562	}
29563
29564	return {
29565		/**
29566		 * Serializes the specified object as a JSON string.
29567		 *
29568		 * @method serialize
29569		 * @param {Object} obj Object to serialize as a JSON string.
29570		 * @param {String}
29570 quote Optional quote string defaults to ".
29571		 * @return {string} JSON string serialized from input.
29572		 */
29573		serialize: serialize,
29574
29575		/**
29576		 * Unserializes/parses the specified JSON string into a object.
29577		 *
29578		 * @method parse
29579		 * @param {string} s JSON String to parse into a JavaScript object.
29580		 * @return {Object} Object from input JSON string or undefined if it failed.
29581		 */
29582		parse: function(text) {
29583			try {
29584				// Trick uglify JS
29585				return window[String.fromCharCode(101) + 'val']('(' + text + ')');
29586			} catch (ex) {
29587				// Ignore
29588			}
29589		}
29590
29591		/**#@-*/
29592	};
29593});
29594
29595// Included from: js/tinymce/classes/util/JSONRequest.js
29596
29597/**
29598 * JSONRequest.js
29599 *
29600 * Copyright, Moxiecode Systems AB
29601 * Released under LGPL License.
29602 *
29603 * License: http://www.tinymce.com/license
29604 * Contributing: http://www.tinymce.com/contributing
29605 */
29606
29607/**
29608 * This class enables you to use JSON-RPC to call backend methods.
29609 *
29610 * @class tinymce.util.JSONRequest
29611 * @example
29612 * var json = new tinymce.util.JSONRequest({
29613 *     url: 'somebackend.php'
29614 * });
29615 *
29616 * // Send RPC call 1
29617 * json.send({
29618 *     method: 'someMethod1',
29619 *     params: ['a', 'b'],
29620 *     success: function(result) {
29621 *         console.dir(result);
29622 *     }
29623 * });
29624 *
29625 * // Send RPC call 2
29626 * json.send({
29627 *     method: 'someMethod2',
29628 *     params: ['a', 'b'],
29629 *     success: function(result) {
29630 *         console.dir(result);
29631 *     }
29632 * });
29633 */
29634define("tinymce/util/JSONRequest", [
29635	"tinymce/util/JSON",
29636	"tinymce/util/XHR",
29637	"tinymce/util/Tools"
29638], function(JSON, XHR, Tools) {
29639	var extend = Tools.extend;
29640
29641	function JSONRequest(settings) {
29642		this.settings = extend({}, settings);
29643		this.count = 0;
29644	}
29645
29646	/**
29647	 * Simple helper function to send a JSON-RPC request without the need to initialize an object.
29648	 * Consult the Wiki API documentation for more details on what you can pass to this function.
29649	 *
29650	 * @method sendRPC
29651	 * @static
29652	 * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
29653	 */
29654	JSONRequest.sendRPC = function(o) {
29655		return new JSONRequest().send(o);
29656	};
29657
29658	JSONRequest.prototype = {
29659		/**
29660		 * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
29661		 *
29662		 * @method send
29663		 * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
29664		 */
29665		send: function(args) {
29666			var ecb = args.error, scb = args.success;
29667
29668			args = extend(this.settings, args);
29669
29670			args.success = function(c, x) {
29671				c = JSON.parse(c);
29672
29673				if (typeof(c) == 'undefined') {
29674					c = {
29675						error : 'JSON Parse error.'
29676					};
29677				}
29678
29679				if (c.error) {
29680					ecb.call(args.error_scope || args.scope, c.error, x);
29681				} else {
29682					scb.call(args.success_scope || args.scope, c.result);
29683				}
29684			};
29685
29686			args.error = function(ty, x) {
29687				if (ecb) {
29688					ecb.call(args.error_scope || args.scope, ty, x);
29689				}
29690			};
29691
29692			args.data = JSON.serialize({
29693				id: args.id || 'c' + (this.count++),
29694				method: args.method,
29695				params: args.params
29696			});
29697
29698			// JSON content type for Ruby on rails. Bug: #1883287
29699			args.content_type = 'application/json';
29700
29701			XHR.send(args);
29702		}
29703	};
29704
29705	return JSONRequest;
29706});
29707
29708// Included from: js/tinymce/classes/util/JSONP.js
29709
29710/**
29711 * JSONP.js
29712 *
29713 * Copyright, Moxiecode Systems AB
29714 * Released under LGPL License.
29715 *
29716 * License: http://www.tinymce.com/license
29717 * Contributing: http://www.tinymce.com/contributing
29718 */
29719
29720define("tinymce/util/JSONP", [
29721	"tinymce/dom/DOMUtils"
29722], function(DOMUtils) {
29723	return {
29724		callbacks: {},
29725		count: 0,
29726
29727		send: function(settings) {
29728			var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
29729			var id = 'tinymce_jsonp_' + count;
29730
29731			self.callbacks[count] = function(json) {
29732				dom.remove(id);
29733				delete self.callbacks[count];
29734
29735				settings.callback(json);
29736			};
29737
29738			dom.add(dom.doc.body, 'script', {
29739				id: id,
29740				src: settings.url,
29741				type: 'text/javascript'
29742			});
29743
29744			self.count++;
29745		}
29746	};
29747});
29748
29749// Included from: js/tinymce/classes/util/LocalStorage.js
29750
29751/**
29752 * LocalStorage.js
29753 *
29754 * Copyright, Moxiecode Systems AB
29755 * Released under LGPL License.
29756 *
29757 * License: http://www.tinymce.com/license
29758 * Contributing: http://www.tinymce.com/contributing
29759 */
29760
29761/**
29762 * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
29763 * Storage is done using userData on IE 7 and a special serialization format. The format is designed
29764 * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
29765 * makes it possible to store for example HTML data.
29766 *
29767 * Storage format for userData:
29768 * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
29769 *
29770 * For example this data key1=value1,key2=value2 would be:
29771 * 4,key1,6,value1,4,key2,6,value2
29772 *
29773 * @class tinymce.util.LocalStorage
29774 * @static
29775 * @version 4.0
29776 * @example
29777 * tinymce.util.LocalStorage.setItem('key', 'value');
29778 * var value = tinymce.util.LocalStorage.getItem('key');
29779 */
29780define("tinymce/util/LocalStorage", [], function() {
29781	var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
29782
29783	// Check for native support
29784	try {
29785		if (window.localStorage) {
29786			return localStorage;
29787		}
29788	} catch (ex) {
29789		// Ignore
29790	}
29791
29792	userDataKey = "tinymce";
29793	storageElm = document.documentElement;
29794	hasOldIEDataSupport = !!storageElm.addBehavior;
29795
29796	if (hasOldIEDataSupport) {
29797		storageElm.addBehavior('#default#userData');
29798	}
29799
29800	/**
29801	 * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
29802	 */
29803	function updateKeys() {
29804		keys = [];
29805
29806		for (var key in items) {
29807			keys.push(key);
29808		}
29809
29810		LocalStorage.length = keys.length;
29811	}
29812
29813	/**
29814	 * Loads the userData string and parses it into the items structure.
29815	 */
29816	function load() {
29817		var key, data, value, pos = 0;
29818
29819		items = {};
29820
29821		// localStorage can be disabled on WebKit/Gecko s
29821o make a dummy storage
29822		if (!hasOldIEDataSupport) {
29823			return;
29824		}
29825
29826		function next(end) {
29827			var value, nextPos;
29828
29829			nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
29830			if (nextPos === -1 || nextPos > data.length) {
29831				return null;
29832			}
29833
29834			value = data.substring(pos, nextPos);
29835			pos = nextPos + 1;
29836
29837			return value;
29838		}
29839
29840		storageElm.load(userDataKey);
29841		data = storageElm.getAttribute(userDataKey) || '';
29842
29843		do {
29844			var offset = next();
29845			if (offset === null) {
29846				break;
29847			}
29848
29849			key = next(parseInt(offset, 32) || 0);
29850			if (key !== null) {
29851				offset = next();
29852				if (offset === null) {
29853					break;
29854				}
29855
29856				value = next(parseInt(offset, 32) || 0);
29857
29858				if (key) {
29859					items[key] = value;
29860				}
29861			}
29862		} while (key !== null);
29863
29864		updateKeys();
29865	}
29866
29867	/**
29868	 * Saves the items structure into a the userData format.
29869	 */
29870	function save() {
29871		var value, data = '';
29872
29873		// localStorage can be disabled on WebKit/Gecko so make a dummy storage
29874		if (!hasOldIEDataSupport) {
29875			return;
29876		}
29877
29878		for (var key in items) {
29879			value = items[key];
29880			data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
29881		}
29882
29883		storageElm.setAttribute(userDataKey, data);
29884
29885		try {
29886			storageElm.save(userDataKey);
29887		} catch (ex) {
29888			// Ignore disk full
29889		}
29890
29891		updateKeys();
29892	}
29893
29894	LocalStorage = {
29895		/**
29896		 * Length of the number of items in storage.
29897		 *
29898		 * @property length
29899		 * @type Number
29900		 * @return {Number} Number of items in storage.
29901		 */
29902		//length:0,
29903
29904		/**
29905		 * Returns the key name by index.
29906		 *
29907		 * @method key
29908		 * @param {Number} index Index of key to return.
29909		 * @return {String} Key value or null if it wasn't found.
29910		 */
29911		key: function(index) {
29912			return keys[index];
29913		},
29914
29915		/**
29916		 * Returns the value if the specified key or null if it wasn't found.
29917		 *
29918		 * @method getItem
29919		 * @param {String} key Key of item to retrive.
29920		 * @return {String} Value of the specified item or null if it wasn't found.
29921		 */
29922		getItem: function(key) {
29923			return key in items ? items[key] : null;
29924		},
29925
29926		/**
29927		 * Sets the value of the specified item by it's key.
29928		 *
29929		 * @method setItem
29930		 * @param {String} key Key of the item to set.
29931		 * @param {String} value Value of the item to set.
29932		 */
29933		setItem: function(key, value) {
29934			items[key] = "" + value;
29935			save();
29936		},
29937
29938		/**
29939		 * Removes the specified item by key.
29940		 *
29941		 * @method removeItem
29942		 * @param {String} key Key of item to remove.
29943		 */
29944		removeItem: function(key) {
29945			delete items[key];
29946			save();
29947		},
29948
29949		/**
29950		 * Removes all items.
29951		 *
29952		 * @method clear
29953		 */
29954		clear: function() {
29955			items = {};
29956			save();
29957		}
29958	};
29959
29960	load();
29961
29962	return LocalStorage;
29963});
29964
29965// Included from: js/tinymce/classes/Compat.js
29966
29967/**
29968 * Compat.js
29969 *
29970 * Copyright, Moxiecode Systems AB
29971 * Released under LGPL License.
29972 *
29973 * License: http://www.tinymce.com/license
29974 * Contributing: http://www.tinymce.com/contributing
29975 */
29976
29977/**
29978 * TinyMCE core class.
29979 *
29980 * @static
29981 * @class tinymce
29982 * @borrow-members tinymce.EditorManager
29983 * @borrow-members tinymce.util.Tools
29984 */
29985define("tinymce/Compat", [
29986	"tinymce/dom/DOMUtils",
29987	"tinymce/dom/EventUtils",
29988	"tinymce/dom/ScriptLoader",
29989	"tinymce/AddOnManager",
29990	"tinymce/util/Tools",
29991	"tinymce/Env"
29992], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
29993	var tinymce = window.tinymce;
29994
29995	/**
29996	 * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
29997	 * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
29998	 * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
29999	 * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
30000	 */
30001	tinymce.DOM = DOMUtils.DOM;
30002	tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
30003	tinymce.PluginManager = AddOnManager.PluginManager;
30004	tinymce.ThemeManager = AddOnManager.ThemeManager;
30005
30006	tinymce.dom = tinymce.dom || {};
30007	tinymce.dom.Event = EventUtils.Event;
30008
30009	Tools.each(Tools, function(func, key) {
30010		tinymce[key] = func;
30011	});
30012
30013	Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
30014		tinymce[name] = Env[name.substr(2).toLowerCase()];
30015	});
30016
30017	return {};
30018});
30019
30020// Describe the different namespaces
30021
30022/**
30023 * Root level namespace this contains classes directly releated to the TinyMCE editor.
30024 *
30025 * @namespace tinymce
30026 */
30027
30028/**
30029 * Contains classes for handling the browsers DOM.
30030 *
30031 * @namespace tinymce.dom
30032 */
30033
30034/**
30035 * Contains html parser and serializer logic.
30036 *
30037 * @namespace tinymce.html
30038 */
30039
30040/**
30041 * Contains the different UI types such as buttons, listboxes etc.
30042 *
30043 * @namespace tinymce.ui
30044 */
30045
30046/**
30047 * Contains various utility classes such as json parser, cookies etc.
30048 *
30049 * @namespace tinymce.util
30050 */
30051
30052// Included from: js/tinymce/classes/ui/Layout.js
30053
30054/**
30055 * Layout.js
30056 *
30057 * Copyright, Moxiecode Systems AB
30058 * Released under LGPL License.
30059 *
30060 * License: http://www.tinymce.com/license
30061 * Contributing: http://www.tinymce.com/contributing
30062 */
30063
30064/**
30065 * Base layout manager class.
30066 *
30067 * @class tinymce.ui.Layout
30068 */
30069define("tinymce/ui/Layout", [
30070	"tinymce/util/Class",
30071	"tinymce/util/Tools"
30072], function(Class, Tools) {
30073	"use strict";
30074
30075	return Class.extend({
30076		Defaults: {
30077			firstControlClass: 'first',
30078			lastControlClass: 'last'
30079		},
30080
30081		/**
30082		 * Constructs a layout instance with the specified settings.
30083		 *
30084		 * @constructor
30085		 * @param {Object} settings Name/value object with settings.
30086		 */
30087		init: function(settings) {
30088			this.settings = Tools.extend({}, this.Defaults, settings);
30089		},
30090
30091		/**
30092		 * This method gets invoked before the layout renders the controls.
30093		 *
30094		 * @method preRender
30095		 * @param {tinymce.ui.Container} container Container instance to preRender.
30096		 */
30097		preRender: function(container) {
30098			container.addClass(this.settings.containerClass, 'body');
30099		},
30100
30101		/**
30102		 * Applies layout classes to the container.
30103		 *
30104		 * @private
30105		 */
30106		applyClasses: function(container) {
30107			var self = this, settings = self.settings, items, firstClass, lastClass;
30108
30109			items = container.items().filter(':visible');
30110			firstClass = settings.firstControlClass;
30111			lastClass = settings.lastControlClass;
30112
30113			items.each(function(item) {
30114				item.removeClass(firstClass).removeClass(lastClass);
30115
30116				if (settings.controlClass) {
30117					item.addClass(settings.controlClass);
30118				}
30119			});
30120
30121			items.eq(0).addClass(firstClass);
30122			items.eq(-1).addClass(lastClass);
30123		},
30124
30125		/**
30126		 * Renders the specified container and any layout specific HTML.
30127		 *
30128		 * @method renderHtml
30129		 * @param {tinymce.ui.Container} container Container to render HTML for.
30130		 */
30131		renderHtml: function(container) {
30132			var self = this, settings = self.settings, items, html = '';
30133
30134			items = container.items();
30135			items.eq(0).addClass(settings.firstControlClass);
30136			items.eq(-1).addClass(settings.lastControlClass);
30137
30138			items.each(function(item) {
30139				if (settings.controlClass) {
30140					item.addClass(settings.controlClass);
30141				}
30142
30143				html += item.renderHtml();
30144			});
30145
30146			return html;
30147		},
30148
30149		/**
30150		 * Recalculates the positions of the controls in the specified container.
30151		 *
30152		 * @method recalc
30153		 * @param {tinymce.ui.Container} container Container instance to recalc.
30154		 */
30155		recalc: function() {
30156		},
30157
30158		/**
30159		 * This method gets invoked after the layout renders the controls.
30160		 *
30161		 * @method postRender
30162		 * @param {tinymce.ui.Container} container Container instance to postRender.
30163		 */
30164		postRender: function() {
30165		}
30166	});
30167});
30168
30169// Included from: js/tinymce/classes/ui/AbsoluteLayout.js
30170
30171/**
30172 * AbsoluteLayout.js
30173 *
30174 * Copyright, Moxiecode Systems AB
30175 * Released under LGPL License.
30176 *
30177 * License: http://www.tinymce.com/license
30178 * Contributing: http://www.tinymce.com/contributing
30179 */
30180
30181/**
30182 * LayoutManager for absolute positioning. This layout manager is more of
30183 * a base class for other layouts but can be created and used directly.
30184 *
30185 * @-x-less AbsoluteLayout.less
30186 * @class tinymce.ui.AbsoluteLayout
30187 * @extends tinymce.ui.Layout
30188 */
30189define("tinymce/ui/AbsoluteLayout", [
30190	"tinymce/ui/Layout"
30191], function(Layout) {
30192	"use strict";
30193
30194	return Layout.extend({
30195		Defaults: {
30196			containerClass: 'abs-layout',
30197			controlClass: 'abs-layout-item'
30198		},
30199
30200		/**
30201		 * Recalculates the positions of the controls in the specified container.
30202		 *
30203		 * @method recalc
30204		 * @param {tinymce.ui.Container} container Container instance to recalc.
30205		 */
30206		recalc: function(container) {
30207			container.items().filter(':visible').each(function(ctrl) {
30208				var settings = ctrl.settings;
30209
30210				ctrl.layoutRect({
30211					x: settings.x,
30212					y: settings.y,
30213					w: settings.w,
30214					h: settings.h
30215				});
30216
30217				if (ctrl.recalc) {
30218					ctrl.recalc();
30219				}
30220			});
30221		},
30222
30223		/**
30224		 * Renders the specified container and any layout specific HTML.
30225		 *
30226		 * @method renderHtml
30227		 * @param {tinymce.ui.Container} container Container to render HTML for.
30228		 */
30229		renderHtml: function(container) {
30230			return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
30231		}
30232	});
30233});
30234
30235// Included from: js/tinymce/classes/ui/Tooltip.js
30236
30237/**
30238 * Tooltip.js
30239 *
30240 * Copyright, Moxiecode Systems AB
30241 * Released under LGPL License.
30242 *
30243 * License: http://www.tinymce.com/license
30244 * Contributing: http://www.tinymce.com/contributing
30245 */
30246
30247/**
30248 * Creates a tooltip instance.
30249 *
30250 * @-x-less ToolTip.less
30251 * @class tinymce.ui.ToolTip
30252 * @extends tinymce.ui.Control
30253 * @mixes tinymce.ui.Movable
30254 */
30255define("tinymce/ui/Tooltip", [
30256	"tinymce/ui/Control",
30257	"tinymce/ui/Movable"
30258], function(Control, Movable) {
30259	return Control.extend({
30260		Mixins: [Movable],
30261
30262		Defaults: {
30263			classes: 'widget tooltip tooltip-n'
30264		},
30265
30266		/**
30267		 * Sets/gets the current label text.
30268		 *
30269		 * @method text
30270		 * @param {String} [text] New label text.
30271		 * @return {String|tinymce.ui.Tooltip} Current text or current label instance.
30272		 */
30273		text: function(value) {
30274			var self = this;
30275
30276			if (typeof(value) != "undefined") {
30277				self._value = value;
30278
30279				if (self._rendered) {
30280					self.getEl().lastChild.innerHTML = self.encode(value);
30281				}
30282
30283				return self;
30284			}
30285
30286			return self._value;
30287		},
30288
30289		/**
30290		 * Renders the control as a HTML string.
30291		 *
30292		 * @method renderHtml
30293		 * @return {String} HTML representing the control.
30294		 */
30295		renderHtml: function() {
30296			var self = this, prefix = self.classPrefix;
30297
30298			return (
30299				'<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' +
30300					'<div class="' + prefix + 'tooltip-arrow"></div>' +
30301					'<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' +
30302				'</div>'
30303			);
30304		},
30305
30306		/**
30307		 * Repaints the control after a layout operation.
30308		 *
30309		 * @method repaint
30310		 */
30311		repaint: function() {
30312			var self = this, style, rect;
30313
30314			style = self.getEl().style;
30315			rect = self._layoutRect;
30316
30317			style.left = rect.x + 'px';
30318			style.top = rect.y + 'px';
30319			style.zIndex = 0xFFFF + 0xFFFF;
30320		}
30321	});
30322});
30323
30324// Included from: js/tinymce/classes/ui/Widget.js
30325
30326/**
30327 * Widget.js
30328 *
30329 * Copyright, Moxiecode Systems AB
30330 * Released under LGPL License.
30331 *
30332 * License: http://www.tinymce.com/license
30333 * Contributing: http://www.tinymce.com/contributing
30334 */
30335
30336/**
30337 * Widget base class a widget is a control that has a tooltip and s
30337ome basic states.
30338 *
30339 * @class tinymce.ui.Widget
30340 * @extends tinymce.ui.Control
30341 */
30342define("tinymce/ui/Widget", [
30343	"tinymce/ui/Control",
30344	"tinymce/ui/Tooltip"
30345], function(Control, Tooltip) {
30346	"use strict";
30347
30348	var tooltip;
30349
30350	var Widget = Control.extend({
30351		/**
30352		 * Constructs a instance with the specified settings.
30353		 *
30354		 * @constructor
30355		 * @param {Object} settings Name/value object with settings.
30356		 * @setting {String} tooltip Tooltip text to display when hovering.
30357		 * @setting {Boolean} autofocus True if the control should be focused when rendered.
30358		 * @setting {String} text Text to display inside widget.
30359		 */
30360		init: function(settings) {
30361			var self = this;
30362
30363			self._super(settings);
30364			settings = self.settings;
30365			self.canFocus = true;
30366
30367			if (settings.tooltip && Widget.tooltips !== false) {
30368				self.on('mouseenter', function(e) {
30369					var tooltip = self.tooltip().moveTo(-0xFFFF);
30370
30371					if (e.control == self) {
30372						var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
30373
30374						tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
30375						tooltip.toggleClass('tooltip-nw', rel == 'bc-tl');
30376						tooltip.toggleClass('tooltip-ne', rel == 'bc-tr');
30377
30378						tooltip.moveRel(self.getEl(), rel);
30379					} else {
30380						tooltip.hide();
30381					}
30382				});
30383
30384				self.on('mouseleave mousedown click', function() {
30385					self.tooltip().hide();
30386				});
30387			}
30388
30389			self.aria('label', settings.ariaLabel || settings.tooltip);
30390		},
30391
30392		/**
30393		 * Returns the current tooltip instance.
30394		 *
30395		 * @method tooltip
30396		 * @return {tinymce.ui.Tooltip} Tooltip instance.
30397		 */
30398		tooltip: function() {
30399			if (!tooltip) {
30400				tooltip = new Tooltip({type: 'tooltip'});
30401				tooltip.renderTo();
30402			}
30403
30404			return tooltip;
30405		},
30406
30407		/**
30408		 * Sets/gets the active state of the widget.
30409		 *
30410		 * @method active
30411		 * @param {Boolean} [state] State if the control is active.
30412		 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
30413		 */
30414		active: function(state) {
30415			var self = this, undef;
30416
30417			if (state !== undef) {
30418				self.aria('pressed', state);
30419				self.toggleClass('active', state);
30420			}
30421
30422			return self._super(state);
30423		},
30424
30425		/**
30426		 * Sets/gets the disabled state of the widget.
30427		 *
30428		 * @method disabled
30429		 * @param {Boolean} [state] State if the control is disabled.
30430		 * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
30431		 */
30432		disabled: function(state) {
30433			var self = this, undef;
30434
30435			if (state !== undef) {
30436				self.aria('disabled', state);
30437				self.toggleClass('disabled', state);
30438			}
30439
30440			return self._super(state);
30441		},
30442
30443		/**
30444		 * Called after the control has been rendered.
30445		 *
30446		 * @method postRender
30447		 */
30448		postRender: function() {
30449			var self = this, settings = self.settings;
30450
30451			self._rendered = true;
30452
30453			self._super();
30454
30455			if (!self.parent() && (settings.width || settings.height)) {
30456				self.initLayoutRect();
30457				self.repaint();
30458			}
30459
30460			if (settings.autofocus) {
30461				self.focus();
30462			}
30463		},
30464
30465		/**
30466		 * Removes the current control from DOM and from UI collections.
30467		 *
30468		 * @method remove
30469		 * @return {tinymce.ui.Control} Current control instance.
30470		 */
30471		remove: function() {
30472			this._super();
30473
30474			if (tooltip) {
30475				tooltip.remove();
30476				tooltip = null;
30477			}
30478		}
30479	});
30480
30481	return Widget;
30482});
30483
30484// Included from: js/tinymce/classes/ui/Button.js
30485
30486/**
30487 * Button.js
30488 *
30489 * Copyright, Moxiecode Systems AB
30490 * Released under LGPL License.
30491 *
30492 * License: http://www.tinymce.com/license
30493 * Contributing: http://www.tinymce.com/contributing
30494 */
30495
30496/**
30497 * This class is used to create buttons. You can create them directly or through the Factory.
30498 *
30499 * @example
30500 * // Create and render a button to the body element
30501 * tinymce.ui.Factory.create({
30502 *     type: 'button',
30503 *     text: 'My button'
30504 * }).renderTo(document.body);
30505 *
30506 * @-x-less Button.less
30507 * @class tinymce.ui.Button
30508 * @extends tinymce.ui.Widget
30509 */
30510define("tinymce/ui/Button", [
30511	"tinymce/ui/Widget"
30512], function(Widget) {
30513	"use strict";
30514
30515	return Widget.extend({
30516		Defaults: {
30517			classes: "widget btn",
30518			role: "button"
30519		},
30520
30521		/**
30522		 * Constructs a new button instance with the specified settings.
30523		 *
30524		 * @constructor
30525		 * @param {Object} settings Name/value object with settings.
30526		 * @setting {String} size Size of the button small|medium|large.
30527		 * @setting {String} image Image to use for icon.
30528		 * @setting {String} icon Icon to use for button.
30529		 */
30530		init: function(settings) {
30531			var self = this, size;
30532
30533			self.on('click mousedown', function(e) {
30534				e.preventDefault();
30535			});
30536
30537			self._super(settings);
30538			size = settings.size;
30539
30540			if (settings.subtype) {
30541				self.addClass(settings.subtype);
30542			}
30543
30544			if (size) {
30545				self.addClass('btn-' + size);
30546			}
30547		},
30548
30549		/**
30550		 * Sets/gets the current button icon.
30551		 *
30552		 * @method icon
30553		 * @param {String} [icon] New icon identifier.
30554		 * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
30555		 */
30556		icon: function(icon) {
30557			var self = this, prefix = self.classPrefix;
30558
30559			if (typeof(icon) == 'undefined') {
30560				return self.settings.icon;
30561			}
30562
30563			self.settings.icon = icon;
30564			icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
30565
30566			if (self._rendered) {
30567				var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
30568
30569				if (icon) {
30570					if (!iconElm || iconElm != btnElm.firstChild) {
30571						iconElm = document.createElement('i');
30572						btnElm.insertBefore(iconElm, btnElm.firstChild);
30573					}
30574
30575					iconElm.className = icon;
30576				} else if (iconElm) {
30577					btnElm.removeChild(iconElm);
30578				}
30579
30580				self.text(self._text); // Set text again to fix whitespace between icon + text
30581			}
30582
30583			return self;
30584		},
30585
30586		/**
30587		 * Repaints the button for example after it's been resizes by a layout engine.
30588		 *
30589		 * @method repaint
30590		 */
30591		repaint: function() {
30592			var btnStyle = this.getEl().firstChild.style;
30593
30594			btnStyle.width = btnStyle.height = "100%";
30595
30596			this._super();
30597		},
30598
30599		/**
30600		 * Sets/gets the current button text.
30601		 *
30602		 * @method text
30603		 * @param {String} [text] New button text.
30604		 * @return {String|tinymce.ui.Button} Current text or current Button instance.
30605		 */
30606		text: function(text) {
30607			var self = this;
30608
30609			if (self._rendered) {
30610				var textNode = self.getEl().lastChild.lastChild;
30611				if (textNode) {
30612					textNode.data = self.translate(text);
30613				}
30614			}
30615
30616			return self._super(text);
30617		},
30618
30619		/**
30620		 * Renders the control as a HTML string.
30621		 *
30622		 * @method renderHtml
30623		 * @return {String} HTML representing the control.
30624		 */
30625		renderHtml: function() {
30626			var self = this, id = self._id, prefix = self.classPrefix;
30627			var icon = self.settings.icon, image;
30628
30629			image = self.settings.image;
30630			if (image) {
30631				icon = 'none';
30632
30633				// Support for [high dpi, low dpi] image sources
30634				if (typeof image != "string") {
30635					image = window.getSelection ? image[0] : image[1];
30636				}
30637
30638				image = ' style="background-image: url(\'' + image + '\')"';
30639			} else {
30640				image = '';
30641			}
30642
30643			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
30644
30645			return (
30646				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
30647					'<button role="presentation" type="button" tabindex="-1">' +
30648						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
30649						(self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
30650					'</button>' +
30651				'</div>'
30652			);
30653		}
30654	});
30655});
30656
30657// Included from: js/tinymce/classes/ui/ButtonGroup.js
30658
30659/**
30660 * ButtonGroup.js
30661 *
30662 * Copyright, Moxiecode Systems AB
30663 * Released under LGPL License.
30664 *
30665 * License: http://www.tinymce.com/license
30666 * Contributing: http://www.tinymce.com/contributing
30667 */
30668
30669/**
30670 * This control enables you to put multiple buttons into a group. This is
30671 * useful when you want to combine similar toolbar buttons into a group.
30672 *
30673 * @example
30674 * // Create and render a buttongroup with two buttons to the body element
30675 * tinymce.ui.Factory.create({
30676 *     type: 'buttongroup',
30677 *     items: [
30678 *         {text: 'Button A'},
30679 *         {text: 'Button B'}
30680 *     ]
30681 * }).renderTo(document.body);
30682 *
30683 * @-x-less ButtonGroup.less
30684 * @class tinymce.ui.ButtonGroup
30685 * @extends tinymce.ui.Container
30686 */
30687define("tinymce/ui/ButtonGroup", [
30688	"tinymce/ui/Container"
30689], function(Container) {
30690	"use strict";
30691
30692	return Container.extend({
30693		Defaults: {
30694			defaultType: 'button',
30695			role: 'group'
30696		},
30697
30698		/**
30699		 * Renders the control as a HTML string.
30700		 *
30701		 * @method renderHtml
30702		 * @return {String} HTML representing the control.
30703		 */
30704		renderHtml: function() {
30705			var self = this, layout = self._layout;
30706
30707			self.addClass('btn-group');
30708			self.preRender();
30709			layout.preRender(self);
30710
30711			return (
30712				'<div id="' + self._id + '" class="' + self.classes() + '">' +
30713					'<div id="' + self._id + '-body">' +
30714						(self.settings.html || '') + layout.renderHtml(self) +
30715					'</div>' +
30716				'</div>'
30717			);
30718		}
30719	});
30720});
30721
30722// Included from: js/tinymce/classes/ui/Checkbox.js
30723
30724/**
30725 * Checkbox.js
30726 *
30727 * Copyright, Moxiecode Systems AB
30728 * Released under LGPL License.
30729 *
30730 * License: http://www.tinymce.com/license
30731 * Contributing: http://www.tinymce.com/contributing
30732 */
30733
30734/**
30735 * This control creates a custom checkbox.
30736 *
30737 * @example
30738 * // Create and render a checkbox to the body element
30739 * tinymce.ui.Factory.create({
30740 *     type: 'checkbox',
30741 *     checked: true,
30742 *     text: 'My checkbox'
30743 * }).renderTo(document.body);
30744 *
30745 * @-x-less Checkbox.less
30746 * @class tinymce.ui.Checkbox
30747 * @extends tinymce.ui.Widget
30748 */
30749define("tinymce/ui/Checkbox", [
30750	"tinymce/ui/Widget"
30751], function(Widget) {
30752	"use strict";
30753
30754	return Widget.extend({
30755		Defaults: {
30756			classes: "checkbox",
30757			role: "checkbox",
30758			checked: false
30759		},
30760
30761		/**
30762		 * Constructs a new Checkbox instance with the specified settings.
30763		 *
30764		 * @constructor
30765		 * @param {Object} settings Name/value object with settings.
30766		 * @setting {Boolean} checked True if the checkbox should be checked by default.
30767		 */
30768		init: function(settings) {
30769			var self = this;
30770
30771			self._super(settings);
30772
30773			self.on('click mousedown', function(e) {
30774				e.preventDefault();
30775			});
30776
30777			self.on('click', function(e) {
30778				e.preventDefault();
30779
30780				if (!self.disabled()) {
30781					self.checked(!self.checked());
30782				}
30783			});
30784
30785			self.checked(self.settings.checked);
30786		},
30787
30788		/**
30789		 * Getter/setter function for the checked state.
30790		 *
30791		 * @method checked
30792		 * @param {Boolean} [state] State to be set.
30793		 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
30794		 */
30795		checked: function(state) {
30796			var self = this;
30797
30798			if (typeof state != "undefined") {
30799				if (state) {
30800					self.addClass('checked');
30801				} else {
30802					self.removeClass('checked');
30803				}
30804
30805				self._checked = state;
30806				self.aria('checked', state);
30807
30808				return self;
30809			}
30810
30811			return self._checked;
30812		},
30813
30814		/**
30815		 * Getter/setter function for the value state.
30816		 *
30817		 * @method value
30818		 * @param {Boolean} [state] State to be set.
30819		 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
30820		 */
30821		value: function(state) {
30822			return this.checked(state);
30823		},
30824
30825		/**
30826		 * Renders the control as a HTML string.
30827		 *
30828		 * @method renderHtml
30829		 * @return {String} HTML representing the control.
30830		 */
30831		renderHtml: function() {
30832			var self = this, id = self._id, prefix = self.classPrefix;
30833
30834			return (
30835				'<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
30836					'<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
30837					'<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
30838				'</div>'
30839			);
30840		}
30841	});
30842});
30843
30844// Included from: js/tinymce/classes/ui/ComboBox.js
30845
30846/**
30847 * ComboBox.js
30848 *
30849 * Copyright, Moxiecode Systems AB
30850 * Released under LGPL License.
30851 *
30852 * License: http://www.tinymce.com/license
30853 * Contributing: http://www.tinymce.com/contributing
30854 */
30855
30856/**
30857 * This class creates a combobox control. Select box that you select a value from or
30858 * type a value into.
30859 *
30860 * @-x-less ComboBox.less
30861 * @class tinymce.ui.ComboBox
30862 * @extends tinymce.ui.Widget
30863 */
30864define("tinymce/ui/ComboBox", [
30865	"tinymce/ui/Widget",
30866	"tinymce/ui/Factory",
30867	"tinymce/ui/DomUtils"
30868], function(Widget, Factory, DomUtils) {
30869	"use strict";
30870
30871	return Widget.extend({
30872		/**
30873		 * Constructs a new control instance with the specified settings.
30874		 *
30875		 * @constructor
30876		 * @param {Object} settings Name/value object with settings.
30877		 * @setting {String} placeholder Placeholder text to display.
30878		 */
30879		init: function(settings) {
30880			var self = this;
30881
30882			self._super(settings);
30883			self.addClass('combobox');
30884			self.subinput = true;
30885			self.ariaTarget = 'inp'; // TODO: Figure out a better way
30886
30887			settings = self.settings;
30888			settings.menu = settings.menu || settings.values;
30889
30890			if (settings.menu) {
30891				settings.icon = 'caret';
30892			}
30893
30894			self.on('click', function(e) {
30895				var elm = e.target, root = self.getEl();
30896
30897				while (elm && elm != root) {
30898					if (elm.id && elm.id.indexOf('-open') != -1) {
30899						self.fire('action');
30900
30901						if (settings.menu) {
30902							self.showMenu();
30903
30904							if (e.aria) {
30905								self.menu.items()[0].focus();
30906							}
30907						}
30908					}
30909
30910					elm = elm.parentNode;
30911				}
30912			});
30913
30914			// TODO: Rework this
30915			self.on('keydown', function(e) {
30916				if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
30917					self.parents().reverse().each(function(ctrl) {
30918						e.preventDefault();
30919						self.fire('change');
30920
30921						if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
30922							ctrl.fire('submit', {data: ctrl.toJSON()});
30923							return false;
30924						}
30925					});
30926				}
30927			});
30928
30929			if (settings.placeholder) {
30930				self.addClass('placeholder');
30931
30932				self.on('focusin', function() {
30933					if (!self._hasOnChange) {
30934						DomUtils.on(self.getEl('inp'), 'change', function() {
30935							self.fire('change');
30936						});
30937
30938						self._hasOnChange = true;
30939					}
30940
30941					if (self.hasClass('placeholder')) {
30942						self.getEl('inp').value = '';
30943						self.removeClass('placeholder');
30944					}
30945				});
30946
30947				self.on('focusout', function() {
30948					if (self.value().length === 0) {
30949						self.getEl('inp').value = settings.placeholder;
30950						self.addClass('placeholder');
30951					}
30952				});
30953			}
30954		},
30955
30956		showMenu: function() {
30957			var self = this, settings = self.settings, menu;
30958
30959			if (!self.menu) {
30960				menu = settings.menu || [];
30961
30962				// Is menu array then auto constuct menu control
30963				if (menu.length) {
30964					menu = {
30965						type: 'menu',
30966						items: menu
30967					};
30968				} else {
30969					menu.type = menu.type || 'menu';
30970				}
30971
30972				self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
30973				self.fire('createmenu');
30974				self.menu.reflow();
30975				self.menu.on('cancel', function(e) {
30976					if (e.control === self.menu) {
30977						self.focus();
30978					}
30979				});
30980
30981				self.menu.on('show hide', function(e) {
30982					e.control.items().each(function(ctrl) {
30983						ctrl.active(ctrl.value() == self.value());
30984					});
30985				}).fire('show');
30986
30987				self.menu.on('select', function(e) {
30988					self.value(e.control.value());
30989				});
30990
30991				self.on('focusin', function(e) {
30992					if (e.target.tagName.toUpperCase() == 'INPUT') {
30993						self.menu.hide();
30994					}
30995				});
30996
30997				self.aria('expanded', true);
30998			}
30999
31000			self.menu.show();
31001			self.menu.layoutRect({w: self.layoutRect().w});
31002			self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
31003		},
31004
31005		/**
31006		 * Getter/setter function for the control value.
31007		 *
31008		 * @method value
31009		 * @param {String} [value] Value to be set.
31010		 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
31011		 */
31012		value: function(value) {
31013			var self = this;
31014
31015			if (typeof(value) != "undefined") {
31016				self._value = value;
31017				self.removeClass('placeholder');
31018
31019				if (self._rendered) {
31020					self.getEl('inp').value = value;
31021				}
31022
31023				return self;
31024			}
31025
31026			if (self._rendered) {
31027				value = self.getEl('inp').value;
31028
31029				if (value != self.settings.placeholder) {
31030					return value;
31031				}
31032
31033				return '';
31034			}
31035
31036			return self._value;
31037		},
31038
31039		/**
31040		 * Getter/setter function for the disabled state.
31041		 *
31042		 * @method value
31043		 * @param {Boolean} [state] State to be set.
31044		 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
31045		 */
31046		disabled: function(state) {
31047			var self = this;
31048
31049			if (self._rendered && typeof(state) != 'undefined') {
31050				self.getEl('inp').disabled = state;
31051			}
31052
31053			return self._super(state);
31054		},
31055
31056		/**
31057		 * Focuses the input area of the control.
31058		 *
31059		 * @method focus
31060		 */
31061		focus: function() {
31062			this.getEl('inp').focus();
31063		},
31064
31065		/**
31066		 * Repaints the control after a layout operation.
31067		 *
31068		 * @method repaint
31069		 */
31070		repaint: function() {
31071			var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
31072			var width, lineHeight;
31073
31074			if (openElm) {
31075				width = rect.w - DomUtils.getSize(openElm).width - 10;
31076			} else {
31077				width = rect.w - 10;
31078			}
31079
31080			// Detect old IE 7+8 add lineHeight to align caret vertically in the middle
31081			var doc = document;
31082			if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
31083				lineHeight = (self.layoutRect().h - 2) + 'px';
31084			}
31085
31086			DomUtils.css(elm.firstChild, {
31087				width: width,
31088				lineHeight: lineHeight
31089			});
31090
31091			self._super();
31092
31093			return self;
31094		},
31095
31096		/**
31097		 * Post render method. Called after the control has been rendered to the target.
31098		 *
31099		 * @method postRender
31100		 * @return {tinymce.ui.ComboBox} Current combobox instance.
31101		 */
31102		postRender: function() {
31103			var self = this;
31104
31105			DomUtils.on(this.getEl('inp'), 'change', function() {
31106				self.fire('change');
31107			});
31108
31109			return self._super();
31110		},
31111
31112		remove: function() {
31113			DomUtils.off(this.getEl('inp'));
31114			this._super();
31115		},
31116
31117		/**
31118		 * Renders the control as a HTML string.
31119		 *
31120		 * @method renderHtml
31121		 * @return {String} HTML representing the control.
31122		 */
31123		renderHtml: function() {
31124			var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
31125			var value = settings.value || settings.placeholder || '';
31126			var icon, text, openBtnHtml = '', extraAttrs = '';
31127
31128			if ("spellcheck" in settings) {
31129				extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
31130			}
31131
31132			if (settings.maxLength) {
31133				extraAttrs += ' maxlength="' + settings.maxLength + '"';
31134			}
31135
31136			if (settings.size) {
31137				extraAttrs += ' size="' + settings.size + '"';
31138			}
31139
31140			if (settings.subtype) {
31141				extraAttrs += ' type="' + settings.subtype + '"';
31142			}
31143
31144			if (self.disabled()) {
31145				extraAttrs += ' disabled="disabled"';
31146			}
31147
31148			icon = settings.icon;
31149			if (icon && icon != 'caret') {
31150				icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
31151			}
31152
31153			text = self._text;
31154
31155			if (icon || text) {
31156				openBtnHtml = (
31157					'<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
31158						'<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
31159							(icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
31160							(text ? (icon ? ' ' : '') + text : '') +
31161						'</button>' +
31162					'</div>'
31163				);
31164
31165				self.addClass('has-open');
31166			}
31167
31168			return (
31169				'<div id="' + id + '" class="' + self.classes() + '">' +
31170					'<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
31171					value + '" hidefocus="1"' + extraAttrs + ' />' +
31172					openBtnHtml +
31173				'</div>'
31174			);
31175		}
31176	});
31177});
31178
31179// Included from: js/tinymce/classes/ui/ColorBox.js
31180
31181/**
31182 * ColorBox.js
31183 *
31184 * Copyright, Moxiecode Systems AB
31185 * Released under LGPL License.
31186 *
31187 * License: http://www.tinymce.com/license
31188 * Contributing: http://www.tinymce.com/contributing
31189 */
31190
31191/**
31192 * This widget lets you enter colors and browse for colors by pressing the color button. It also displays
31193 * a preview of the current color.
31194 *
31195 * @-x-less ColorBox.less
31196 * @class tinymce.ui.ColorBox
31197 * @extends tinymce.ui.ComboBox
31198 */
31199define("tinymce/ui/ColorBox", [
31200	"tinymce/ui/ComboBox"
31201], function(ComboBox) {
31202	"use strict";
31203
31204	return ComboBox.extend({
31205		/**
31206		 * Constructs a new control instance with the specified settings.
31207		 *
31208		 * @constructor
31209		 * @param {Object} settings Name/value object with settings.
31210		 */
31211		init: function(settings) {
31212			var self = this;
31213
31214			settings.spellcheck = false;
31215
31216			if (settings.onaction) {
31217				settings.icon = 'none';
31218			}
31219
31220			self._super(settings);
31221
31222			self.addClass('colorbox');
31223			self.on('change keyup postrender', function() {
31224				self.repaintColor(self.value());
31225			});
31226		},
31227
31228		repaintColor: function(value) {
31229			var elm = this.getEl().getElementsByTagName('i')[0];
31230
31231			if (elm) {
31232				try {
31233					elm.style.background = value;
31234				} catch (ex) {
31235					// Ignore
31236				}
31237			}
31238		},
31239
31240		value: function(value) {
31241			var self = this;
31242
31243			if (typeof value != "undefined") {
31244				if (self._rendered) {
31245					self.repaintColor(value);
31246				}
31247			}
31248
31249			return self._super(value);
31250		}
31251	});
31252});
31253
31254// Included from: js/tinymce/classes/ui/PanelButton.js
31255
31256/**
31257 * PanelButton.js
31258 *
31259 * Copyright, Moxiecode Systems AB
31260 * Released under LGPL License.
31261 *
31262 * License: http://www.tinymce.com/license
31263 * Contributing: http://www.tinymce.com/contributing
31264 */
31265
31266/**
31267 * Creates a new panel button.
31268 *
31269 * @class tinymce.ui.PanelButton
31270 * @extends tinymce.ui.Button
31271 */
31272define("tinymce/ui/PanelButton", [
31273	"tinymce/ui/Button",
31274	"tinymce/ui/FloatPanel"
31275], function(Button, FloatPanel) {
31276	"use strict";
31277
31278	return Button.extend({
31279		/**
31280		 * Shows the panel for the button.
31281		 *
31282		 * @method showPanel
31283		 */
31284		showPanel: function() {
31285			var self = this, settings = self.settings;
31286
31287			self.active(true);
31288
31289			if (!self.panel) {
31290				var panelSettings = settings.panel;
31291
31292				// Wrap panel in grid layout if type if specified
31293				// This makes it possible to add forms or other containers directly in the panel option
31294				if (panelSettings.type) {
31295					panelSettings = {
31296						layout: 'grid',
31297						items: panelSettings
31298					};
31299				}
31300
31301				panelSettings.role = panelSettings.role || 'dialog';
31302				panelSettings.popover = true;
31303				panelSettings.autohide = true;
31304				panelSettings.ariaRoot = true;
31305
31306				self.panel = new FloatPanel(panelSettings).on('hide', function() {
31307					self.active(false);
31308				}).on('cancel', function(e) {
31309					e.stopPropagation();
31310					self.focus();
31311					self.hidePanel();
31312				}).parent(self).renderTo(self.getContainerElm());
31313
31314				self.panel.fire('show');
31315				self.panel.reflow();
31316			} else {
31317				self.panel.show();
31318			}
31319
31320			self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
31321		},
31322
31323		/**
31324		 * Hides the panel for the button.
31325		 *
31326		 * @method hidePanel
31327		 */
31328		hidePanel: function() {
31329			var self = this;
31330
31331			if (self.panel) {
31332				self.panel.hide();
31333			}
31334		},
31335
31336		/**
31337		 * Called after the control has been rendered.
31338		 *
31339		 * @method postRender
31340		 */
31341		postRender: function() {
31342			var self = this;
31343
31344			self.aria('haspopup', true);
31345
31346			self.on('click', function(e) {
31347				if (e.control === self) {
31348					if (self.panel && self.panel.visible()) {
31349						self.hidePanel();
31350					} else {
31351						self.showPanel();
31352						self.panel.focus(!!e.aria);
31353					}
31354				}
31355			});
31356
31357			return self._super();
31358		},
31359
31360		remove: function() {
31361			if (this.panel) {
31362				this.panel.remove();
31363				this.panel = null;
31364			}
31365
31366			return this._super();
31367		}
31368	});
31369});
31370
31371// Included from: js/tinymce/classes/ui/ColorButton.js
31372
31373/**
31374 * ColorButton.js
31375 *
31376 * Copyright, Moxiecode Systems AB
31377 * Released under LGPL License.
31378 *
31379 * License: http://www.tinymce.com/license
31380 * Contributing: http://www.tinymce.com/contributing
31381 */
31382
31383/**
31384 * This class creates a color button control. This is a split button in which the main
31385 * button has a visual representation of the currently selected color. When clicked
31386 * the caret button displays a color picker, allowing the user to select a new color.
31387 *
31388 * @-x-less ColorButton.less
31389 * @class tinymce.ui.ColorButton
31390 * @extends tinymce.ui.PanelButton
31391 */
31392define("tinymce/ui/ColorButton", [
31393	"tinymce/ui/PanelButton",
31394	"tinymce/dom/DOMUtils"
31395], function(PanelButton, DomUtils) {
31396	"use strict";
31397
31398	var DOM = DomUtils.DOM;
31399
31400	return PanelButton.extend({
31401		/**
31402		 * Constructs a new ColorButton instance with the specified settings.
31403		 *
31404		 * @constructor
31405		 * @param {Object} settings Name/value object with settings.
31406		 */
31407		init: function(settings) {
31408			this._super(settings);
31409			this.addClass('colorbutton');
31410		},
31411
31412		/**
31413		 * Getter/setter for the current color.
31414		 *
31415		 * @method color
31416		 * @param {String} [color] Color to set.
31417		 * @return {String|tinymce.ui.ColorButton} Current color or current instance.
31418		 */
31419		color: function(color) {
31420			if (color) {
31421				this._color = color;
31422				this.getEl('preview').style.backgroundColor = color;
31423				return this;
31424			}
31425
31426			return this._color;
31427		},
31428
31429		/**
31430		 * Renders the control as a HTML string.
31431		 *
31432		 * @method renderHtml
31433		 * @return {String} HTML representing the control.
31434		 */
31435		renderHtml: function() {
31436			var self = this, id = self._id, prefix = self.classPrefix;
31437			var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
31438			var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
31439
31440			return (
31441				'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
31442					'<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
31443						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
31444						'<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
31445						(self._text ? (icon ? ' ' : '') + (self._text) : '') +
31446					'</button>' +
31447					'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
31448						' <i class="' + prefix + 'caret"></i>' +
31449					'</button>' +
31450				'</div>'
31451			);
31452		},
31453
31454		/**
31455		 * Called after the control has been rendered.
31456		 *
31457		 * @method postRender
31458		 */
31459		postRender: function() {
31460			var self = this, onClickHandler = self.settings.onclick;
31461
31462			self.on('click', function(e) {
31463				if (e.aria && e.aria.key == 'down') {
31464					return;
31465				}
31466
31467				if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
31468					e.stopImmediatePropagation();
31469					onClickHandler.call(self, e);
31470				}
31471			});
31472
31473			delete self.settings.onclick;
31474
31475			return self._super();
31476		}
31477	});
31478});
31479
31480// Included from: js/tinymce/classes/util/Color.js
31481
31482/**
31483 * Color.js
31484 *
31485 * Copyright, Moxiecode Systems AB
31486 * Released under LGPL License.
31487 *
31488 * License: http://www.tinymce.com/license
31489 * Contributing: http://www.tinymce.com/contributing
31490 */
31491
31492/**
31493 * This class lets you parse/serialize colors and convert rgb/hsb.
31494 *
31495 * @class tinymce.util.Color
31496 * @example
31497 * var white = new tinymce.util.Color({r: 255, g: 255, b: 255});
31498 * var red = new tinymce.util.Color('#FF0000');
31499 *
31500 * console.log(white.toHex(), red.toHsv());
31501 */
31502define("tinymce/util/Color", [], function() {
31503	var min = Math.min, max = Math.max, round = Math.round;
31504
31505	/**
31506	 * Constructs a new color instance.
31507	 *
31508	 * @constructor
31509	 * @method Color
31510	 * @param {String} value Optional initial value to parse.
31511	 */
31512	function Color(value) {
31513		var self = this, r = 0, g = 0, b = 0;
31514
31515		function rgb2hsv(r, g, b) {
31516			var h, s, v, d, minRGB, maxRGB;
31517
31518			h = 0;
31519			s = 0;
31520			v = 0;
31521			r = r / 255;
31522			g = g / 255;
31523			b = b / 255;
31524
31525			minRGB = min(r, min(g, b));
31526			maxRGB = max(r, max(g, b));
31527
31528			if (minRGB == maxRGB) {
31529				v = minRGB;
31530
31531				return {
31532					h: 0,
31533					s: 0,
31534					v: v * 100
31535				};
31536			}
31537
31538			/*eslint no-nested-ternary:0 */
31539			d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r);
31540			h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5);
31541			h = 60 * (h - d / (maxRGB - minRGB));
31542			s = (maxRGB - minRGB) / maxRGB;
31543			v = maxRGB;
31544
31545			return {
31546				h: round(h),
31547				s: round(s * 100),
31548				v: round(v * 100)
31549			};
31550		}
31551
31552		function hsvToRgb(hue, saturation, brightness) {
31553			var side, chroma, x, match;
31554
31555			hue = (parseInt(hue, 10) || 0) % 360;
31556			saturation = parseInt(saturation, 10) / 100;
31557			brightness = parseInt(brightness, 10) / 100;
31558			saturation = max(0, min(saturation, 1));
31559			brightness = max(0, min(brightness, 1));
31560
31561			if (saturation === 0) {
31562				r = g = b = round(255 * brightness);
31563				return;
31564			}
31565
31566			side = hue / 60;
31567			chroma = brightness * saturation;
31568			x = chroma * (1 - Math.abs(side % 2 - 1));
31569			match = brightness - chroma;
31570
31571			switch (Math.floor(side)) {
31572				case 0:
31573					r = chroma;
31574					g = x;
31575					b = 0;
31576					break;
31577
31578				case 1:
31579					r = x;
31580					g = chroma;
31581					b = 0;
31582					break;
31583
31584				case 2:
31585					r = 0;
31586					g = chroma;
31587					b = x;
31588					break;
31589
31590				case 3:
31591					r = 0;
31592					g = x;
31593					b = chroma;
31594					break;
31595
31596				case 4:
31597					r = x;
31598					g = 0;
31599					b = chroma;
31600					break;
31601
31602				case 5:
31603					r = chroma;
31604					g = 0;
31605					b = x;
31606					break;
31607
31608				default:
31609					r = g = b = 0;
31610			}
31611
31612			r = round(255 * (r + match));
31613			g = round(255 * (g + match));
31614			b = round(255 * (b + match));
31615		}
31616
31617		/**
31618		 * Returns the hex string of the current color. For example: #ff00ff
31619		 *
31620		 * @method toHex
31621		 * @return {String} Hex string of current color.
31622		 */
31623		function toHex() {
31624			function hex(val) {
31625				val = parseInt(val, 10).toString(16);
31626
31627				return val.length > 1 ? val : '0' + val;
31628			}
31629
31630			return '#' + hex(r) + hex(g) + hex(b);
31631		}
31632
31633		/**
31634		 * Returns the r, g, b values of the color. Each channel has a range from 0-255.
31635		 *
31636		 * @method toRgb
31637		 * @return {Object} Object with r, g, b fields.
31638		 */
31639		function toRgb() {
31640			return {
31641				r: r,
31642				g: g,
31643				b: b
31644			};
31645		}
31646
31647		/**
31648		 * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100.
31649		 *
31650		 * @method toHsv
31651		 * @return {Object} Object with h, s, v fields.
31652		 */
31653		function toHsv() {
31654			return rgb2hsv(r, g, b);
31655		}
31656
31657		/**
31658		 * Parses the specified value and populates the color instance.
31659		 *
31660		 * Supported format examples:
31661		 *  * rbg(255,0,0)
31662		 *  * #ff0000
31663		 *  * #fff
31664		 *  * {r: 255, g: 0, b: 0}
31665		 *  * {h: 360, s: 100, v: 100}
31666		 *
31667		 * @method parse
31668		 * @param {Object/String} value Color value to parse.
31669		 * @return {tinymce.util.Color} Current color instance.
31670		 */
31671		function parse(value) {
31672			var matches;
31673
31674			if (typeof value == 'object') {
31675				if ("r" in value) {
31676					r = value.r;
31677					g = value.g;
31678					b = value.b;
31679				} else if ("v" in value) {
31680					hsvToRgb(value.h, value.s, value.v);
31681				}
31682			} else {
31683				if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) {
31684					r = parseInt(matches[1], 10);
31685					g = parseInt(matches[2], 10);
31686					b = parseInt(matches[3], 10);
31687				} else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) {
31688					r = parseInt(matches[1], 16);
31689					g = parseInt(matches[2], 16);
31690					b = parseInt(matches[3], 16);
31691				} else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) {
31692					r = parseInt(matches[1] + matches[1], 16);
31693					g = parseInt(matches[2] + matches[2], 16);
31694					b = parseInt(matches[3] + matches[3], 16);
31695				}
31696			}
31697
31698			r = r < 0 ? 0 : (r > 255 ? 255 : r);
31699			g = g < 0 ? 0 : (g > 255 ? 255 : g);
31700			b = b < 0 ? 0 : (b > 255 ? 255 : b);
31701
31702			return self;
31703		}
31704
31705		if (value) {
31706			parse(value);
31707		}
31708
31709		self.toRgb = toRgb;
31710		self.toHsv = toHsv;
31711		self.toHex = toHex;
31712		self.parse = parse;
31713	}
31714
31715	return Color;
31716});
31717
31718// Included from: js/tinymce/classes/ui/ColorPicker.js
31719
31720/**
31721 * ColorPicker.js
31722 *
31723 * Copyright, Moxiecode Systems AB
31724 * Released under LGPL License.
31725 *
31726 * License: http://www.tinymce.com/license
31727 * Contributing: http://www.tinymce.com/contributing
31728 */
31729
31730/**
31731 * Color picker widget lets you select colors.
31732 *
31733 * @-x-less ColorPicker.less
31734 * @class tinymce.ui.ColorPicker
31735 * @extends tinymce.ui.Widget
31736 */
31737define("tinymce/ui/ColorPicker", [
31738	"tinymce/ui/Widget",
31739	"tinymce/ui/DragHelper",
31740	"tinymce/ui/DomUtils",
31741	"tinymce/util/Color"
31742], function(Widget, DragHelper, DomUtils, Color) {
31743	"use strict";
31744
31745	return Widget.extend({
31746		Defaults: {
31747			classes: "widget colorpicker"
31748		},
31749
31750		/**
31751		 * Constructs a new colorpicker instance with the specified settings.
31752		 *
31753		 * @constructor
31754		 * @param {Object} settings Name/value object with settings.
31755		 * @setting {String} color Initial color value.
31756		 */
31757		init: function(settings) {
31758			this._super(settings);
31759		},
31760
31761		postRender: function() {
31762			var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm;
31763
31764			hueRootElm = self.getEl('h');
31765			huePointElm = self.getEl('hp');
31766			svRootElm = self.getEl('sv');
31767			svPointElm = self.getEl('svp');
31768
31769			function getPos(elm, event) {
31770				var pos = DomUtils.getPos(elm), x, y;
31771
31772				x = event.pageX - pos.x;
31773				y = event.pageY - pos.y;
31774
31775				x = Math.max(0, Math.min(x / elm.clientWidth, 1));
31776				y = Math.max(0, Math.min(y / elm.clientHeight, 1));
31777
31778				return {
31779					x: x,
31780					y: y
31781				};
31782			}
31783
31784			function updateColor(hsv, hueUpdate) {
31785				var hue = (360 - hsv.h) / 360;
31786
31787				DomUtils.css(huePointElm, {
31788					top: (hue * 100) + '%'
31789				});
31790
31791				if (!hueUpdate) {
31792					DomUtils.css(svPointElm, {
31793						left: hsv.s + '%',
31794						top: (100 - hsv.v) + '%'
31795					});
31796				}
31797
31798				svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex();
31799				self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h});
31800			}
31801
31802			function updateSaturationAndValue(e) {
31803				var pos;
31804
31805				pos = getPos(svRootElm, e);
31806				hsv.s = pos.x * 100;
31807				hsv.v = (1 - pos.y) * 100;
31808
31809				updateColor(hsv);
31810				self.fire('change');
31811			}
31812
31813			function updateHue(e) {
31814				var pos;
31815
31816				pos = getPos(hueRootElm, e);
31817				hsv = color.toHsv();
31818				hsv.h = (1 - pos.y) * 360;
31819				updateColor(hsv, true);
31820				self.fire('change');
31821			}
31822
31823			self._repaint = function() {
31824				hsv = color.toHsv();
31825				updateColor(hsv);
31826			};
31827
31828			self._super();
31829
31830			self._svdraghelper = new DragHelper(self._id + '-sv', {
31831				start: updateSaturationAndValue,
31832				drag: updateSaturationAndValue
31833			});
31834
31835			self._hdraghelper = new DragHelper(self._id + '-h', {
31836				start: updateHue,
31837				drag: updateHue
31838			});
31839
31840			self._repaint();
31841		},
31842
31843		rgb: function() {
31844			return this.color().toRgb();
31845		},
31846
31847		value: function(value) {
31848			var self = this;
31849
31850			if (arguments.length) {
31851				self.color().parse(value);
31852
31853				if (self._rendered) {
31854					self._repaint();
31855				}
31856			} else {
31857				return self.color().toHex();
31858			}
31859		},
31860
31861		color: function() {
31862			if (!this._color) {
31863				this._color = new Color();
31864			}
31865
31866			return this._color;
31867		},
31868
31869		/**
31870		 * Renders the control as a HTML string.
31871		 *
31872		 * @method renderHtml
31873		 * @return {String} HTML representing the control.
31874		 */
31875		renderHtml: function() {
31876			var self = this, id = self._id, prefix = self.classPrefix, hueHtml;
31877			var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000';
31878
31879			function getOldIeFallbackHtml() {
31880				var i, l, html = '', gradientPrefix, stopsList;
31881
31882				gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=';
31883				stopsList = stops.split(',');
31884				for (i = 0, l = stopsList.length - 1; i < l; i++) {
31885					html += (
31886						'<div class="' + prefix + 'colorpicker-h-chunk" style="' +
31887							'height:' + (100 / l) + '%;' +
31888							gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' +
31889							'-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' +
31890						'"></div>'
31891					);
31892				}
31893
31894				return html;
31895			}
31896
31897			var gradientCssText = (
31898				'background: -ms-linear-gradient(top,' + stops + ');' +
31899				'background: linear-gradient(to bottom,' + stops + ');'
31900			);
31901
31902			hueHtml = (
31903				'<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' +
31904					getOldIeFallbackHtml() +
31905					'<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' +
31906				'</div>'
31907			);
31908
31909			return (
31910				'<div id="' + id + '" class="' + self.classes() + '">' +
31911					'<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' +
31912						'<div class="' + prefix + 'colorpicker-overlay1">' +
31913							'<div class="' + prefix + 'colorpicker-overlay2">' +
31914								'<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' +
31915									'<div class="' + prefix + 'colorpicker-selector2"></div>' +
31916								'</div>' +
31917							'</div>' +
31918						'</div>' +
31919					'</div>' +
31920					hueHtml +
31921				'</div>'
31922			);
31923		}
31924	});
31925});
31926
31927// Included from: js/tinymce/classes/ui/Path.js
31928
31929/**
31930 * Path.js
31931 *
31932 * Copyright, Moxiecode Systems AB
31933 * Released under LGPL License.
31934 *
31935 * License: http://www.tinymce.com/license
31936 * Contributing: http://www.tinymce.com/contributing
31937 */
31938
31939/**
31940 * Creates a new path control.
31941 *
31942 * @-x-less Path.less
31943 * @class tinymce.ui.Path
31944 * @extends tinymce.ui.Widget
31945 */
31946define("tinymce/ui/Path", [
31947	"tinymce/ui/Widget"
31948], function(Widget) {
31949	"use strict";
31950
31951	return Widget.extend({
31952		/**
31953		 * Constructs a instance with the specified settings.
31954		 *
31955		 * @constructor
31956		 * @param {Object} settings Name/value object with settings.
31957		 * @setting {String} delimiter Delimiter to display between items in path.
31958		 */
31959		init: function(settings) {
31960			var self = this;
31961
31962			if (!settings.delimiter) {
31963				settings.delimiter = '\u00BB';
31964			}
31965
31966			self._super(settings);
31967			self.addClass('path');
31968			self.canFocus = true;
31969
31970			self.on('click', function(e) {
31971				var index, target = e.target;
31972
31973				if ((index = target.getAttribute('data-index'))) {
31974					self.fire('select', {value: self.data()[index], index: index});
31975				}
31976			});
31977		},
31978
31979		/**
31980		 * Focuses the current control.
31981		 *
31982		 * @method focus
31983		 * @return {tinymce.ui.Control} Current control instance.
31984		 */
31985		focus: function() {
31986			var self = this;
31987
31988			self.getEl().firstChild.focus();
31989
31990			return self;
31991		},
31992
31993		/**
31994		 * Sets/gets the data to be used for the path.
31995		 *
31996		 * @method data
31997		 * @param {Array} data Array with items name is rendered to path.
31998		 */
31999		data: function(data) {
32000			var self = this;
32001
32002			if (typeof(data) !== "undefined") {
32003				self._data = data;
32004				self.update();
32005
32006				return self;
32007			}
32008
32009			return self._data;
32010		},
32011
32012		/**
32013		 * Updated the path.
32014		 *
32015		 * @private
32016		 */
32017		update: function() {
32018			this.innerHtml(this._getPathHtml());
32019		},
32020
32021		/**
32022		 * Called after the control has been rendered.
32023		 *
32024		 * @method postRender
32025		 */
32026		postRender: function() {
32027			var self = this;
32028
32029			self._super();
32030
32031			self.data(self.settings.data);
32032		},
32033
32034		/**
32035		 * Renders the control as a HTML string.
32036		 *
32037		 * @method renderHtml
32038		 * @return {String} HTML representing the control.
32039		 */
32040		renderHtml: function() {
32041			var self = this;
32042
32043			return (
32044				'<div id="' + self._id + '" class="' + self.classes() + '">' +
32045					self._getPathHtml() +
32046				'</div>'
32047			);
32048		},
32049
32050		_getPathHtml: function() {
32051			var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix;
32052
32053			for (i = 0, l = parts.length; i < l; i++) {
32054				html += (
32055					(i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
32056					'<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
32057					i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
32058				);
32059			}
32060
32061			if (!html) {
32062				html = '<div class="' + prefix + 'path-item">\u00a0</div>';
32063			}
32064
32065			return html;
32066		}
32067	});
32068});
32069
32070// Included from: js/tinymce/classes/ui/ElementPath.js
32071
32072/**
32073 * ElementPath.js
32074 *
32075 * Copyright, Moxiecode Systems AB
32076 * Released under LGPL License.
32077 *
32078 * License: http://www.tinymce.com/license
32079 * Contributing: http://www.tinymce.com/contributing
32080 */
32081
32082/**
32083 * This control creates an path for the current selections parent elements in TinyMCE.
32084 *
32085 * @class tinymce.ui.ElementPath
32086 * @extends tinymce.ui.Path
32087 */
32088define("tinymce/ui/ElementPath", [
32089	"tinymce/ui/Path",
32090	"tinymce/EditorManager"
32091], function(Path, EditorManager) {
32092	return Path.extend({
32093		/**
32094		 * Post render method. Called after the control has been rendered to the target.
32095		 *
32096		 * @method postRender
32097		 * @return {tinymce.ui.ElementPath} Current combobox instance.
32098		 */
32099		postRender: function() {
32100			var self = this, editor = EditorManager.activeEditor;
32101
32102			function isHidden(elm) {
32103				if (elm.nodeType === 1) {
32104					if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
32105						return true;
32106					}
32107
32108					if (elm.getAttribute('data-mce-type') === 'bookmark') {
32109						return true;
32110					}
32111				}
32112
32113				return false;
32114			}
32115
32116			self.on('select', function(e) {
32117				editor.focus();
32118				editor.selection.select(this.data()[e.index].element);
32119				editor.nodeChanged();
32120			});
32121
32122			editor.on('nodeChange', function(e) {
32123				var outParents = [], parents = e.parents, i = parents.length;
32124
32125				while (i--) {
32126					if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
32127						var args = editor.fire('ResolveName', {
32128							name: parents[i].nodeName.toLowerCase(),
32129							target: parents[i]
32130						});
32131
32132						if (!args.isDefaultPrevented()) {
32133							outParents.push({name: args.name, element: parents[i]});
32134						}
32135
32136						if (args.isPropagationStopped()) {
32137							break;
32138						}
32139					}
32140				}
32141
32142				self.data(outParents);
32143			});
32144
32145			return self._super();
32146		}
32147	});
32148});
32149
32150// Included from: js/tinymce/classes/ui/FormItem.js
32151
32152/**
32153 * FormItem.js
32154 *
32155 * Copyright, Moxiecode Systems AB
32156 * Released under LGPL License.
32157 *
32158 * License: http://www.tinymce.com/license
32159 * Contributing: http://www.tinymce.com/contributing
32160 */
32161
32162/**
32163 * This class is a container created by the form element with
32164 * a label and control item.
32165 *
32166 * @class tinymce.ui.FormItem
32167 * @extends tinymce.ui.Container
32168 * @setting {String} label Label to display for the form item.
32169 */
32170define("tinymce/ui/FormItem", [
32171	"tinymce/ui/Container"
32172], function(Container) {
32173	"use strict";
32174
32175	return Container.extend({
32176		Defaults: {
32177			layout: 'flex',
32178			align: 'center',
32179			defaults: {
32180				flex: 1
32181			}
32182		},
32183
32184		/**
32185		 * Renders the control as a HTML string.
32186		 *
32187		 * @method renderHtml
32188		 * @return {String} HTML representing the control.
32189		 */
32190		renderHtml: function() {
32191			var self = this, layout = self._layout, prefix = self.classPrefix;
32192
32193			self.addClass('formitem');
32194			layout.preRender(self);
32195
32196			return (
32197				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
32198					(self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
32199						self.settings.title + '</div>') : '') +
32200					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
32201						(self.settings.html || '') + layout.renderHtml(self) +
32202					'</div>' +
32203				'</div>'
32204			);
32205		}
32206	});
32207});
32208
32209// Included from: js/tinymce/classes/ui/Form.js
32210
32211/**
32212 * Form.js
32213 *
32214 * Copyright, Moxiecode Systems AB
32215 * Released under LGPL License.
32216 *
32217 * License: http://www.tinymce.com/license
32218 * Contributing: http://www.tinymce.com/contributing
32219 */
32220
32221/**
32222 * This class creates a form container. A form container has the ability
32223 * to automatically wrap items in tinymce.ui.FormItem instances.
32224 *
32225 * Each FormItem instance is a container for the label and the item.
32226 *
32227 * @example
32228 * tinymce.ui.Factory.create({
32229 *     type: 'form',
32230 *     items: [
32231 *         {type: 'textbox', label: 'My text box'}
32232 *     ]
32233 * }).renderTo(document.body);
32234 *
32235 * @class tinymce.ui.Form
32236 * @extends tinymce.ui.Container
32237 */
32238define("tinymce/ui/Form", [
32239	"tinymce/ui/Container",
32240	"tinymce/ui/FormItem",
32241	"tinymce/util/Tools"
32242], function(Container, FormItem, Tools) {
32243	"use strict";
32244
32245	return Container.extend({
32246		Defaults: {
32247			containerCls: 'form',
32248			layout: 'flex',
32249			direction: 'column',
32250			align: 'stretch',
32251			flex: 1,
32252			padding: 20,
32253			labelGap: 30,
32254			spacing: 10,
32255			callbacks: {
32256				submit: function() {
32257					this.submit();
32258				}
32259			}
32260		},
32261
32262		/**
32263		 * This method gets invoked before the control is rendered.
32264		 *
32265		 * @method preRender
32266		 */
32267		preRender: function() {
32268			var self = this, items = self.items();
32269
32270			if (!self.settings.formItemDefaults) {
32271				self.settings.formItemDefaults = {
32272					layout: 'flex',
32273					autoResize: "overflow",
32274					defaults: {flex: 1}
32275				};
32276			}
32277
32278			// Wrap any labeled items in FormItems
32279			items.each(function(ctrl) {
32280				var formItem, label = ctrl.settings.label;
32281
32282				if (label) {
32283					formItem = new FormItem(Tools.extend({
32284						items: {
32285							type: 'label',
32286							id: ctrl._id + '-l',
32287							text: label,
32288							flex: 0,
32289							forId: ctrl._id,
32290							disabled: ctrl.disabled()
32291						}
32292					}, self.settings.formItemDefaults));
32293
32294					formItem.type = 'formitem';
32295					ctrl.aria('labelledby', ctrl._id + '-l');
32296
32297					if (typeof(ctrl.settings.flex) == "undefined") {
32298						ctrl.settings.flex = 1;
32299					}
32300
32301					self.replace(ctrl, formItem);
32302					formItem.add(ctrl);
32303				}
32304			});
32305		},
32306
32307		/**
32308		 * Recalcs label widths.
32309		 *
32310		 * @private
32311		 */
32312		recalcLabels: function() {
32313			var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items;
32314
32315			if (self.settings.labelGapCalc === false) {
32316				return;
32317			}
32318
32319			if (self.settings.labelGapCalc == "children") {
32320				items = self.find('formitem');
32321			} else {
32322				items = self.items();
32323			}
32324
32325			items.filter('formitem').each(function(item) {
32326				var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
32327
32328				maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
32329				labels.push(labelCtrl);
32330			});
32331
32332			labelGap = self.settings.labelGap || 0;
32333
32334			i = labels.length;
32335			while (i--) {
32336				labels[i].settings.minWidth = maxLabelWidth + labelGap;
32337			}
32338		},
32339
32340		/**
32341		 * Getter/setter for the visibility state.
32342		 *
32343		 * @method visible
32344		 * @param {Boolean} [state] True/false state to show/hide.
32345		 * @return {tinymce.ui.Form|Boolean} True/false state or current control.
32346		 */
32347		visible: function(state) {
32348			var val = this._super(state);
32349
32350			if (state === true && this._rendered) {
32351				this.recalcLabels();
32352			}
32353
32354			return val;
32355		},
32356
32357		/**
32358		 * Fires a submit event with the serialized form.
32359		 *
32360		 * @method submit
32361		 * @return {Object} Event arguments object.
32362		 */
32363		submit: function() {
32364			return this.fire('submit', {data: this.toJSON()});
32365		},
32366
32367		/**
32368		 * Post render method. Called after the control has been rendered to the target.
32369		 *
32370		 * @method postRender
32371		 * @return {tinymce.ui.ComboBox} Current combobox instance.
32372		 */
32373		postRender: function() {
32374			var self = this;
32375
32376			self._super();
32377			self.recalcLabels();
32378			self.fromJSON(self.settings.data);
32379		}
32380	});
32381});
32382
32383// Included from: js/tinymce/classes/ui/FieldSet.js
32384
32385/**
32386 * FieldSet.js
32387 *
32388 * Copyright, Moxiecode Systems AB
32389 * Released under LGPL License.
32390 *
32391 * License: http://www.tinymce.com/license
32392 * Contributing: http://www.tinymce.com/contributing
32393 */
32394
32395/**
32396 * This class creates fieldset containers.
32397 *
32398 * @-x-less FieldSet.less
32399 * @class tinymce.ui.FieldSet
32400 * @extends tinymce.ui.Form
32401 */
32402define("tinymce/ui/FieldSet", [
32403	"tinymce/ui/Form"
32404], function(Form) {
32405	"use strict";
32406
32407	return Form.extend({
32408		Defaults: {
32409			containerCls: 'fieldset',
32410			layout: 'flex',
32411			direction: 'column',
32412			align: 'stretch',
32413			flex: 1,
32414			padding: "25 15 5 15",
32415			labelGap: 30,
32416			spacing: 10,
32417			border: 1
32418		},
32419
32420		/**
32421		 * Renders the control as a HTML string.
32422		 *
32423		 * @method renderHtml
32424		 * @return {String} HTML representing the control.
32425		 */
32426		renderHtml: function() {
32427			var self = this, layout = self._layout, prefix = self.classPrefix;
32428
32429			self.preRender();
32430			layout.preRender(self);
32431
32432			return (
32433				'<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
32434					(self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
32435						self.settings.title + '</legend>') : '') +
32436					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
32437						(self.settings.html || '') + layout.renderHtml(self) +
32438					'</div>' +
32439				'</fieldset>'
32440			);
32441		}
32442	});
32443});
32444
32445// Included from: js/tinymce/classes/ui/FilePicker.js
32446
32447/**
32448 * FilePicker.js
32449 *
32450 * Copyright, Moxiecode Systems AB
32451 * Released under LGPL License.
32452 *
32453 * License: http://www.tinymce.com/license
32454 * Contributing: http://www.tinymce.com/contributing
32455 */
32456
32457/*global tinymce:true */
32458
32459/**
32460 * This class creates a file picker control.
32461 *
32462 * @class tinymce.ui.FilePicker
32463 * @extends tinymce.ui.ComboBox
32464 */
32465define("tinymce/ui/FilePicker", [
32466	"tinymce/ui/ComboBox",
32467	"tinymce/util/Tools"
32468], function(ComboBox, Tools) {
32469	"use strict";
32470
32471	return ComboBox.extend({
32472		/**
32473		 * Constructs a new control instance with the specified settings.
32474		 *
32475		 * @constructor
32476		 * @param {Object} settings Name/value object with settings.
32477		 */
32478		init: function(settings) {
32479			var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings;
32480			var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;
32481
32482			settings.spellcheck = false;
32483
32484			fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
32485			if (fileBrowserCallbackTypes) {
32486				fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
32487			}
32488
32489			if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) {
32490				fileBrowserCallback = editorSettings.file_picker_callback;
32491				if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
32492					actionCallback = function() {
32493						var meta = self.fire('beforecall').meta;
32494
32495						meta = Tools.extend({filetype: settings.filetype}, meta);
32496
32497						// file_picker_callback(callback, currentValue, metaData)
32498						fileBrowserCallback.call(
32499							editor,
32500							function(value, meta) {
32501								self.value(value).fire('change', {meta: meta});
32502							},
32503							self.value(),
32504							meta
32505						);
32506					};
32507				} else {
32508					// Legacy callback: file_picker_callback(id, currentValue, filetype, window)
32509					fileBrowserCallback = editorSettings.file_browser_callback;
32510					if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
32511						actionCallback = function() {
32512							fileBrowserCallback(
32513								self.getEl('inp').id,
32514								self.value(),
32515								settings.filetype,
32516								window
32517							);
32518						};
32519					}
32520				}
32521			}
32522
32523			if (actionCallback) {
32524				settings.icon = 'browse';
32525				settings.onaction = actionCallback;
32526			}
32527
32528			self._super(settings);
32529		}
32530	});
32531});
32532
32533// Included from: js/tinymce/classes/ui/FitLayout.js
32534
32535/**
32536 * FitLayout.js
32537 *
32538 * Copyright, Moxiecode Systems AB
32539 * Released under LGPL License.
32540 *
32541 * License: http://www.tinymce.com/license
32542 * Contributing: http://www.tinymce.com/contributing
32543 */
32544
32545/**
32546 * This layout manager will resize the control to be the size of it's parent container.
32547 * In other words width: 100% and height: 100%.
32548 *
32549 * @-x-less FitLayout.less
32550 * @class tinymce.ui.FitLayout
32551 * @extends tinymce.ui.AbsoluteLayout
32552 */
32553define("tinymce/ui/FitLayout", [
32554	"tinymce/ui/AbsoluteLayout"
32555], function(AbsoluteLayout) {
32556	"use strict";
32557
32558	return AbsoluteLayout.extend({
32559		/**
32560		 * Recalculates the positions of the controls in the specified container.
32561		 *
32562		 * @method recalc
32563		 * @param {tinymce.ui.Container} container Container instance to recalc.
32564		 */
32565		recalc: function(container) {
32566			var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox();
32567
32568			container.items().filter(':visible').each(function(ctrl) {
32569				ctrl.layoutRect({
32570					x: paddingBox.left,
32571					y: paddingBox.top,
32572					w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
32573					h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
32574				});
32575
32576				if (ctrl.recalc) {
32577					ctrl.recalc();
32578				}
32579			});
32580		}
32581	});
32582});
32583
32584// Included from: js/tinymce/classes/ui/FlexLayout.js
32585
32586/**
32587 * FlexLayout.js
32588 *
32589 * Copyright, Moxiecode Systems AB
32590 * Released under LGPL License.
32591 *
32592 * License: http://www.tinymce.com/license
32593 * Contributing: http://www.tinymce.com/contributing
32594 */
32595
32596/**
32597 * This layout manager works similar to the CSS flex box.
32598 *
32599 * @setting {String} direction row|row-reverse|column|column-reverse
32600 * @setting {Number} flex A positive-number to flex by.
32601 * @setting {String} align start|end|center|stretch
32602 * @setting {String} pack start|end|justify
32603 *
32604 * @class tinymce.ui.FlexLayout
32605 * @extends tinymce.ui.AbsoluteLayout
32606 */
32607define("tinymce/ui/FlexLayout", [
32608	"tinymce/ui/AbsoluteLayout"
32609], function(AbsoluteLayout) {
32610	"use strict";
32611
32612	return AbsoluteLayout.extend({
32613		/**
32614		 * Recalculates the positions of the controls in the specified container.
32615		 *
32616		 * @method recalc
32617		 * @param {tinymce.ui.Container} container Container instance to recalc.
32618		 */
32619		recalc: function(container) {
32620			// A ton of variables, needs to be in the same scope for performance
32621			var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
32622			var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
32623			var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
32624			var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
32625			var alignDeltaSizeName, alignContentSizeName;
32626			var max = Math.max, min = Math.min;
32627
32628			// Get container items, properties and settings
32629			items = container.items().filter(':visible');
32630			contLayoutRect = container.layoutRect();
32631			contPaddingBox = container._paddingBox;
32632			contSettings = container.settings;
32633			direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
32634			align = contSettings.align;
32635			pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
32636			spacing = contSettings.spacing || 0;
32637
32638			if (direction == "row-reversed" || direction == "column-reverse") {
32639				items = items.set(items.toArray().reverse());
32640				direction = direction.split('-')[0];
32641			}
32642
32643			// Setup axis variable name for row/column direction since the calculations is the same
32644			if (direction == "column") {
32645				posName = "y";
32646				sizeName = "h";
32647				minSizeName = "minH";
32648				maxSizeName = "maxH";
32649				innerSizeName = "innerH";
32650				beforeName = 'top';
32651				deltaSizeName = "deltaH";
32652				contentSizeName = "contentH";
32653
32654				alignBeforeName = "left";
32655				alignSizeName = "w";
32656				alignAxisName = "x";
32657				alignInnerSizeName = "innerW";
32658				alignMinSizeName = "minW";
32659				alignAfterName = "right";
32660				alignDeltaSizeName = "deltaW";
32661				alignContentSizeName = "contentW";
32662			} else {
32663				posName = "x";
32664				sizeName = "w";
32665				minSizeName = "minW";
32666				maxSizeName = "maxW";
32667				innerSizeName = "innerW";
32668				beforeName = 'left';
32669				deltaSizeName = "deltaW";
32670				contentSizeName = "contentW";
32671
32672				alignBeforeName = "top";
32673				alignSizeName = "h";
32674				alignAxisName = "y";
32675				alignInnerSizeName = "innerH";
32676				alignMinSizeName = "minH";
32677				alignAfterName = "bottom";
32678				alignDeltaSizeName = "deltaH";
32679				alignContentSizeName = "contentH";
32680			}
32681
32682			// Figure out total flex, availableSpace and collect any max size elements
32683			availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
32684			maxAlignEndPos = totalFlex = 0;
32685			for (i = 0, l = items.length; i < l; i++) {
32686				ctrl = items[i];
32687				ctrlLayoutRect = ctrl.layoutRect();
32688				ctrlSettings = ctrl.settings;
32689				flex = ctrlSettings.flex;
32690				availableSpace -= (i < l - 1 ? spacing : 0);
32691
32692				if (flex > 0) {
32693					totalFlex += flex;
32694
32695					// Flexed item has a max size then we need to check if we will hit that size
32696					if (ctrlLayoutRect[maxSizeName]) {
32697						maxSizeItems.push(ctrl);
32698					}
32699
32700					ctrlLayoutRect.flex = flex;
32701				}
32702
32703				availableSpace -= ctrlLayoutRect[minSizeName];
32704
32705				// Calculate the align end position to be used to check for overflow/underflow
32706				size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
32707				if (size > maxAlignEndPos) {
32708					maxAlignEndPos = size;
32709				}
32710			}
32711
32712			// Calculate minW/minH
32713			rect = {};
32714			if (availableSpace < 0) {
32715				rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
32716			} else {
32717				rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
32718			}
32719
32720			rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
32721
32722			rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
32723			rect[alignContentSizeName] = maxAlignEndPos;
32724			rect.minW = min(rect.minW, contLayoutRect.maxW);
32725			rect.minH = min(rect.minH, contLayoutRect.maxH);
32726			rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
32727			rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
32728
32729			// Resize container container if minSize was changed
32730			if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
32731				rect.w = rect.minW;
32732				rect.h = rect.minH;
32733
32734				container.layoutRect(rect);
32735				this.recalc(container);
32736
32737				// Forced recalc for example if items are hidden/shown
32738				if (container._lastRect === null) {
32739					var parentCtrl = container.parent();
32740					if (parentCtrl) {
32741						parentCtrl._lastRect = null;
32742						parentCtrl.recalc();
32743					}
32744				}
32745
32746				return;
32747			}
32748
32749			// Handle max size elements, check if they will become to wide with current options
32750			ratio = availableSpace / totalFlex;
32751			for (i = 0, l = maxSizeItems.length; i < l; i++) {
32752				ctrl = maxSizeItems[i];
32753				ctrlLayoutRect = ctrl.layoutRect();
32754				maxSize = ctrlLayoutRect[maxSizeName];
32755				size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
32756
32757				if (size > maxSize) {
32758					availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
32759					totalFlex -= ctrlLayoutRect.flex;
32760					ctrlLayoutRect.flex = 0;
32761					ctrlLayoutRect.maxFlexSize = maxSize;
32762				} else {
32763					ctrlLayoutRect.maxFlexSize = 0;
32764				}
32765			}
32766
32767			// Setup new ratio, target layout rect, start position
32768			ratio = availableSpace / totalFlex;
32769			pos = contPaddingBox[beforeName];
32770			rect = {};
32771
32772			// Handle pack setting moves the start position to end, center
32773			if (totalFlex === 0) {
32774				if (pack == "end") {
32775					pos = availableSpace + contPaddingBox[beforeName];
32776				} else if (pack == "center") {
32777					pos = Math.round(
32778						(contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
32779					) + contPaddingBox[beforeName];
32780
32781					if (pos < 0) {
32782						pos = contPaddingBox[beforeName];
32783					}
32784				} else if (pack == "justify") {
32785					pos = contPaddingBox[beforeName];
32786					spacing = Math.floor(availableSpace / (items.length - 1));
32787				}
32788			}
32789
32790			// Default aligning (start) the other ones needs to be calculated while doing the layout
32791			rect[alignAxisName] = contPaddingBox[alignBeforeName];
32792
32793			// Start laying out controls
32794			for (i = 0, l = items.length; i < l; i++) {
32795				ctrl = items[i];
32796				ctrlLayoutRect = ctrl.layoutRect();
32797				size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
32798
32799				// Align the control on the other axis
32800				if (align === "center") {
32801					rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
32802				} else if (align === "stretch") {
32803					rect[alignSizeName] = max(
32804						ctrlLayoutRect[alignMinSizeName] || 0,
32805						contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
32806					);
32807					rect[alignAxisName] = contPaddingBox[alignBeforeName];
32808				} else if (align === "end") {
32809					rect[alignAxisName] = contLayoutRect[alignInnerSizeName]  - ctrlLayoutRect[alignSizeName]  - contPaddingBox.top;
32810				}
32811
32812				// Calculate new size based on flex
32813				if (ctrlLayoutRect.flex > 0) {
32814					size += ctrlLayoutRect.flex * ratio;
32815				}
32816
32817				rect[sizeName] = size;
32818				rect[posName] = pos;
32819				ctrl.layoutRect(rect);
32820
32821				// Recalculate containers
32822				if (ctrl.recalc) {
32823					ctrl.recalc();
32824				}
32825
32826				// Move x/y position
32827				pos += size + spacing;
32828			}
32829		}
32830	});
32831});
32832
32833// Included from: js/tinymce/classes/ui/FlowLayout.js
32834
32835/**
32836 * FlowLayout.js
32837 *
32838 * Copyright, Moxiecode Systems AB
32839 * Released under LGPL License.
32840 *
32841 * License: http://www.tinymce.com/license
32842 * Contributing: http://www.tinymce.com/contributing
32843 */
32844
32845/**
32846 * This layout manager will place the controls by using the browsers native layout.
32847 *
32848 * @-x-less FlowLayout.less
32849 * @class tinymce.ui.FlowLayout
32850 * @extends tinymce.ui.Layout
32851 */
32852define("tinymce/ui/FlowLayout", [
32853	"tinymce/ui/Layout"
32854], function(Layout) {
32855	return Layout.extend({
32856		Defaults: {
32857			containerClass: 'flow-layout',
32858			controlClass: 'flow-layout-item',
32859			endClass : 'break'
32860		},
32861
32862		/**
32863		 * Recalculates the positions of the controls in the specified container.
32864		 *
32865		 * @method recalc
32866		 * @param {tinymce.ui.Container} container Container instance to recalc.
32867		 */
32868		recalc: function(container) {
32869			container.items().filter(':visible').each(function(ctrl) {
32870				if (ctrl.recalc) {
32871					ctrl.recalc();
32872				}
32873			});
32874		}
32875	});
32876});
32877
32878// Included from: js/tinymce/classes/ui/FormatControls.js
32879
32880/**
32881 * FormatControls.js
32882 *
32883 * Copyright, Moxiecode Systems AB
32884 * Released under LGPL License.
32885 *
32886 * License: http://www.tinymce.com/license
32887 * Contributing: http://www.tinymce.com/contributing
32888 */
32889
32890/**
32891 * Internal class containing all TinyMCE specific control types such as
32892 * format listboxes, fontlist boxes, toolbar buttons etc.
32893 *
32894 * @class tinymce.ui.FormatControls
32895 */
32896define("tinymce/ui/FormatControls", [
32897	"tinymce/ui/Control",
32898	"tinymce/ui/Widget",
32899	"tinymce/ui/FloatPanel",
32900	"tinymce/util/Tools",
32901	"tinymce/EditorManager",
32902	"tinymce/Env"
32903], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
32904	var each = Tools.each;
32905
32906	EditorManager.on('AddEditor', function(e) {
32907		if (e.editor.rtl) {
32908			Control.rtl = true;
32909		}
32910
32911		registerControls(e.editor);
32912	});
32913
32914	Control.translate = function(text) {
32915		return EditorManager.translate(text);
32916	};
32917
32918	Widget.tooltips = !Env.iOS;
32919
32920	function registerControls(editor) {
32921		var formatMenu;
32922
32923		function createListBoxChangeHandler(items, formatName) {
32924			return function() {
32925				var self = this;
32926
32927				editor.on('nodeChange', function(e) {
32928					var formatter = editor.formatter;
32929					var value = null;
32930
32931					each(e.parents, function(node) {
32932						each(items, function(item) {
32933							if (formatName) {
32934								if (formatter.matchNode(node, formatName, {value: item.value})) {
32935									value = item.value;
32936								}
32937							} else {
32938								if (formatter.matchNode(node, item.value)) {
32939									value = item.value;
32940								}
32941							}
32942
32943							if (value) {
32944								return false;
32945							}
32946						});
32947
32948						if (value) {
32949							return false;
32950						}
32951					});
32952
32953					self.value(value);
32954				});
32955			};
32956		}
32957
32958		function createFormats(formats) {
32959			formats = formats.replace(/;$/, '').split(';');
32960
32961			var i = formats.length;
32962			while (i--) {
32963				formats[i] = formats[i].split('=');
32964			}
32965
32966			return formats;
32967		}
32968
32969		function createFormatMenu() {
32970			var count = 0, newFormats = [];
32971
32972			var defaultStyleFormats = [
32973				{title: 'Headings', items: [
32974					{title: 'Heading 1', format: 'h1'},
32975					{title: 'Heading 2', format: 'h2'},
32976					{title: 'Heading 3', format: 'h3'},
32977					{title: 'Heading 4', format: 'h4'},
32978					{title: 'Heading 5', format: 'h5'},
32979					{title: 'Heading 6', format: 'h6'}
32980				]},
32981
32982				{title: 'Inline', items: [
32983					{title: 'Bold', icon: 'bold', format: 'bold'},
32984					{title: 'Italic', icon: 'italic', format: 'italic'},
32985					{title: 'Underline', icon: 'underline', format: 'underline'},
32986					{title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
32987					{title: 'Superscript', icon: 'superscript', format: 'superscript'},
32988					{title: 'Subscript', icon: 'subscript', format: 'subscript'},
32989					{title: 'Code', icon: 'code', format: 'code'}
32990				]},
32991
32992				{title: 'Blocks', items: [
32993					{title: 'Paragraph', format: 'p'},
32994					{title: 'Blockquote', format: 'blockquote'},
32995					{title: 'Div', format: 'div'},
32996					{title: 'Pre', format: 'pre'}
32997				]},
32998
32999				{title: 'Alignment', items: [
33000					{title: 'Left', icon: 'alignleft', format: 'alignleft'},
33001					{title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
33002					{title: 'Right', icon: 'alignright', format: 'alignright'},
33003					{title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
33004				]}
33005			];
33006
33007			function createMenu(formats) {
33008				var menu = [];
33009
33010				if (!formats) {
33011					return;
33012				}
33013
33014				each(formats, function(format) {
33015					var menuItem = {
33016						text: format.title,
33017						icon: format.icon
33018					};
33019
33020					if (format.items) {
33021						menuItem.menu = createMenu(format.items);
33022					} else {
33023						var formatName = format.format || "custom" + count++;
33024
33025						if (!format.format) {
33026							format.name = formatName;
33027							newFormats.push(format);
33028						}
33029
33030						menuItem.format = formatName;
33031						menuItem.cmd = format.cmd;
33032					}
33033
33034					menu.push(menuItem);
33035				});
33036
33037				return menu;
33038			}
33039
33040			function createStylesMenu() {
33041				var menu;
33042
33043				if (editor.settings.style_formats_merge) {
33044					if (editor.settings.style_formats) {
33045						menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
33046					} else {
33047						menu = createMenu(defaultStyleFormats);
33048					}
33049				} else {
33050					menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
33051				}
33052
33053				return menu;
33054			}
33055
33056			editor.on('init', function() {
33057				each(newFormats, function(format) {
33058					editor.formatter.register(format.name, format);
33059				});
33060			});
33061
33062			return {
33063				type: 'menu',
33064				items: createStylesMenu(),
33065				onPostRender: function(e) {
33066					editor.fire('renderFormatsMenu', {control: e.control});
33067				},
33068				itemDefaults: {
33069					preview: true,
33070
33071					textStyle: function() {
33072						if (this.settings.format) {
33073							return editor.formatter.getCssText(this.settings.format);
33074						}
33075					},
33076
33077					onPostRender: function() {
33078						var self = this;
33079
33080						self.parent().on('show', function() {
33081							var formatName, command;
33082
33083							formatName = self.settings.format;
33084							if (formatName) {
33085								self.disabled(!editor.formatter.canApply(formatName));
33086								self.active(editor.formatter.match(formatName));
33087							}
33088
33089							command = self.settings.cmd;
33090							if (command) {
33091								self.active(editor.queryCommandState(command));
33092							}
33093						});
33094					},
33095
33096					onclick: function() {
33097						if (this.settings.format) {
33098							toggleFormat(this.settings.format);
33099						}
33100
33101						if (this.settings.cmd) {
33102							editor.execCommand(this.settings.cmd);
33103						}
33104					}
33105				}
33106			};
33107		}
33108
33109		formatMenu = createFormatMenu();
33110
33111		// Simple format controls <control/format>:<UI text>
33112		each({
33113			bold: 'Bold',
33114			italic: 'Italic',
33115			underline: 'Underline',
33116			strikethrough: 'Strikethrough',
33117			subscript: 'Subscript',
33118			superscript: 'Superscript'
33119		}, function(text, name) {
33120			editor.addButton(name, {
33121				tooltip: text,
33122				onPostRender: function() {
33123					var self = this;
33124
33125					// TODO: Fix this
33126					if (editor.formatter) {
33127						editor.formatter.formatChanged(name, function(state) {
33128							self.active(state);
33129						});
33130					} else {
33131						editor.on('init', function() {
33132							editor.formatter.formatChanged(name, function(state) {
33133								self.active(state);
33134							});
33135						});
33136					}
33137				},
33138				onclick: function() {
33139					toggleFormat(name);
33140				}
33141			});
33142		});
33143
33144		// Simple command controls <control>:[<UI text>,<Command>]
33145		each({
33146			outdent: ['Decrease indent', 'Outdent'],
33147			indent: ['Increase indent', 'Indent'],
33148			cut: ['Cut', 'Cut'],
33149			copy: ['Copy', 'Copy'],
33150			paste: ['Paste', 'Paste'],
33151			help: ['Help', 'mceHelp'],
33152			selectall: ['Select all', 'SelectAll'],
33153			removeformat: ['Clear formatting', 'RemoveFormat'],
33154			visualaid: ['Visual aids', 'mceToggleVisualAid'],
33155			newdocument: ['New document', 'mceNewDocument']
33156		}, function(item, name) {
33157			editor.addButton(name, {
33158				tooltip: item[0],
33159				cmd: item[1]
33160			});
33161		});
33162
33163		// Simple command controls with format state
33164		each({
33165			blockquote: ['Blockquote', 'mceBlockQuote'],
33166			numlist: ['Numbered list', 'InsertOrderedList'],
33167			bullist: ['Bullet list', 'InsertUnorderedList'],
33168			subscript: ['Subscript', 'Subscript'],
33169			superscript: ['Superscript', 'Superscript'],
33170			alignleft: ['Align left', 'JustifyLeft'],
33171			aligncenter: ['Align center', 'JustifyCenter'],
33172			alignright: ['Align right', 'JustifyRight'],
33173			alignjustify: ['Justify', 'JustifyFull']
33174		}, function(item, name) {
33175			editor.addButton(name, {
33176				tooltip: item[0],
33177				cmd: item[1],
33178				onPostRender: function() {
33179					var self = this;
33180
33181					// TODO: Fix this
33182					if (editor.formatter) {
33183						editor.formatter.formatChanged(name, function(state) {
33184							self.active(state);
33185						});
33186					} else {
33187						editor.on('init', function() {
33188							editor.formatter.formatChanged(name, function(state) {
33189								self.active(state);
33190							});
33191						});
33192					}
33193				}
33194			});
33195		});
33196
33197		function toggleUndoRedoState(type) {
33198			return function() {
33199				var self = this;
33200
33201				type = type == 'redo' ? 'hasRedo' : 'hasUndo';
33202
33203				function checkState() {
33204					return editor.undoManager ? editor.undoManager[type]() : false;
33205				}
33206
33207				self.disabled(!checkState());
33208				editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() {
33209					self.disabled(!checkState());
33210				});
33211			};
33212		}
33213
33214		function toggleVisualAidState() {
33215			var self = this;
33216
33217			editor.on('VisualAid', function(e) {
33218				self.active(e.hasVisual);
33219			});
33220
33221			self.active(editor.hasVisual);
33222		}
33223
33224		editor.addButton('undo', {
33225			tooltip: 'Undo',
33226			onPostRender: toggleUndoRedoState('undo'),
33227			cmd: 'undo'
33228		});
33229
33230		editor.addButton('redo', {
33231			tooltip: 'Redo',
33232			onPostRender: toggleUndoRedoState('redo'),
33233			cmd: 'redo'
33234		});
33235
33236		editor.addMenuItem('newdocument', {
33237			text: 'New document',
33238			shortcut: 'Ctrl+N',
33239			icon: 'newdocument',
33240			cmd: 'mceNewDocument'
33241		});
33242
33243		editor.addMenuItem('undo', {
33244			text: 'Undo',
33245			icon: 'undo',
33246			shortcut: 'Ctrl+Z',
33247			onPostRender: toggleUndoRedoState('undo'),
33248			cmd: 'undo'
33249		});
33250
33251		editor.addMenuItem('redo', {
33252			text: 'Redo',
33253			icon: 'redo',
33254			shortcut: 'Ctrl+Y',
33255			onPostRender: toggleUndoRedoState('redo'),
33256			cmd: 'redo'
33257		});
33258
33259		editor.addMenuItem('visualaid', {
33260			text: 'Visual aids',
33261			selectable: true,
33262			onPostRender: toggleVisualAidState,
33263			cmd: 'mceToggleVisualAid'
33264		});
33265
33266		each({
33267			cut: ['Cut', 'Cut', 'Ctrl+X'],
33268			copy: ['Copy', 'Copy', 'Ctrl+C'],
33269			paste: ['Paste', 'Paste', 'Ctrl+V'],
33270			selectall: ['Select all', 'SelectAll', 'Ctrl+A'],
33271			bold: ['Bold', 'Bold', 'Ctrl+B'],
33272			italic: ['Italic', 'Italic', 'Ctrl+I'],
33273			underline: ['Underline', 'Underline'],
33274			strikethrough: ['Strikethrough', 'Strikethrough'],
33275			subscript: ['Subscript', 'Subscript'],
33276			superscript: ['Superscript', 'Superscript'],
33277			removeformat: ['Clear formatting', 'RemoveFormat']
33278		}, function(item, name) {
33279			editor.addMenuItem(name, {
33280				text: item[0],
33281				icon: name,
33282				shortcut: item[2],
33283				cmd: item[1]
33284			});
33285		});
33286
33287		editor.on('mousedown', function() {
33288			FloatPanel.hideAll();
33289		});
33290
33291		function toggleFormat(fmt) {
33292			if (fmt.control) {
33293				fmt = fmt.control.value();
33294			}
33295
33296			if (fmt) {
33297				editor.execCommand('mceToggleFormat', false, fmt);
33298			}
33299		}
33300
33301		editor.addButton('styleselect', {
33302			type: 'menubutton',
33303			text: 'Formats',
33304			menu: formatMenu
33305		});
33306
33307		editor.addButton('formatselect', function() {
33308			var items = [], blocks = createFormats(editor.settings.block_formats ||
33309				'Paragraph=p;' +
33310				'Address=address;' +
33311				'Pre=pre;' +
33312				'Heading 1=h1;' +
33313				'Heading 2=h2;' +
33314				'Heading 3=h3;' +
33315				'Heading 4=h4;' +
33316				'Heading 5=h5;' +
33317				'Heading 6=h6'
33318			);
33319
33320			each(blocks, function(block) {
33321				items.push({
33322					text: block[0],
33323					value: block[1],
33324					textStyle: function() {
33325						return editor.formatter.getCssText(block[1]);
33326					}
33327				});
33328			});
33329
33330			return {
33331				type: 'listbox',
33332				text: blocks[0][0],
33333				values: items,
33334				fixedWidth: true,
33335				onselect: toggleFormat,
33336				onPostRender: createListBoxChangeHandler(items)
33337			};
33338		});
33339
33340		editor.addButton('fontselect', function() {
33341			var defaultFontsFormats =
33342				'Andale Mono=andale mono,times;' +
33343				'Arial=arial,helvetica,sans-serif;' +
33344				'Arial Black=arial black,avant garde;' +
33345				'Book Antiqua=book antiqua,palatino;' +
33346				'Comic Sans MS=comic sans ms,sans-serif;' +
33347				'Courier New=courier new,courier;' +
33348				'Georgia=georgia,palatino;' +
33349				'Helvetica=helvetica;' +
33350				'Impact=impact,chicago;' +
33351                                'Open Sans=Open Sans, sans-serif;' +
33352				'Symbol=symbol;' +
33353				'Tahoma=tahoma,arial,helvetica,sans-serif;' +
33354				'Terminal=terminal,monaco;' +
33355				'Times New Roman=times new roman,times;' +
33356				'Trebuchet MS=trebuchet ms,geneva;' +
33357				'Verdana=verdana,geneva;' +
33358				'Webdings=webdings;' +
33359				'Wingdings=wingdings,zapf dingbats';
33360
33361			var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
33362
33363			each(fonts, function(font) {
33364				items.push({
33365					text: {raw: font[0]},
33366					value: font[1],
33367					textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
33368				});
33369			});
33370
33371			return {
33372				type: 'listbox',
33373				text: 'Font Family',
33374				tooltip: 'Font Family',
33375				values: items,
33376				fixedWidth: true,
33377				onPostRender: createListBoxChangeHandler(items, 'fontname'),
33378				onselect: function(e) {
33379					if (e.control.settings.value) {
33380						editor.execCommand('FontName', false, e.control.settings.value);
33381					}
33382				}
33383			};
33384		});
33385
33386		editor.addButton('fontsizeselect', function() {
33387			var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 16pt 18pt 24pt 36pt';
33388			var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
33389
33390			each(fontsize_formats.split(' '), function(item) {
33391				var text = item, value = item;
33392				// Allow text=value font sizes.
33393				var values = item.split('=');
33394				if (values.length > 1) {
33395					text = values[0];
33396					value = values[1];
33397				}
33398				items.push({text: text, value: value});
33399			});
33400
33401			return {
33402				type: 'listbox',
33403				text: 'Font Sizes',
33404				tooltip: 'Font Sizes',
33405				values: items,
33406				fixedWidth: true,
33407				onPostRender: createListBoxChangeHandler(items, 'fontsize'),
33408				onclick: function(e) {
33409					if (e.control.settings.value) {
33410						editor.execCommand('FontSize', false, e.control.settings.value);
33411					}
33412				}
33413			};
33414		});
33415
33416		editor.addMenuItem('formats', {
33417			text: 'Formats',
33418			menu: formatMenu
33419		});
33420	}
33421});
33422
33423// Included from: js/tinymce/classes/ui/GridLayout.js
33424
33425/**
33426 * GridLayout.js
33427 *
33428 * Copyright, Moxiecode Systems AB
33429 * Released under LGPL License.
33430 *
33431 * License: http://www.tinymce.com/license
33432 * Contributing: http://www.tinymce.com/contributing
33433 */
33434
33435/**
33436 * This layout manager places controls in a grid.
33437 *
33438 * @setting {Number} spacing Spacing between controls.
33439 * @setting {Number} spacingH Horizontal spacing between controls.
33440 * @setting {Number} spacingV Vertical spacing between controls.
33441 * @setting {Number} columns Number of columns to use.
33442 * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
33443 * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
33444 * @setting {String} pack start|end
33445 *
33446 * @class tinymce.ui.GridLayout
33447 * @extends tinymce.ui.AbsoluteLayout
33448 */
33449define("tinymce/ui/GridLayout", [
33450	"tinymce/ui/AbsoluteLayout"
33451], function(AbsoluteLayout) {
33452	"use strict";
33453
33454	return AbsoluteLayout.extend({
33455		/**
33456		 * Recalculates the positions of the controls in the specified container.
33457		 *
33458		 * @method recalc
33459		 * @param {tinymce.ui.Container} container Container instance to recalc.
33460		 */
33461		recalc: function(container) {
33462			var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
33463				ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
33464				colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx;
33465
33466			// Get layout settings
33467			settings = container.settings;
33468			items = container.items().filter(':visible');
33469			contLayoutRect = container.layoutRect();
33470			cols = settings.columns || Math.ceil(Math.sqrt(items.length));
33471			rows = Math.ceil(items.length / cols);
33472			spacingH = settings.spacingH || settings.spacing || 0;
33473			spacingV = settings.spacingV || settings.spacing || 0;
33474			alignH = settings.alignH || settings.align;
33475			alignV = settings.alignV || settings.align;
33476			contPaddingBox = container._paddingBox;
33477			reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl();
33478
33479			if (alignH && typeof(alignH) == "string") {
33480				alignH = [alignH];
33481			}
33482
33483			if (alignV && typeof(alignV) == "string") {
33484				alignV = [alignV];
33485			}
33486
33487			// Zero padd columnWidths
33488			for (x = 0; x < cols; x++) {
33489				colWidths.push(0);
33490			}
33491
33492			// Zero padd rowHeights
33493			for (y = 0; y < rows; y++) {
33494				rowHeights.push(0);
33495			}
33496
33497			// Calculate columnWidths and rowHeights
33498			for (y = 0; y < rows; y++) {
33499				for (x = 0; x < cols; x++) {
33500					ctrl = items[y * cols + x];
33501
33502					// Out of bounds
33503					if (!ctrl) {
33504						break;
33505					}
33506
33507					ctrlLayoutRect = ctrl.layoutRect();
33508					ctrlMinWidth = ctrlLayoutRect.minW;
33509					ctrlMinHeight = ctrlLayoutRect.minH;
33510
33511					colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
33512					rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
33513				}
33514			}
33515
33516			// Calculate maxX
33517			availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
33518			for (maxX = 0, x = 0; x < cols; x++) {
33519				maxX += colWidths[x] + (x > 0 ? spacingH : 0);
33520				availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
33521			}
33522
33523			// Calculate maxY
33524			availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
33525			for (maxY = 0, y = 0; y < rows; y++) {
33526				maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
33527				availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
33528			}
33529
33530			maxX += contPaddingBox.left + contPaddingBox.right;
33531			maxY += contPaddingBox.top + contPaddingBox.bottom;
33532
33533			// Calculate minW/minH
33534			rect = {};
33535			rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
33536			rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
33537
33538			rect.contentW = rect.minW - contLayoutRect.deltaW;
33539			rect.contentH = rect.minH - contLayoutRect.deltaH;
33540			rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
33541			rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
33542			rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
33543			rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
33544
33545			// Resize container container if minSize was changed
33546			if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
33547				rect.w = rect.minW;
33548				rect.h = rect.minH;
33549
33550				container.layoutRect(rect);
33551				this.recalc(container);
33552
33553				// Forced recalc for example if items are hidden/shown
33554				if (container._lastRect === null) {
33555					var parentCtrl = container.parent();
33556					if (parentCtrl) {
33557						parentCtrl._lastRect = null;
33558						parentCtrl.recalc();
33559					}
33560				}
33561
33562				return;
33563			}
33564
33565			// Update contentW/contentH so absEnd moves correctly
33566			if (contLayoutRect.autoResize) {
33567				rect = container.layoutRect(rect);
33568				rect.contentW = rect.minW - contLayoutRect.deltaW;
33569				rect.contentH = rect.minH - contLayoutRect.deltaH;
33570			}
33571
33572			var flexV;
33573
33574			if (settings.packV == 'start') {
33575				flexV = 0;
33576			} else {
33577				flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
33578			}
33579
33580			// Calculate totalFlex
33581			var totalFlex = 0;
33582			var flexWidths = settings.flexWidths;
33583			if (flexWidths) {
33584				for (x = 0; x < flexWidths.length; x++) {
33585					totalFlex += flexWidths[x];
33586				}
33587			} else {
33588				totalFlex = cols;
33589			}
33590
33591			// Calculate new column widths based on flex values
33592			var ratio = availableWidth / totalFlex;
33593			for (x = 0; x < cols; x++) {
33594				colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
33595			}
33596
33597			// Move/resize controls
33598			posY = contPaddingBox.top;
33599			for (y = 0; y < rows; y++) {
33600				posX = contPaddingBox.left;
33601				height = rowHeights[y] + flexV;
33602
33603				for (x = 0; x < cols; x++) {
33604					if (reverseRows) {
33605						idx = y * cols + cols - 1 - x;
33606					} else {
33607						idx = y * cols + x;
33608					}
33609
33610					ctrl = items[idx];
33611
33612					// No more controls to render then break
33613					if (!ctrl) {
33614						break;
33615					}
33616
33617					// Get control settings and calculate x, y
33618					ctrlSettings = ctrl.settings;
33619					ctrlLayoutRect = ctrl.layoutRect();
33620					width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
33621					ctrlLayoutRect.x = posX;
33622					ctrlLayoutRect.y = posY;
33623
33624					// Align control horizontal
33625					align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
33626					if (align == "center") {
33627						ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
33628					} else if (align == "right") {
33629						ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
33630					} else if (align == "stretch") {
33631						ctrlLayoutRect.w = width;
33632					}
33633
33634					// Align control vertical
33635					align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
33636					if (align == "center") {
33637						ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
33638					} else if (align == "bottom") {
33639						ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
33640					} else if (align == "stretch") {
33641						ctrlLayoutRect.h = height;
33642					}
33643
33644					ctrl.layoutRect(ctrlLayoutRect);
33645
33646					posX += width + spacingH;
33647
33648					if (ctrl.recalc) {
33649						ctrl.recalc();
33650					}
33651				}
33652
33653				posY += height + spacingV;
33654			}
33655		}
33656	});
33657});
33658
33659// Included from: js/tinymce/classes/ui/Iframe.js
33660
33661/**
33662 * Iframe.js
33663 *
33664 * Copyright, Moxiecode Systems AB
33665 * Released under LGPL License.
33666 *
33667 * License: http://www.tinymce.com/license
33668 * Contributing: http://www.tinymce.com/contributing
33669 */
33670
33671/*jshint scripturl:true */
33672
33673/**
33674 * This class creates an iframe.
33675 *
33676 * @setting {String} url Url to open in the iframe.
33677 *
33678 * @-x-less Iframe.less
33679 * @class tinymce.ui.Iframe
33680 * @extends tinymce.ui.Widget
33681 */
33682define("tinymce/ui/Iframe", [
33683	"tinymce/ui/Widget"
33684], function(Widget) {
33685	"use strict";
33686
33687	return Widget.extend({
33688		/**
33689		 * Renders the control as a HTML string.
33690		 *
33691		 * @method renderHtml
33692		 * @return {String} HTML representing the control.
33693		 */
33694		renderHtml: function() {
33695			var self = this;
33696
33697			self.addClass('iframe');
33698			self.canFocus = false;
33699
33700			/*eslint no-script-url:0 */
33701			return (
33702				'<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
33703				(self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
33704			);
33705		},
33706
33707		/**
33708		 * Setter for the iframe source.
33709		 *
33710		 * @method src
33711		 * @param {String} src Source URL for iframe.
33712		 */
33713		src: function(src) {
33714			this.getEl().src = src;
33715		},
33716
33717		/**
33718		 * Inner HTML for the iframe.
33719		 *
33720		 * @method html
33721		 * @param {String} html HTML string to set as HTML inside the iframe.
33722		 * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
33723		 * @return {tinymce.ui.Iframe} Current iframe control.
33724		 */
33725		html: function(html, callback) {
33726			var self = this, body = this.getEl().contentWindow.document.body;
33727
33728			// Wait for iframe to initialize IE 10 takes time
33729			if (!body) {
33730				setTimeout(function() {
33731					self.html(html);
33732				}, 0);
33733			} else {
33734				body.innerHTML = html;
33735
33736				if (callback) {
33737					callback();
33738				}
33739			}
33740
33741			return this;
33742		}
33743	});
33744});
33745
33746// Included from: js/tinymce/classes/ui/Label.js
33747
33748/**
33749 * Label.js
33750 *
33751 * Copyright, Moxiecode Systems AB
33752 * Released under LGPL License.
33753 *
33754 * License: http://www.tinymce.com/license
33755 * Contributing: http://www.tinymce.com/contributing
33756 */
33757
33758/**
33759 * This class creates a label element. A label is a simple text control
33760 * that can be bound to other controls.
33761 *
33762 * @-x-less Label.less
33763 * @class tinymce.ui.Label
33764 * @extends tinymce.ui.Widget
33765 */
33766define("tinymce/ui/Label", [
33767	"tinymce/ui/Widget",
33768	"tinymce/ui/DomUtils"
33769], function(Widget, DomUtils) {
33770	"use strict";
33771
33772	return Widget.extend({
33773		/**
33774		 * Constructs a instance with the specified settings.
33775		 *
33776		 * @constructor
33777		 * @param {Object} settings Name/value object with settings.
33778		 * @param {Boolean} multiline Multiline label.
33779		 */
33780		init: function(settings) {
33781			var self = this;
33782
33783			self._super(settings);
33784			self.addClass('widget');
33785			self.addClass('label');
33786			self.canFocus = false;
33787
33788			if (settings.multiline) {
33789				self.addClass('autoscroll');
33790			}
33791
33792			if (settings.strong) {
33793				self.addClass('strong');
33794			}
33795		},
33796
33797		/**
33798		 * Initializes the current controls layout rect.
33799		 * This will be executed by the layout managers to determine the
33800		 * default minWidth/minHeight etc.
33801		 *
33802		 * @method initLayoutRect
33803		 * @return {Object} Layout rect instance.
33804		 */
33805		initLayoutRect: function() {
33806			var self = this, layoutRect = self._super();
33807
33808			if (self.settings.multiline) {
33809				var size = DomUtils.getSize(self.getEl());
33810
33811				// Check if the text fits within maxW if not then try word wrapping it
33812				if (size.width > layoutRect.maxW) {
33813					layoutRect.minW = layoutRect.maxW;
33814					self.addClass('multiline');
33815				}
33816
33817				self.getEl().style.width = layoutRect.minW + 'px';
33818				layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
33819			}
33820
33821			return layoutRect;
33822		},
33823
33824		/**
33825		 * Repaints the control after a layout operation.
33826		 *
33827		 * @method repaint
33828		 */
33829		repaint: function() {
33830			var self = this;
33831
33832			if (!self.settings.multiline) {
33833				self.getEl().style.lineHeight = self.layoutRect().h + 'px';
33834			}
33835
33836			return self._super();
33837		},
33838
33839		/**
33840		 * Sets/gets the current label text.
33841		 *
33842		 * @method text
33843		 * @param {String} [text] New label text.
33844		 * @return {String|tinymce.ui.Label} Current text or current label instance.
33845		 */
33846		text: function(text) {
33847			var self = this;
33848
33849			if (self._rendered && text) {
33850				this.innerHtml(self.encode(text));
33851			}
33852
33853			return self._super(text);
33854		},
33855
33856		/**
33857		 * Renders the control as a HTML string.
33858		 *
33859		 * @method renderHtml
33860		 * @return {String} HTML representing the control.
33861		 */
33862		renderHtml: function() {
33863			var self = this, forId = self.settings.forId;
33864
33865			return (
33866				'<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>
33866' +
33867					self.encode(self._text) +
33868				'</label>'
33869			);
33870		}
33871	});
33872});
33873
33874// Included from: js/tinymce/classes/ui/Toolbar.js
33875
33876/**
33877 * Toolbar.js
33878 *
33879 * Copyright, Moxiecode Systems AB
33880 * Released under LGPL License.
33881 *
33882 * License: http://www.tinymce.com/license
33883 * Contributing: http://www.tinymce.com/contributing
33884 */
33885
33886/**
33887 * Creates a new toolbar.
33888 *
33889 * @class tinymce.ui.Toolbar
33890 * @extends tinymce.ui.Container
33891 */
33892define("tinymce/ui/Toolbar", [
33893	"tinymce/ui/Container"
33894], function(Container) {
33895	"use strict";
33896
33897	return Container.extend({
33898		Defaults: {
33899			role: 'toolbar',
33900			layout: 'flow'
33901		},
33902
33903		/**
33904		 * Constructs a instance with the specified settings.
33905		 *
33906		 * @constructor
33907		 * @param {Object} settings Name/value object with settings.
33908		 */
33909		init: function(settings) {
33910			var self = this;
33911
33912			self._super(settings);
33913			self.addClass('toolbar');
33914		},
33915
33916		/**
33917		 * Called after the control has been rendered.
33918		 *
33919		 * @method postRender
33920		 */
33921		postRender: function() {
33922			var self = this;
33923
33924			self.items().addClass('toolbar-item');
33925
33926			return self._super();
33927		}
33928	});
33929});
33930
33931// Included from: js/tinymce/classes/ui/MenuBar.js
33932
33933/**
33934 * MenuBar.js
33935 *
33936 * Copyright, Moxiecode Systems AB
33937 * Released under LGPL License.
33938 *
33939 * License: http://www.tinymce.com/license
33940 * Contributing: http://www.tinymce.com/contributing
33941 */
33942
33943/**
33944 * Creates a new menubar.
33945 *
33946 * @-x-less MenuBar.less
33947 * @class tinymce.ui.MenuBar
33948 * @extends tinymce.ui.Toolbar
33949 */
33950define("tinymce/ui/MenuBar", [
33951	"tinymce/ui/Toolbar"
33952], function(Toolbar) {
33953	"use strict";
33954
33955	return Toolbar.extend({
33956		Defaults: {
33957			role: 'menubar',
33958			containerCls: 'menubar',
33959			ariaRoot: true,
33960			defaults: {
33961				type: 'menubutton'
33962			}
33963		}
33964	});
33965});
33966
33967// Included from: js/tinymce/classes/ui/MenuButton.js
33968
33969/**
33970 * MenuButton.js
33971 *
33972 * Copyright, Moxiecode Systems AB
33973 * Released under LGPL License.
33974 *
33975 * License: http://www.tinymce.com/license
33976 * Contributing: http://www.tinymce.com/contributing
33977 */
33978
33979/**
33980 * Creates a new menu button.
33981 *
33982 * @-x-less MenuButton.less
33983 * @class tinymce.ui.MenuButton
33984 * @extends tinymce.ui.Button
33985 */
33986define("tinymce/ui/MenuButton", [
33987	"tinymce/ui/Button",
33988	"tinymce/ui/Factory",
33989	"tinymce/ui/MenuBar"
33990], function(Button, Factory, MenuBar) {
33991	"use strict";
33992
33993	// TODO: Maybe add as some global function
33994	function isChildOf(node, parent) {
33995		while (node) {
33996			if (parent === node) {
33997				return true;
33998			}
33999
34000			node = node.parentNode;
34001		}
34002
34003		return false;
34004	}
34005
34006	var MenuButton = Button.extend({
34007		/**
34008		 * Constructs a instance with the specified settings.
34009		 *
34010		 * @constructor
34011		 * @param {Object} settings Name/value object with settings.
34012		 */
34013		init: function(settings) {
34014			var self = this;
34015
34016			self._renderOpen = true;
34017			self._super(settings);
34018
34019			self.addClass('menubtn');
34020
34021			if (settings.fixedWidth) {
34022				self.addClass('fixed-width');
34023			}
34024
34025			self.aria('haspopup', true);
34026			self.hasPopup = true;
34027		},
34028
34029		/**
34030		 * Shows the menu for the button.
34031		 *
34032		 * @method showMenu
34033		 */
34034		showMenu: function() {
34035			var self = this, settings = self.settings, menu;
34036
34037			if (self.menu && self.menu.visible()) {
34038				return self.hideMenu();
34039			}
34040
34041			if (!self.menu) {
34042				menu = settings.menu || [];
34043
34044				// Is menu array then auto constuct menu control
34045				if (menu.length) {
34046					menu = {
34047						type: 'menu',
34048						items: menu
34049					};
34050				} else {
34051					menu.type = menu.type || 'menu';
34052				}
34053
34054				self.menu = Factory.create(menu).parent(self).renderTo();
34055				self.fire('createmenu');
34056				self.menu.reflow();
34057				self.menu.on('cancel', function(e) {
34058					if (e.control.parent() === self.menu) {
34059						e.stopPropagation();
34060						self.focus();
34061						self.hideMenu();
34062					}
34063				});
34064
34065				// Move focus to button when a menu item is selected/clicked
34066				self.menu.on('select', function() {
34067					self.focus();
34068				});
34069
34070				self.menu.on('show hide', function(e) {
34071					if (e.control == self.menu) {
34072						self.activeMenu(e.type == 'show');
34073					}
34074
34075					self.aria('expanded', e.type == 'show');
34076				}).fire('show');
34077			}
34078
34079			self.menu.show();
34080			self.menu.layoutRect({w: self.layoutRect().w});
34081			self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
34082		},
34083
34084		/**
34085		 * Hides the menu for the button.
34086		 *
34087		 * @method hideMenu
34088		 */
34089		hideMenu: function() {
34090			var self = this;
34091
34092			if (self.menu) {
34093				self.menu.items().each(function(item) {
34094					if (item.hideMenu) {
34095						item.hideMenu();
34096					}
34097				});
34098
34099				self.menu.hide();
34100			}
34101		},
34102
34103		/**
34104		 * Sets the active menu state.
34105		 *
34106		 * @private
34107		 */
34108		activeMenu: function(state) {
34109			this.toggleClass('active', state);
34110		},
34111
34112		/**
34113		 * Renders the control as a HTML string.
34114		 *
34115		 * @method renderHtml
34116		 * @return {String} HTML representing the control.
34117		 */
34118		renderHtml: function() {
34119			var self = this, id = self._id, prefix = self.classPrefix;
34120			var icon = self.settings.icon, image;
34121
34122			image = self.settings.image;
34123			if (image) {
34124				icon = 'none';
34125
34126				// Support for [high dpi, low dpi] image sources
34127				if (typeof image != "string") {
34128					image = window.getSelection ? image[0] : image[1];
34129				}
34130
34131				image = ' style="background-image: url(\'' + image + '\')"';
34132			} else {
34133				image = '';
34134			}
34135
34136			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
34137
34138			self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
34139
34140			return (
34141				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
34142					'<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
34143						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
34144						'<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
34145						' <i class="' + prefix + 'caret"></i>' +
34146					'</button>' +
34147				'</div>'
34148			);
34149		},
34150
34151		/**
34152		 * Gets invoked after the control has been rendered.
34153		 *
34154		 * @method postRender
34155		 */
34156		postRender: function() {
34157			var self = this;
34158
34159			self.on('click', function(e) {
34160				if (e.control === self && isChildOf(e.target, self.getEl())) {
34161					self.showMenu();
34162
34163					if (e.aria) {
34164						self.menu.items()[0].focus();
34165					}
34166				}
34167			});
34168
34169			self.on('mouseenter', function(e) {
34170				var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
34171
34172				if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
34173					parent.items().filter('MenuButton').each(function(ctrl) {
34174						if (ctrl.hideMenu && ctrl != overCtrl) {
34175							if (ctrl.menu && ctrl.menu.visible()) {
34176								hasVisibleSiblingMenu = true;
34177							}
34178
34179							ctrl.hideMenu();
34180						}
34181					});
34182
34183					if (hasVisibleSiblingMenu) {
34184						overCtrl.focus(); // Fix for: #5887
34185						overCtrl.showMenu();
34186					}
34187				}
34188			});
34189
34190			return self._super();
34191		},
34192
34193		/**
34194		 * Sets/gets the current button text.
34195		 *
34196		 * @method text
34197		 * @param {String} [text] New button text.
34198		 * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance.
34199		 */
34200		text: function(text) {
34201			var self = this, i, children;
34202
34203			if (self._rendered) {
34204				children = self.getEl('open').getElementsByTagName('span');
34205				for (i = 0; i < children.length; i++) {
34206					children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text);
34207				}
34208			}
34209
34210			return this._super(text);
34211		},
34212
34213		/**
34214		 * Removes the control and it's menus.
34215		 *
34216		 * @method remove
34217		 */
34218		remove: function() {
34219			this._super();
34220
34221			if (this.menu) {
34222				this.menu.remove();
34223			}
34224		}
34225	});
34226
34227	return MenuButton;
34228});
34229
34230// Included from: js/tinymce/classes/ui/ListBox.js
34231
34232/**
34233 * ListBox.js
34234 *
34235 * Copyright, Moxiecode Systems AB
34236 * Released under LGPL License.
34237 *
34238 * License: http://www.tinymce.com/license
34239 * Contributing: http://www.tinymce.com/contributing
34240 */
34241
34242/**
34243 * Creates a new list box control.
34244 *
34245 * @-x-less ListBox.less
34246 * @class tinymce.ui.ListBox
34247 * @extends tinymce.ui.MenuButton
34248 */
34249define("tinymce/ui/ListBox", [
34250	"tinymce/ui/MenuButton"
34251], function(MenuButton) {
34252	"use strict";
34253
34254	return MenuButton.extend({
34255		/**
34256		 * Constructs a instance with the specified settings.
34257		 *
34258		 * @constructor
34259		 * @param {Object} settings Name/value object with settings.
34260		 * @setting {Array} values Array with values to add to list box.
34261		 */
34262		init: function(settings) {
34263			var self = this, values, selected, selectedText, lastItemCtrl;
34264
34265			function setSelected(menuValues) {
34266				// Try to find a selected value
34267				for (var i = 0; i < menuValues.length; i++) {
34268					selected = menuValues[i].selected || settings.value === menuValues[i].value;
34269
34270					if (selected) {
34271						selectedText = selectedText || menuValues[i].text;
34272						self._value = menuValues[i].value;
34273						break;
34274					}
34275
34276					// If the value has a submenu, try to find the selected values in that menu
34277					if (menuValues[i].menu) {
34278						setSelected(menuValues[i].menu);
34279					}
34280				}
34281			}
34282
34283			self._values = values = settings.values;
34284			if (values) {
34285				if (typeof settings.value != "undefined") {
34286					setSelected(values);
34287				}
34288
34289				// Default with first item
34290				if (!selected && values.length > 0) {
34291					selectedText = values[0].text;
34292					self._value = values[0].value;
34293				}
34294
34295				settings.menu = values;
34296			}
34297
34298			settings.text = settings.text || selectedText || values[0].text;
34299
34300			self._super(settings);
34301			self.addClass('listbox');
34302
34303			self.on('select', function(e) {
34304				var ctrl = e.control;
34305
34306				if (lastItemCtrl) {
34307					e.lastControl = lastItemCtrl;
34308				}
34309
34310				if (settings.multiple) {
34311					ctrl.active(!ctrl.active());
34312				} else {
34313					self.value(e.control.settings.value);
34314				}
34315
34316				lastItemCtrl = ctrl;
34317			});
34318		},
34319
34320		/**
34321		 * Getter/setter function for the control value.
34322		 *
34323		 * @method value
34324		 * @param {String} [value] Value to be set.
34325		 * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
34326		 */
34327		value: function(value) {
34328			var self = this, active, selectedText, menu;
34329
34330			function activateByValue(menu, value) {
34331				menu.items().each(function(ctrl) {
34332					active = ctrl.value() === value;
34333
34334					if (active) {
34335						selectedText = selectedText || ctrl.text();
34336					}
34337
34338					ctrl.active(active);
34339
34340					if (ctrl.menu) {
34341						activateByValue(ctrl.menu, value);
34342					}
34343				});
34344			}
34345
34346			function setActiveValues(menuValues) {
34347				for (var i = 0; i < menuValues.length; i++) {
34348					active = menuValues[i].value == value;
34349
34350					if (active) {
34351						selectedText = selectedText || menuValues[i].text;
34352					}
34353
34354					menuValues[i].active = active;
34355
34356					if (menuValues[i].menu) {
34357						setActiveValues(menuValues[i].menu);
34358					}
34359				}
34360			}
34361
34362			if (typeof(value) != "undefined") {
34363				if (self.menu) {
34364					activateByValue(self.menu, value);
34365				} else {
34366					menu = self.settings.menu;
34367					setActiveValues(menu);
34368				}
34369
34370				self.text(selectedText || this.settings.text);
34371			}
34372
34373			return self._super(value);
34374		}
34375	});
34376});
34377
34378// Included from: js/tinymce/classes/ui/MenuItem.js
34379
34380/**
34381 * MenuItem.js
34382 *
34383 * Copyright, Moxiecode Systems AB
34384 * Released under LGPL License.
34385 *
34386 * License: http://www.tinymce.com/license
34387 * Contributing: http://www.tinymce.com/contributing
34388 */
34389
34390/**
34391 * Creates a new menu item.
34392 *
34393 * @-x-less MenuItem.less
34394 * @class tinymce.ui.MenuItem
34395 * @extends tinymce.ui.Widget
34396 */
34397define("tinymce/ui/MenuItem", [
34398	"tinymce/ui/Widget",
34399	"tinymce/ui/Factory",
34400	"tinymce/Env"
34401], function(Widget, Factory, Env) {
34402	"use strict";
34403
34404	return Widget.extend({
34405		Defaults: {
34406			border: 0,
34407			role: 'menuitem'
34408		},
34409
34410		/**
34411		 * Constructs a instance with the specified settings.
34412		 *
34413		 * @constructor
34414		 * @param {Object} settings Name/value object with settings.
34415		 * @setting {Boolean} selectable Selectable menu.
34416		 * @setting {Array} menu Submenu array with items.
34417		 * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
34418		 */
34419		init: function(settings) {
34420			var self = this;
34421
34422			self.hasPopup = true;
34423
34424			self._super(settings);
34425
34426			settings = self.settings;
34427
34428			self.addClass('menu-item');
34429
34430			if (settings.menu) {
34431				self.addClass('menu-item-expand');
34432			}
34433
34434			if (settings.preview) {
34435				self.addClass('menu-item-preview');
34436			}
34437
34438			if (self._text === '-' || self._text === '|') {
34439				self.addClass('menu-item-sep');
34440				self.aria('role', 'separator');
34441				self._text = '-';
34442			}
34443
34444			if (settings.selectable) {
34445				self.aria('role', 'menuitemcheckbox');
34446				self.addClass('menu-item-checkbox');
34447				settings.icon = 'selected';
34448			}
34449
34450			if (!settings.preview && !settings.selectable) {
34451				self.addClass('menu-item-normal');
34452			}
34453
34454			self.on('mousedown', function(e) {
34455				e.preventDefault();
34456			});
34457
34458			if (settings.menu && !settings.ariaHideMenu) {
34459				self.aria('haspopup', true);
34460			}
34461		},
34462
34463		/**
34464		 * Returns true/false if the menuitem has sub menu.
34465		 *
34466		 * @method hasMenus
34467		 * @return {Boolean} True/false state if it has submenu.
34468		 */
34469		hasMenus: function() {
34470			return !!this.settings.menu;
34471		},
34472
34473		/**
34474		 * Shows the menu for the menu item.
34475		 *
34476		 * @method showMenu
34477		 */
34478		showMenu: function() {
34479			var self = this, settings = self.settings, menu, parent = self.parent();
34480
34481			parent.items().each(function(ctrl) {
34482				if (ctrl !== self) {
34483					ctrl.hideMenu();
34484				}
34485			});
34486
34487			if (settings.menu) {
34488				menu = self.menu;
34489
34490				if (!menu) {
34491					menu = settings.menu;
34492
34493					// Is menu array then auto constuct menu control
34494					if (menu.length) {
34495						menu = {
34496							type: 'menu',
34497							items: menu
34498						};
34499					} else {
34500						menu.type = menu.type || 'menu';
34501					}
34502
34503					if (parent.settings.itemDefaults) {
34504						menu.itemDefaults = parent.settings.itemDefaults;
34505					}
34506
34507					menu = self.menu = Factory.create(menu).parent(self).renderTo();
34508					menu.reflow();
34509					menu.on('cancel', function(e) {
34510						e.stopPropagation();
34511						self.focus();
34512						menu.hide();
34513					});
34514					menu.on('show hide', function(e) {
34515						e.control.items().each(function(ctrl) {
34516							ctrl.active(ctrl.settings.selected);
34517						});
34518					}).fire('show');
34519
34520					menu.on('hide', function(e) {
34521						if (e.control === menu) {
34522							self.removeClass('selected');
34523						}
34524					});
34525
34526					menu.submenu = true;
34527				} else {
34528					menu.show();
34529				}
34530
34531				menu._parentMenu = parent;
34532
34533				menu.addClass('menu-sub');
34534
34535				var rel = menu.testMoveRel(
34536					self.getEl(),
34537					self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
34538				);
34539
34540				menu.moveRel(self.getEl(), rel);
34541				menu.rel = rel;
34542
34543				rel = 'menu-sub-' + rel;
34544				menu.removeClass(menu._lastRel);
34545				menu.addClass(rel);
34546				menu._lastRel = rel;
34547
34548				self.addClass('selected');
34549				self.aria('expanded', true);
34550			}
34551		},
34552
34553		/**
34554		 * Hides the menu for the menu item.
34555		 *
34556		 * @method hideMenu
34557		 */
34558		hideMenu: function() {
34559			var self = this;
34560
34561			if (self.menu) {
34562				self.menu.items().each(function(item) {
34563					if (item.hideMenu) {
34564						item.hideMenu();
34565					}
34566				});
34567
34568				self.menu.hide();
34569				self.aria('expanded', false);
34570			}
34571
34572			return self;
34573		},
34574
34575		/**
34576		 * Renders the control as a HTML string.
34577		 *
34578		 * @method renderHtml
34579		 * @return {String} HTML representing the control.
34580		 */
34581		renderHtml: function() {
34582			var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
34583			var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
34584
34585			if (icon) {
34586				self.parent().addClass('menu-has-icons');
34587			}
34588
34589			if (settings.image) {
34590				icon = 'none';
34591				image = ' style="background-image: url(\'' + settings.image + '\')"';
34592			}
34593
34594			if (shortcut && Env.mac) {
34595				// format shortcut for Mac
34596				shortcut = shortcut.replace(/ctrl\+alt\+/i, '&#x2325;&#x2318;'); // ctrl+cmd
34597				shortcut = shortcut.replace(/ctrl\+/i, '&#x2318;'); // ctrl symbol
34598				shortcut = shortcut.replace(/alt\+/i, '&#x2325;'); // cmd symbol
34599				shortcut = shortcut.replace(/shift\+/i, '&#x21E7;'); // shift symbol
34600			}
34601
34602			icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
34603
34604			return (
34605				'<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
34606					(text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
34607					(text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
34608					(shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
34609					(settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
34610				'</div>'
34611			);
34612		},
34613
34614		/**
34615		 * Gets invoked after the control has been rendered.
34616		 *
34617		 * @method postRender
34618		 */
34619		postRender: function() {
34620			var self = this, settings = self.settings;
34621
34622			var textStyle = settings.textStyle;
34623			if (typeof(textStyle) == "function") {
34624				textStyle = textStyle.call(this);
34625			}
34626
34627			if (textStyle) {
34628				var textElm = self.getEl('text');
34629				if (textElm) {
34630					textElm.setAttribute('style', textStyle);
34631				}
34632			}
34633
34634			self.on('mouseenter click', function(e) {
34635				if (e.control === self) {
34636					if (!settings.menu && e.type === 'click') {
34637						self.fire('select');
34638						self.parent().hideAll();
34639					} else {
34640						self.showMenu();
34641
34642						if (e.aria) {
34643							self.menu.focus(true);
34644						}
34645					}
34646				}
34647			});
34648
34649			self._super();
34650
34651			return self;
34652		},
34653
34654		active: function(state) {
34655			if (typeof(state) != "undefined") {
34656				this.aria('checked', state);
34657			}
34658
34659			return this._super(state);
34660		},
34661
34662		/**
34663		 * Removes the control and it's menus.
34664		 *
34665		 * @method remove
34666		 */
34667		remove: function() {
34668			this._super();
34669
34670			if (this.menu) {
34671				this.menu.remove();
34672			}
34673		}
34674	});
34675});
34676
34677// Included from: js/tinymce/classes/ui/Menu.js
34678
34679/**
34680 * Menu.js
34681 *
34682 * Copyright, Moxiecode Systems AB
34683 * Released under LGPL License.
34684 *
34685 * License: http://www.tinymce.com/license
34686 * Contributing: http://www.tinymce.com/contributing
34687 */
34688
34689/**
34690 * Creates a new menu.
34691 *
34692 * @-x-less Menu.less
34693 * @class tinymce.ui.Menu
34694 * @extends tinymce.ui.FloatPanel
34695 */
34696define("tinymce/ui/Menu", [
34697	"tinymce/ui/FloatPanel",
34698	"tinymce/ui/MenuItem",
34699	"tinymce/util/Tools"
34700], function(FloatPanel, MenuItem, Tools) {
34701	"use strict";
34702
34703	var Menu = FloatPanel.extend({
34704		Defaults: {
34705			defaultType: 'menuitem',
34706			border: 1,
34707			layout: 'stack',
34708			role: 'application',
34709			bodyRole: 'menu',
34710			ariaRoot: true
34711		},
34712
34713		/**
34714		 * Constructs a instance with the specified settings.
34715		 *
34716		 * @constructor
34717		 * @param {Object} settings Name/value object with settings.
34718		 */
34719		init: function(settings) {
34720			var self = this;
34721
34722			settings.autohide = true;
34723			settings.constrainToViewport = true;
34724
34725			if (settings.itemDefaults) {
34726				var items = settings.items, i = items.length;
34727
34728				while (i--) {
34729					items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
34730				}
34731			}
34732
34733			self._super(settings);
34734			self.addClass('menu');
34735		},
34736
34737		/**
34738		 * Repaints the control after a layout operation.
34739		 *
34740		 * @method repaint
34741		 */
34742		repaint: function() {
34743			this.toggleClass('menu-align', true);
34744
34745			this._super();
34746
34747			this.getEl().style.height = '';
34748			this.getEl('body').style.height = '';
34749
34750			return this;
34751		},
34752
34753		/**
34754		 * Hides/closes the menu.
34755		 *
34756		 * @method cancel
34757		 */
34758		cancel: function() {
34759			var self = this;
34760
34761			self.hideAll();
34762			self.fire('select');
34763		},
34764
34765		/**
34766		 * Hide menu and all sub menus.
34767		 *
34768		 * @method hideAll
34769		 */
34770		hideAll: function() {
34771			var self = this;
34772
34773			this.find('menuitem').exec('hideMenu');
34774
34775			return self._super();
34776		},
34777/*
34778		getContainerElm: function() {
34779			var doc = document, id = this.classPrefix + 'menucontainer';
34780
34781			var elm = doc.getElementById(id);
34782			if (!elm) {
34783				elm = doc.createElement('div');
34784				elm.id = id;
34785				elm.setAttribute('role', 'application');
34786				elm.className = this.classPrefix + '-reset';
34787				elm.style.position = 'absolute';
34788				elm.style.top = elm.style.left = '0';
34789				elm.style.overflow = 'visible';
34790				doc.body.appendChild(elm);
34791			}
34792
34793			return elm;
34794		},
34795*/
34796		/**
34797		 * Invoked before the menu is rendered.
34798		 *
34799		 * @method preRender
34800		 */
34801		preRender: function() {
34802			var self = this;
34803
34804			self.items().each(function(ctrl) {
34805				var settings = ctrl.settings;
34806
34807				if (settings.icon || settings.selectable) {
34808					self._hasIcons = true;
34809					return false;
34810				}
34811			});
34812
34813			return self._super();
34814		}
34815	});
34816
34817	return Menu;
34818});
34819
34820// Included from: js/tinymce/classes/ui/Radio.js
34821
34822/**
34823 * Radio.js
34824 *
34825 * Copyright, Moxiecode Systems AB
34826 * Released under LGPL License.
34827 *
34828 * License: http://www.tinymce.com/license
34829 * Contributing: http://www.tinymce.com/contributing
34830 */
34831
34832/**
34833 * Creates a new radio button.
34834 *
34835 * @-x-less Radio.less
34836 * @class tinymce.ui.Radio
34837 * @extends tinymce.ui.Checkbox
34838 */
34839define("tinymce/ui/Radio", [
34840	"tinymce/ui/Checkbox"
34841], function(Checkbox) {
34842	"use strict";
34843
34844	return Checkbox.extend({
34845		Defaults: {
34846			classes: "radio",
34847			role: "radio"
34848		}
34849	});
34850});
34851
34852// Included from: js/tinymce/classes/ui/ResizeHandle.js
34853
34854/**
34855 * ResizeHandle.js
34856 *
34857 * Copyright, Moxiecode Systems AB
34858 * Released under LGPL License.
34859 *
34860 * License: http://www.tinymce.com/license
34861 * Contributing: http://www.tinymce.com/contributing
34862 */
34863
34864/**
34865 * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
34866 *
34867 * @-x-less ResizeHandle.less
34868 * @class tinymce.ui.ResizeHandle
34869 * @extends tinymce.ui.Widget
34870 */
34871define("tinymce/ui/ResizeHandle", [
34872	"tinymce/ui/Widget",
34873	"tinymce/ui/DragHelper"
34874], function(Widget, DragHelper) {
34875	"use strict";
34876
34877	return Widget.extend({
34878		/**
34879		 * Renders the control as a HTML string.
34880		 *
34881		 * @method renderHtml
34882		 * @return {String} HTML representing the control.
34883		 */
34884		renderHtml: function() {
34885			var self = this, prefix = self.classPrefix;
34886
34887			self.addClass('resizehandle');
34888
34889			if (self.settings.direction == "both") {
34890				self.addClass('resizehandle-both');
34891			}
34892
34893			self.canFocus = false;
34894
34895			return (
34896				'<div id="' + self._id + '" class="' + self.classes() + '">' +
34897					'<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
34898				'</div>'
34899			);
34900		},
34901
34902		/**
34903		 * Called after the control has been rendered.
34904		 *
34905		 * @method postRender
34906		 */
34907		postRender: function() {
34908			var self = this;
34909
34910			self._super();
34911
34912			self.resizeDragHelper = new DragHelper(this._id, {
34913				start: function() {
34914					self.fire('ResizeStart');
34915				},
34916
34917				drag: function(e) {
34918					if (self.settings.direction != "both") {
34919						e.deltaX = 0;
34920					}
34921
34922					self.fire('Resize', e);
34923				},
34924
34925				stop: function() {
34926					self.fire('ResizeEnd');
34927				}
34928			});
34929		},
34930
34931		remove: function() {
34932			if (this.resizeDragHelper) {
34933				this.resizeDragHelper.destroy();
34934			}
34935
34936			return this._super();
34937		}
34938	});
34939});
34940
34941// Included from: js/tinymce/classes/ui/Spacer.js
34942
34943/**
34944 * Spacer.js
34945 *
34946 * Copyright, Moxiecode Systems AB
34947 * Released under LGPL License.
34948 *
34949 * License: http://www.tinymce.com/license
34950 * Contributing: http://www.tinymce.com/contributing
34951 */
34952
34953/**
34954 * Creates a spacer. This control is used in flex layouts for example.
34955 *
34956 * @-x-less Spacer.less
34957 * @class tinymce.ui.Spacer
34958 * @extends tinymce.ui.Widget
34959 */
34960define("tinymce/ui/Spacer", [
34961	"tinymce/ui/Widget"
34962], function(Widget) {
34963	"use strict";
34964
34965	return Widget.extend({
34966		/**
34967		 * Renders the control as a HTML string.
34968		 *
34969		 * @method renderHtml
34970		 * @return {String} HTML representing the control.
34971		 */
34972		renderHtml: function() {
34973			var self = this;
34974
34975			self.addClass('spacer');
34976			self.canFocus = false;
34977
34978			return '<div id="' + self._id + '" class="' + self.classes() + '"></div>';
34979		}
34980	});
34981});
34982
34983// Included from: js/tinymce/classes/ui/SplitButton.js
34984
34985/**
34986 * SplitButton.js
34987 *
34988 * Copyright, Moxiecode Systems AB
34989 * Released under LGPL License.
34990 *
34991 * License: http://www.tinymce.com/license
34992 * Contributing: http://www.tinymce.com/contributing
34993 */
34994
34995/**
34996 * Creates a split button.
34997 *
34998 * @-x-less SplitButton.less
34999 * @class tinymce.ui.SplitButton
35000 * @extends tinymce.ui.MenuButton
35001 */
35002define("tinymce/ui/SplitButton", [
35003	"tinymce/ui/MenuButton",
35004	"tinymce/ui/DomUtils"
35005], function(MenuButton, DomUtils) {
35006	return MenuButton.extend({
35007		Defaults: {
35008			classes: "widget btn splitbtn",
35009			role: "button"
35010		},
35011
35012		/**
35013		 * Repaints the control after a layout operation.
35014		 *
35015		 * @method repaint
35016		 */
35017		repaint: function() {
35018			var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;
35019
35020			self._super();
35021
35022			mainButtonElm = elm.firstChild;
35023			menuButtonElm = elm.lastChild;
35024
35025			DomUtils.css(mainButtonElm, {
35026				width: rect.w - DomUtils.getSize(menuButtonElm).width,
35027				height: rect.h - 2
35028			});
35029
35030			DomUtils.css(menuButtonElm, {
35031				height: rect.h - 2
35032			});
35033
35034			return self;
35035		},
35036
35037		/**
35038		 * Sets the active menu state.
35039		 *
35040		 * @private
35041		 */
35042		activeMenu: function(state) {
35043			var self = this;
35044
35045			DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state);
35046		},
35047
35048		/**
35049		 * Renders the control as a HTML string.
35050		 *
35051		 * @method renderHtml
35052		 * @return {String} HTML representing the control.
35053		 */
35054		renderHtml: function() {
35055			var self = this, id = self._id, prefix = self.classPrefix, image;
35056			var icon = self.settings.icon;
35057
35058			image = self.settings.image;
35059			if (image) {
35060				icon = 'none';
35061
35062				// Support for [high dpi, low dpi] image sources
35063				if (typeof image != "string") {
35064					image = window.getSelection ? image[0] : image[1];
35065				}
35066
35067				image = ' style="background-image: url(\'' + image + '\')"';
35068			} else {
35069				image = '';
35070			}
35071
35072			icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
35073
35074			return (
35075				'<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
35076					'<button type="button" hidefocus="1" tabindex="-1">' +
35077						(icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
35078						(self._text ? (icon ? ' ' : '') + self._text : '') +
35079					'</button>' +
35080					'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
35081						//(icon ? '<i class="' + icon + '"></i>' : '') +
35082						(self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
35083						' <i class="' + prefix + 'caret"></i>' +
35084					'</button>' +
35085				'</div>'
35086			);
35087		},
35088
35089		/**
35090		 * Called after the control has been rendered.
35091		 *
35092		 * @method postRender
35093		 */
35094		postRender: function() {
35095			var self = this, onClickHandler = self.settings.onclick;
35096
35097			self.on('click', function(e) {
35098				var node = e.target;
35099
35100				if (e.control == this) {
35101					// Find clicks that is on the main button
35102					while (node) {
35103						if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
35104							e.stopImmediatePropagation();
35105							onClickHandler.call(this, e);
35106							return;
35107						}
35108
35109						node = node.parentNode;
35110					}
35111				}
35112			});
35113
35114			delete self.settings.onclick;
35115
35116			return self._super();
35117		}
35118	});
35119});
35120
35121// Included from: js/tinymce/classes/ui/StackLayout.js
35122
35123/**
35124 * StackLayout.js
35125 *
35126 * Copyright, Moxiecode Systems AB
35127 * Released under LGPL License.
35128 *
35129 * License: http://www.tinymce.com/license
35130 * Contributing: http://www.tinymce.com/contributing
35131 */
35132
35133/**
35134 * This layout uses the browsers layout when the items are blocks.
35135 *
35136 * @-x-less StackLayout.less
35137 * @class tinymce.ui.StackLayout
35138 * @extends tinymce.ui.FlowLayout
35139 */
35140define("tinymce/ui/StackLayout", [
35141	"tinymce/ui/FlowLayout"
35142], function(FlowLayout) {
35143	"use strict";
35144
35145	return FlowLayout.extend({
35146		Defaults: {
35147			containerClass: 'stack-layout',
35148			controlClass: 'stack-layout-item',
35149			endClass : 'break'
35150		}
35151	});
35152});
35153
35154// Included from: js/tinymce/classes/ui/TabPanel.js
35155
35156/**
35157 * TabPanel.js
35158 *
35159 * Copyright, Moxiecode Systems AB
35160 * Released under LGPL License.
35161 *
35162 * License: http://www.tinymce.com/license
35163 * Contributing: http://www.tinymce.com/contributing
35164 */
35165
35166/**
35167 * Creates a tab panel control.
35168 *
35169 * @-x-less TabPanel.less
35170 * @class tinymce.ui.TabPanel
35171 * @extends tinymce.ui.Panel
35172 *
35173 * @setting {Number} activeTab Active tab index.
35174 */
35175define("tinymce/ui/TabPanel", [
35176	"tinymce/ui/Panel",
35177	"tinymce/ui/DomUtils"
35178], function(Panel, DomUtils) {
35179	"use strict";
35180
35181	return Panel.extend({
35182		Defaults: {
35183			layout: 'absolute',
35184			defaults: {
35185				type: 'panel'
35186			}
35187		},
35188
35189		/**
35190		 * Activates the specified tab by index.
35191		 *
35192		 * @method activateTab
35193		 * @param {Number} idx Index of the tab to activate.
35194		 */
35195		activateTab: function(idx) {
35196			var activeTabElm;
35197
35198			if (this.activeTabId) {
35199				activeTabElm = this.getEl(this.activeTabId);
35200				DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
35201				activeTabElm.setAttribute('aria-selected', "false");
35202			}
35203
35204			this.activeTabId = 't' + idx;
35205
35206			activeTabElm = this.getEl('t' + idx);
35207			activeTabElm.setAttribute('aria-selected', "true");
35208			DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
35209
35210			this.items()[idx].show().fire('showtab');
35211			this.reflow();
35212
35213			this.items().each(function(item, i) {
35214				if (idx != i) {
35215					item.hide();
35216				}
35217			});
35218		},
35219
35220		/**
35221		 * Renders the control as a HTML string.
35222		 *
35223		 * @method renderHtml
35224		 * @return {String} HTML representing the control.
35225		 */
35226		renderHtml: function() {
35227			var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
35228
35229			self.preRender();
35230			layout.preRender(self);
35231
35232			self.items().each(function(ctrl, i) {
35233				var id = self._id + '-t' + i;
35234
35235				ctrl.aria('role', 'tabpanel');
35236				ctrl.aria('labelledby', id);
35237
35238				tabsHtml += (
35239					'<div id="' + id + '" class="' + prefix + 'tab" ' +
35240						'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
35241						self.encode(ctrl.settings.title) +
35242					'</div>'
35243				);
35244			});
35245
35246			return (
35247				'<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
35248					'<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
35249						tabsHtml +
35250					'</div>' +
35251					'<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
35252						layout.renderHtml(self) +
35253					'</div>' +
35254				'</div>'
35255			);
35256		},
35257
35258		/**
35259		 * Called after the control has been rendered.
35260		 *
35261		 * @method postRender
35262		 */
35263		postRender: function() {
35264			var self = this;
35265
35266			self._super();
35267
35268			self.settings.activeTab = self.settings.activeTab || 0;
35269			self.activateTab(self.settings.activeTab);
35270
35271			this.on('click', function(e) {
35272				var targetParent = e.target.parentNode;
35273
35274				if (e.target.parentNode.id == self._id + '-head') {
35275					var i = targetParent.childNodes.length;
35276
35277					while (i--) {
35278						if (targetParent.childNodes[i] == e.target) {
35279							self.activateTab(i);
35280						}
35281					}
35282				}
35283			});
35284		},
35285
35286		/**
35287		 * Initializes the current controls layout rect.
35288		 * This will be executed by the layout managers to determine the
35289		 * default minWidth/minHeight etc.
35290		 *
35291		 * @method initLayoutRect
35292		 * @return {Object} Layout rect instance.
35293		 */
35294		initLayoutRect: function() {
35295			var self = this, rect, minW, minH;
35296
35297			minW = DomUtils.getSize(self.getEl('head')).width;
35298			minW = minW < 0 ? 0 : minW;
35299			minH = 0;
35300
35301			self.items().each(function(item) {
35302				minW = Math.max(minW, item.layoutRect().minW);
35303				minH = Math.max(minH, item.layoutRect().minH);
35304			});
35305
35306			self.items().each(function(ctrl) {
35307				ctrl.settings.x = 0;
35308				ctrl.settings.y = 0;
35309				ctrl.settings.w = minW;
35310				ctrl.settings.h = minH;
35311
35312				ctrl.layoutRect({
35313					x: 0,
35314					y: 0,
35315					w: minW,
35316					h: minH
35317				});
35318			});
35319
35320			var headH = DomUtils.getSize(self.getEl('head')).height;
35321
35322			self.settings.minWidth = minW;
35323			self.settings.minHeight = minH + headH;
35324
35325			rect = self._super();
35326			rect.deltaH += headH;
35327			rect.innerH = rect.h - rect.deltaH;
35328
35329			return rect;
35330		}
35331	});
35332});
35333
35334// Included from: js/tinymce/classes/ui/TextBox.js
35335
35336/**
35337 * TextBox.js
35338 *
35339 * Copyright, Moxiecode Systems AB
35340 * Released under LGPL License.
35341 *
35342 * License: http://www.tinymce.com/license
35343 * Contributing: http://www.tinymce.com/contributing
35344 */
35345
35346/**
35347 * Creates a new textbox.
35348 *
35349 * @-x-less TextBox.less
35350 * @class tinymce.ui.TextBox
35351 * @extends tinymce.ui.Widget
35352 */
35353define("tinymce/ui/TextBox", [
35354	"tinymce/ui/Widget",
35355	"tinymce/ui/DomUtils"
35356], function(Widget, DomUtils) {
35357	"use strict";
35358
35359	return Widget.extend({
35360		/**
35361		 * Constructs a instance with the specified settings.
35362		 *
35363		 * @constructor
35364		 * @param {Object} settings Name/value object with settings.
35365		 * @setting {Boolean} multiline True if the textbox is a multiline control.
35366		 * @setting {Number} maxLength Max length for the textbox.
35367		 * @setting {Number} size Size of the textbox in characters.
35368		 */
35369		init: function(settings) {
35370			var self = this;
35371
35372			self._super(settings);
35373
35374			self._value = settings.value || '';
35375			self.addClass('textbox');
35376
35377			if (settings.multiline) {
35378				self.addClass('multiline');
35379			} else {
35380				// TODO: Rework this
35381				self.on('keydown', function(e) {
35382					if (e.keyCode == 13) {
35383						self.parents().reverse().each(function(ctrl) {
35384							e.preventDefault();
35385
35386							if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
35387								ctrl.fire('submit', {data: ctrl.toJSON()});
35388								return false;
35389							}
35390						});
35391					}
35392				});
35393			}
35394		},
35395
35396		/**
35397		 * Getter/setter function for the disabled state.
35398		 *
35399		 * @method value
35400		 * @param {Boolean} [state] State to be set.
35401		 * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
35402		 */
35403		disabled: function(state) {
35404			var self = this;
35405
35406			if (self._rendered && typeof(state) != 'undefined') {
35407				self.getEl().disabled = state;
35408			}
35409
35410			return self._super(state);
35411		},
35412
35413		/**
35414		 * Getter/setter function for the control value.
35415		 *
35416		 * @method value
35417		 * @param {String} [value] Value to be set.
35418		 * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
35419		 */
35420		value: function(value) {
35421			var self = this;
35422
35423			if (typeof(value) != "undefined") {
35424				self._value = value;
35425
35426				if (self._rendered) {
35427					self.getEl().value = value;
35428				}
35429
35430				return self;
35431			}
35432
35433			if (self._rendered) {
35434				return self.getEl().value;
35435			}
35436
35437			return self._value;
35438		},
35439
35440		/**
35441		 * Repaints the control after a layout operation.
35442		 *
35443		 * @method repaint
35444		 */
35445		repaint: function() {
35446			var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
35447
35448			style = self.getEl().style;
35449			rect = self._layoutRect;
35450			lastRepaintRect = self._lastRepaintRect || {};
35451
35452			// Detect old IE 7+8 add lineHeight to align caret vertically in the middle
35453			var doc = document;
35454			if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
35455				style.lineHeight = (rect.h - borderH) + 'px';
35456			}
35457
35458			borderBox = self._borderBox;
35459			borderW = borderBox.left + borderBox.right + 8;
35460			borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
35461
35462			if (rect.x !== lastRepaintRect.x) {
35463				style.left = rect.x + 'px';
35464				lastRepaintRect.x = rect.x;
35465			}
35466
35467			if (rect.y !== lastRepaintRect.y) {
35468				style.top = rect.y + 'px';
35469				lastRepaintRect.y = rect.y;
35470			}
35471
35472			if (rect.w !== lastRepaintRect.w) {
35473				style.width = (rect.w - borderW) + 'px';
35474				lastRepaintRect.w = rect.w;
35475			}
35476
35477			if (rect.h !== lastRepaintRect.h) {
35478				style.height = (rect.h - borderH) + 'px';
35479				lastRepaintRect.h = rect.h;
35480			}
35481
35482			self._lastRepaintRect = lastRepaintRect;
35483			self.fire('repaint', {}, false);
35484
35485			return self;
35486		},
35487
35488		/**
35489		 * Renders the control as a HTML string.
35490		 *
35491		 * @method renderHtml
35492		 * @return {String} HTML representing the control.
35493		 */
35494		renderHtml: function() {
35495			var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = '';
35496
35497			if ("spellcheck" in settings) {
35498				extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
35499			}
35500
35501			if (settings.maxLength) {
35502				extraAttrs += ' maxlength="' + settings.maxLength + '"';
35503			}
35504
35505			if (settings.size) {
35506				extraAttrs += ' size="' + settings.size + '"';
35507			}
35508
35509			if (settings.subtype) {
35510				extraAttrs += ' type="' + settings.subtype + '"';
35511			}
35512
35513			if (self.disabled()) {
35514				extraAttrs += ' disabled="disabled"';
35515			}
35516
35517			if (settings.multiline) {
35518				return (
35519					'<textarea id="' + id + '" class="' + self.classes() + '" ' +
35520					(settings.rows ? ' rows="' + settings.rows + '"' : '') +
35521					' hidefocus="1"' + extraAttrs + '>' + value +
35522					'</textarea>'
35523				);
35524			}
35525
35526			return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
35527		},
35528
35529		/**
35530		 * Called after the control has been rendered.
35531		 *
35532		 * @method postRender
35533		 */
35534		postRender: function() {
35535			var self = this;
35536
35537			DomUtils.on(self.getEl(), 'change', function(e) {
35538				self.fire('change', e);
35539			});
35540
35541			return self._super();
35542		},
35543
35544		remove: function() {
35545			DomUtils.off(this.getEl());
35546			this._super();
35547		}
35548	});
35549});
35550
35551// Included from: js/tinymce/classes/ui/Throbber.js
35552
35553/**
35554 * Throbber.js
35555 *
35556 * Copyright, Moxiecode Systems AB
35557 * Released under LGPL License.
35558 *
35559 * License: http://www.tinymce.com/license
35560 * Contributing: http://www.tinymce.com/contributing
35561 */
35562
35563/**
35564 * This class enables you to display a Throbber for any element.
35565 *
35566 * @-x-less Throbber.less
35567 * @class tinymce.ui.Throbber
35568 */
35569define("tinymce/ui/Throbber", [
35570	"tinymce/ui/DomUtils",
35571	"tinymce/ui/Control"
35572], function(DomUtils, Control) {
35573	"use strict";
35574
35575	/**
35576	 * Constructs a new throbber.
35577	 *
35578	 * @constructor
35579	 * @param {Element} elm DOM Html element to display throbber in.
35580	 * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
35581	 */
35582	return function(elm, inline) {
35583		var self = this, state, classPrefix = Control.classPrefix;
35584
35585		/**
35586		 * Shows the throbber.
35587		 *
35588		 * @method show
35589		 * @param {Number} [time] Time to wait before showing.
35590		 * @return {tinymce.ui.Throbber} Current throbber instance.
35591		 */
35592		self.show = function(time) {
35593			self.hide();
35594
35595			state = true;
35596
35597			window.setTimeout(function() {
35598				if (state) {
35599					elm.appendChild(DomUtils.createFragment(
35600						'<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
35601					));
35602				}
35603			}, time || 0);
35604
35605			return self;
35606		};
35607
35608		/**
35609		 * Hides the throbber.
35610		 *
35611		 * @method hide
35612		 * @return {tinymce.ui.Throbber} Current throbber instance.
35613		 */
35614		self.hide = function() {
35615			var child = elm.lastChild;
35616
35617			if (child && child.className.indexOf('throbber') != -1) {
35618				child.parentNode.removeChild(child);
35619			}
35620
35621			state = false;
35622
35623			return self;
35624		};
35625	};
35626});
35627
35628expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/Env","tinymce/util/Tools","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/dom/Range","tinymce/html/Entities","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/dom/ElementUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/ComboBox","tinymce/ui/ColorBox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/util/Color","tinymce/ui/ColorPicker","tinymce/ui
35628/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
35629})(this);

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.