]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/debian/missing-sources/framework/Web/Javascripts/source/tinymce-405/jquery.tinymce.js
baculum: Add missing-sources directory in debian metadata structure
[bacula/bacula] / gui / baculum / debian / missing-sources / framework / Web / Javascripts / source / tinymce-405 / jquery.tinymce.js
1 // 4.0.5 (2013-08-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/Sizzle.jQuery.js
86
87 /**
88  * Sizzle.jQuery.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 /*global jQuery:true */
98
99 /*
100  * Fake Sizzle using jQuery.
101  */
102 define("tinymce/dom/Sizzle", [], function() {
103         // Detect if jQuery is loaded
104         if (!window.jQuery) {
105                 throw new Error("Load jQuery first");
106         }
107
108         var $ = jQuery;
109
110         function Sizzle(selector, context, results, seed) {
111                 return $.find(selector, context, results, seed);
112         }
113
114         Sizzle.matches = function(expr, elements) {
115                 return $(elements).is(expr) ? elements : [];
116         };
117
118         return Sizzle;
119 });
120
121 // Included from: js/tinymce/classes/html/Styles.js
122
123 /**
124  * Styles.js
125  *
126  * Copyright, Moxiecode Systems AB
127  * Released under LGPL License.
128  *
129  * License: http://www.tinymce.com/license
130  * Contributing: http://www.tinymce.com/contributing
131  */
132
133 /**
134  * This class is used to parse CSS styles it also compresses styles to reduce the output size.
135  *
136  * @example
137  * var Styles = new tinymce.html.Styles({
138  *    url_converter: function(url) {
139  *       return url;
140  *    }
141  * });
142  *
143  * styles = Styles.parse('border: 1px solid red');
144  * styles.color = 'red';
145  *
146  * console.log(new tinymce.html.StyleSerializer().serialize(styles));
147  *
148  * @class tinymce.html.Styles
149  * @version 3.4
150  */
151 define("tinymce/html/Styles", [], function() {
152         return function(settings, schema) {
153                 /*jshint maxlen:255 */
154                 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
155                         urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
156                         styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
157                         trimRightRegExp = /\s+$/,
158                         undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF';
159
160                 settings = settings || {};
161
162                 encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
163                 for (i = 0; i < encodingItems.length; i++) {
164                         encodingLookup[encodingItems[i]] = invisibleChar + i;
165                         encodingLookup[invisibleChar + i] = encodingItems[i];
166                 }
167
168                 function toHex(match, r, g, b) {
169                         function hex(val) {
170                                 val = parseInt(val, 10).toString(16);
171
172                                 return val.length > 1 ? val : '0' + val; // 0 -> 00
173                         }
174
175                         return '#' + hex(r) + hex(g) + hex(b);
176                 }
177
178                 return {
179                         /**
180                          * Parses the specified RGB color value and returns a hex version of that color.
181                          *
182                          * @method toHex
183                          * @param {String} color RGB string value like rgb(1,2,3)
184                          * @return {String} Hex version of that RGB value like #FF00FF.
185                          */
186                         toHex: function(color) {
187                                 return color.replace(rgbRegExp, toHex);
188                         },
189
190                         /**
191                          * Parses the specified style value into an object collection. This parser will also
192                          * merge and remove any redundant items that browsers might have added. It will also convert non hex
193                          * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
194                          *
195                          * @method parse
196                          * @param {String} css Style value to parse for example: border:1px solid red;.
197                          * @return {Object} Object representation of that style like {border: '1px solid red'}
198                          */
199                         parse: function(css) {
200                                 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
201
202                                 function compress(prefix, suffix) {
203                                         var top, right, bottom, left;
204
205                                         // Get values and check it it needs compressing
206                                         top = styles[prefix + '-top' + suffix];
207                                         if (!top) {
208                                                 return;
209                                         }
210
211                                         right = styles[prefix + '-right' + suffix];
212                                         if (top != right) {
213                                                 return;
214                                         }
215
216                                         bottom = styles[prefix + '-bottom' + suffix];
217                                         if (right != bottom) {
218                                                 return;
219                                         }
220
221                                         left = styles[prefix + '-left' + suffix];
222                                         if (bottom != left) {
223                                                 return;
224                                         }
225
226                                         // Compress
227                                         styles[prefix + suffix] = left;
228                                         delete styles[prefix + '-top' + suffix];
229                                         delete styles[prefix + '-right' + suffix];
230                                         delete styles[prefix + '-bottom' + suffix];
231                                         delete styles[prefix + '-left' + suffix];
232                                 }
233
234                                 /**
235                                  * Checks if the specific style can be compressed in other words if all border-width are equal.
236                                  */
237                                 function canCompress(key) {
238                                         var value = styles[key], i;
239
240                                         if (!value || value.indexOf(' ') < 0) {
241                                                 return;
242                                         }
243
244                                         value = value.split(' ');
245                                         i = value.length;
246                                         while (i--) {
247                                                 if (value[i] !== value[0]) {
248                                                         return false;
249                                                 }
250                                         }
251
252                                         styles[key] = value[0];
253
254                                         return true;
255                                 }
256
257                                 /**
258                                  * Compresses multiple styles into one style.
259                                  */
260                                 function compress2(target, a, b, c) {
261                                         if (!canCompress(a)) {
262                                                 return;
263                                         }
264
265                                         if (!canCompress(b)) {
266                                                 return;
267                                         }
268
269                                         if (!canCompress(c)) {
270                                                 return;
271                                         }
272
273                                         // Compress
274                                         styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
275                                         delete styles[a];
276                                         delete styles[b];
277                                         delete styles[c];
278                                 }
279
280                                 // Encodes the specified string by replacing all \" \' ; : with _<num>
281                                 function encode(str) {
282                                         isEncoded = true;
283
284                                         return encodingLookup[str];
285                                 }
286
287                                 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
288                                 // It will also decode the \" \' if keep_slashes is set to fale or omitted
289                                 function decode(str, keep_slashes) {
290                                         if (isEncoded) {
291                                                 str = str.replace(/\uFEFF[0-9]/g, function(str) {
292                                                         return encodingLookup[str];
293                                                 });
294                                         }
295
296                                         if (!keep_slashes) {
297                                                 str = str.replace(/\\([\'\";:])/g, "$1");
298                                         }
299
300                                         return str;
301                                 }
302
303                                 function processUrl(match, url, url2, url3, str, str2) {
304                                         str = str || str2;
305
306                                         if (str) {
307                                                 str = decode(str);
308
309                                                 // Force strings into single quote format
310                                                 return "'" + str.replace(/\'/g, "\\'") + "'";
311                                         }
312
313                                         url = decode(url || url2 || url3);
314
315                                         // Convert the URL to relative/absolute depending on config
316                                         if (urlConverter) {
317                                                 url = urlConverter.call(urlConverterScope, url, 'style');
318                                         }
319
320                                         // Output new URL format
321                                         return "url('" + url.replace(/\'/g, "\\'") + "')";
322                                 }
323
324                                 if (css) {
325                                         // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
326                                         css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
327                                                 return str.replace(/[;:]/g, encode);
328                                         });
329
330                                         // Parse styles
331                                         while ((matches = styleRegExp.exec(css))) {
332                                                 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
333                                                 value = matches[2].replace(trimRightRegExp, '');
334
335                                                 if (name && value.length > 0) {
336                                                         // Opera will produce 700 instead of bold in their style values
337                                                         if (name === 'font-weight' && value === '700') {
338                                                                 value = 'bold';
339                                                         } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
340                                                                 value = value.toLowerCase();
341                                                         }
342
343                                                         // Convert RGB colors to HEX
344                                                         value = value.replace(rgbRegExp, toHex);
345
346                                                         // Convert URLs and force them into url('value') format
347                                                         value = value.replace(urlOrStrRegExp, processUrl);
348                                                         styles[name] = isEncoded ? decode(value, true) : value;
349                                                 }
350
351                                                 styleRegExp.lastIndex = matches.index + matches[0].length;
352                                         }
353
354                                         // Compress the styles to reduce it's size for example IE will expand styles
355                                         compress("border", "");
356                                         compress("border", "-width");
357                                         compress("border", "-color");
358                                         compress("border", "-style");
359                                         compress("padding", "");
360                                         compress("margin", "");
361                                         compress2('border', 'border-width', 'border-style', 'border-color');
362
363                                         // Remove pointless border, IE produces these
364                                         if (styles.border === 'medium none') {
365                                                 delete styles.border;
366                                         }
367                                 }
368
369                                 return styles;
370                         },
371
372                         /**
373                          * Serializes the specified style object into a string.
374                          *
375                          * @method serialize
376                          * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
377                          * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
378                          * @return {String} String representation of the style object for example: border: 1px solid red.
379                          */
380                         serialize: function(styles, element_name) {
381                                 var css = '', name, value;
382
383                                 function serializeStyles(name) {
384                                         var styleList, i, l, value;
385
386                                         styleList = schema.styles[name];
387                                         if (styleList) {
388                                                 for (i = 0, l = styleList.length; i < l; i++) {
389                                                         name = styleList[i];
390                                                         value = styles[name];
391
392                                                         if (value !== undef && value.length > 0) {
393                                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
394                                                         }
395                                                 }
396                                         }
397                                 }
398
399                                 // Serialize styles according to schema
400                                 if (element_name && schema && schema.styles) {
401                                         // Serialize global styles and element specific styles
402                                         serializeStyles('*');
403                                         serializeStyles(element_name);
404                                 } else {
405                                         // Output the styles in the order they are inside the object
406                                         for (name in styles) {
407                                                 value = styles[name];
408
409                                                 if (value !== undef && value.length > 0) {
410                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
411                                                 }
412                                         }
413                                 }
414
415                                 return css;
416                         }
417                 };
418         };
419 });
420
421 // Included from: js/tinymce/classes/dom/EventUtils.js
422
423 /**
424  * EventUtils.js
425  *
426  * Copyright, Moxiecode Systems AB
427  * Released under LGPL License.
428  *
429  * License: http://www.tinymce.com/license
430  * Contributing: http://www.tinymce.com/contributing
431  */
432
433 /*jshint loopfunc:true*/
434
435 define("tinymce/dom/EventUtils", [], function() {
436         "use strict";
437
438         var eventExpandoPrefix = "mce-data-";
439         var mouseEventRe = /^(?:mouse|contextmenu)|click/;
440         var deprecated = {keyLocation: 1, layerX: 1, layerY: 1};
441
442         /**
443          * Binds a native event to a callback on the speified target.
444          */
445         function addEvent(target, name, callback, capture) {
446                 if (target.addEventListener) {
447                         target.addEventListener(name, callback, capture || false);
448                 } else if (target.attachEvent) {
449                         target.attachEvent('on' + name, callback);
450                 }
451         }
452
453         /**
454          * Unbinds a native event callback on the specified target.
455          */
456         function removeEvent(target, name, callback, capture) {
457                 if (target.removeEventListener) {
458                         target.removeEventListener(name, callback, capture || false);
459                 } else if (target.detachEvent) {
460                         target.detachEvent('on' + name, callback);
461                 }
462         }
463
464         /**
465          * Normalizes a native event object or just adds the event specific methods on a custom event.
466          */
467         function fix(originalEvent, data) {
468                 var name, event = data || {}, undef;
469
470                 // Dummy function that gets replaced on the delegation state functions
471                 function returnFalse() {
472                         return false;
473                 }
474
475                 // Dummy function that gets replaced on the delegation state functions
476                 function returnTrue() {
477                         return true;
478                 }
479
480                 // Copy all properties from the original event
481                 for (name in originalEvent) {
482                         // layerX/layerY is deprecated in Chrome and produces a warning
483                         if (!deprecated[name]) {
484                                 event[name] = originalEvent[name];
485                         }
486                 }
487
488                 // Normalize target IE uses srcElement
489                 if (!event.target) {
490                         event.target = event.srcElement || document;
491                 }
492
493                 // Calculate pageX/Y if missing and clientX/Y available
494                 if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
495                         var eventDoc = event.target.ownerDocument || document;
496                         var doc = eventDoc.documentElement;
497                         var body = eventDoc.body;
498
499                         event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
500                                 ( doc && doc.clientLeft || body && body.clientLeft || 0);
501
502                         event.pageY = originalEvent.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
503                                 ( doc && doc.clientTop  || body && body.clientTop  || 0);
504                 }
505
506                 // Add preventDefault method
507                 event.preventDefault = function() {
508                         event.isDefaultPrevented = returnTrue;
509
510                         // Execute preventDefault on the original event object
511                         if (originalEvent) {
512                                 if (originalEvent.preventDefault) {
513                                         originalEvent.preventDefault();
514                                 } else {
515                                         originalEvent.returnValue = false; // IE
516                                 }
517                         }
518                 };
519
520                 // Add stopPropagation
521                 event.stopPropagation = function() {
522                         event.isPropagationStopped = returnTrue;
523
524                         // Execute stopPropagation on the original event object
525                         if (originalEvent) {
526                                 if (originalEvent.stopPropagation) {
527                                         originalEvent.stopPropagation();
528                                 } else {
529                                         originalEvent.cancelBubble = true; // IE
530                                 }
531                          }
532                 };
533
534                 // Add stopImmediatePropagation
535                 event.stopImmediatePropagation = function() {
536                         event.isImmediatePropagationStopped = returnTrue;
537                         event.stopPropagation();
538                 };
539
540                 // Add event delegation states
541                 if (!event.isDefaultPrevented) {
542                         event.isDefaultPrevented = returnFalse;
543                         event.isPropagationStopped = returnFalse;
544                         event.isImmediatePropagationStopped = returnFalse;
545                 }
546
547                 return event;
548         }
549
550         /**
551          * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
552          * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
553          */
554         function bindOnReady(win, callback, eventUtils) {
555                 var doc = win.document, event = {type: 'ready'};
556
557                 if (eventUtils.domLoaded) {
558                         callback(event);
559                         return;
560                 }
561
562                 // Gets called when the DOM is ready
563                 function readyHandler() {
564                         if (!eventUtils.domLoaded) {
565                                 eventUtils.domLoaded = true;
566                                 callback(event);
567                         }
568                 }
569
570                 function waitForDomLoaded() {
571                         if (doc.readyState === "complete") {
572                                 removeEvent(doc, "readystatechange", waitForDomLoaded);
573                                 readyHandler();
574                         }
575                 }
576
577                 function tryScroll() {
578                         try {
579                                 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
580                                 // http://javascript.nwbox.com/IEContentLoaded/
581                                 doc.documentElement.doScroll("left");
582                         } catch (ex) {
583                                 setTimeout(tryScroll, 0);
584                                 return;
585                         }
586
587                         readyHandler();
588                 }
589
590                 // Use W3C method
591                 if (doc.addEventListener) {
592                         addEvent(win, 'DOMContentLoaded', readyHandler);
593                 } else {
594                         // Use IE method
595                         addEvent(doc, "readystatechange", waitForDomLoaded);
596
597                         // Wait until we can scroll, when we can the DOM is initialized
598                         if (doc.documentElement.doScroll && win === win.top) {
599                                 tryScroll();
600                         }
601                 }
602
603                 // Fallback if any of the above methods should fail for some odd reason
604                 addEvent(win, 'load', readyHandler);
605         }
606
607         /**
608          * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
609          */
610         function EventUtils() {
611                 var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
612
613                 expando = eventExpandoPrefix + (+new Date()).toString(32);
614                 hasMouseEnterLeave = "onmouseenter" in document.documentElement;
615                 hasFocusIn = "onfocusin" in document.documentElement;
616                 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
617                 count = 1;
618
619                 // State if the DOMContentLoaded was executed or not
620                 self.domLoaded = false;
621                 self.events = events;
622
623                 /**
624                  * Executes all event handler callbacks for a specific event.
625                  *
626                  * @private
627                  * @param {Event} evt Event object.
628                  * @param {String} id Expando id value to look for.
629                  */
630                 function executeHandlers(evt, id) {
631                         var callbackList, i, l, callback;
632
633                         callbackList = events[id][evt.type];
634                         if (callbackList) {
635                                 for (i = 0, l = callbackList.length; i < l; i++) {
636                                         callback = callbackList[i];
637
638                                         // Check if callback exists might be removed if a unbind is called inside the callback
639                                         if (callback && callback.func.call(callback.scope, evt) === false) {
640                                                 evt.preventDefault();
641                                         }
642
643                                         // Should we stop propagation to immediate listeners
644                                         if (evt.isImmediatePropagationStopped()) {
645                                                 return;
646                                         }
647                                 }
648                         }
649                 }
650
651                 /**
652                  * Binds a callback to an event on the specified target.
653                  *
654                  * @method bind
655                  * @param {Object} target Target node/window or custom object.
656                  * @param {String} names Name of the event to bind.
657                  * @param {function} callback Callback function to execute when the event occurs.
658                  * @param {Object} scope Scope to call the callback function on, defaults to target.
659                  * @return {function} Callback function that got bound.
660                  */
661                 self.bind = function(target, names, callback, scope) {
662                         var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
663
664                         // Native event handler function patches the event and executes the callbacks for the expando
665                         function defaultNativeHandler(evt) {
666                                 executeHandlers(fix(evt || win.event), id);
667                         }
668
669                         // Don't bind to text nodes or comments
670                         if (!target || target.nodeType === 3 || target.nodeType === 8) {
671                                 return;
672                         }
673
674                         // Create or get events id for the target
675                         if (!target[expando]) {
676                                 id = count++;
677                                 target[expando] = id;
678                                 events[id] = {};
679                         } else {
680                                 id = target[expando];
681                         }
682
683                         // Setup the specified scope or use the target as a default
684                         scope = scope || target;
685
686                         // Split names and bind each event, enables you to bind multiple events with one call
687                         names = names.split(' ');
688                         i = names.length;
689                         while (i--) {
690                                 name = names[i];
691                                 nativeHandler = defaultNativeHandler;
692                                 fakeName = capture = false;
693
694                                 // Use ready instead of DOMContentLoaded
695                                 if (name === "DOMContentLoaded") {
696                                         name = "ready";
697                                 }
698
699                                 // DOM is already ready
700                                 if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
701                                         callback.call(scope, fix({type: name}));
702                                         continue;
703                                 }
704
705                                 // Handle mouseenter/mouseleaver
706                                 if (!hasMouseEnterLeave) {
707                                         fakeName = mouseEnterLeave[name];
708
709                                         if (fakeName) {
710                                                 nativeHandler = function(evt) {
711                                                         var current, related;
712
713                                                         current = evt.currentTarget;
714                                                         related = evt.relatedTarget;
715
716                                                         // Check if related is inside the current target if it's not then the event should
717                                                         // be ignored since it's a mouseover/mouseout inside the element
718                                                         if (related && current.contains) {
719                                                                 // Use contains for performance
720                                                                 related = current.contains(related);
721                                                         } else {
722                                                                 while (related && related !== current) {
723                                                                         related = related.parentNode;
724                                                                 }
725                                                         }
726
727                                                         // Fire fake event
728                                                         if (!related) {
729                                                                 evt = fix(evt || win.event);
730                                                                 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
731                                                                 evt.target = current;
732                                                                 executeHandlers(evt, id);
733                                                         }
734                                                 };
735                                         }
736                                 }
737
738                                 // Fake bubbeling of focusin/focusout
739                                 if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
740                                         capture = true;
741                                         fakeName = name === "focusin" ? "focus" : "blur";
742                                         nativeHandler = function(evt) {
743                                                 evt = fix(evt || win.event);
744                                                 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
745                                                 executeHandlers(evt, id);
746                                         };
747                                 }
748
749                                 // Setup callback list and bind native event
750                                 callbackList = events[id][name];
751                                 if (!callbackList) {
752                                         events[id][name] = callbackList = [{func: callback, scope: scope}];
753                                         callbackList.fakeName = fakeName;
754                                         callbackList.capture = capture;
755
756                                         // Add the nativeHandler to the callback list so that we can later unbind it
757                                         callbackList.nativeHandler = nativeHandler;
758
759                                         // Check if the target has native events support
760
761                                         if (name === "ready") {
762                                                 bindOnReady(target, nativeHandler, self);
763                                         } else {
764                                                 addEvent(target, fakeName || name, nativeHandler, capture);
765                                         }
766                                 } else {
767                                         if (name === "ready" && self.domLoaded) {
768                                                 callback({type: name});
769                                         } else {
770                                                 // If it already has an native handler then just push the callback
771                                                 callbackList.push({func: callback, scope: scope});
772                                         }
773                                 }
774                         }
775
776                         target = callbackList = 0; // Clean memory for IE
777
778                         return callback;
779                 };
780
781                 /**
782                  * Unbinds the specified event by name, name and callback or all events on the target.
783                  *
784                  * @method unbind
785                  * @param {Object} target Target node/window or custom object.
786                  * @param {String} names Optional event name to unbind.
787                  * @param {function} callback Optional callback function to unbind.
788                  * @return {EventUtils} Event utils instance.
789                  */
790                 self.unbind = function(target, names, callback) {
791                         var id, callbackList, i, ci, name, eventMap;
792
793                         // Don't bind to text nodes or comments
794                         if (!target || target.nodeType === 3 || target.nodeType === 8) {
795                                 return self;
796                         }
797
798                         // Unbind event or events if the target has the expando
799                         id = target[expando];
800                         if (id) {
801                                 eventMap = events[id];
802
803                                 // Specific callback
804                                 if (names) {
805                                         names = names.split(' ');
806                                         i = names.length;
807                                         while (i--) {
808                                                 name = names[i];
809                                                 callbackList = eventMap[name];
810
811                                                 // Unbind the event if it exists in the map
812                                                 if (callbackList) {
813                                                         // Remove specified callback
814                                                         if (callback) {
815                                                                 ci = callbackList.length;
816                                                                 while (ci--) {
817                                                                         if (callbackList[ci].func === callback) {
818                                                                                 callbackList.splice(ci, 1);
819                                                                         }
820                                                                 }
821                                                         }
822
823                                                         // Remove all callbacks if there isn't a specified callback or there is no callbacks left
824                                                         if (!callback || callbackList.length === 0) {
825                                                                 delete eventMap[name];
826                                                                 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
827                                                         }
828                                                 }
829                                         }
830                                 } else {
831                                         // All events for a specific element
832                                         for (name in eventMap) {
833                                                 callbackList = eventMap[name];
834                                                 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
835                                         }
836
837                                         eventMap = {};
838                                 }
839
840                                 // Check if object is empty, if it isn't then we won't remove the expando map
841                                 for (name in eventMap) {
842                                         return self;
843                                 }
844
845                                 // Delete event object
846                                 delete events[id];
847
848                                 // Remove expando from target
849                                 try {
850                                         // IE will fail here since it can't delete properties from window
851                                         delete target[expando];
852                                 } catch (ex) {
853                                         // IE will set it to null
854                                         target[expando] = null;
855                                 }
856                         }
857
858                         return self;
859                 };
860
861                 /**
862                  * Fires the specified event on the specified target.
863                  *
864                  * @method fire
865                  * @param {Object} target Target node/window or custom object.
866                  * @param {String} name Event name to fire.
867                  * @param {Object} args Optional arguments to send to the observers.
868                  * @return {EventUtils} Event utils instance.
869                  */
870                 self.fire = function(target, name, args) {
871                         var id;
872
873                         // Don't bind to text nodes or comments
874                         if (!target || target.nodeType === 3 || target.nodeType === 8) {
875                                 return self;
876                         }
877
878                         // Build event object by patching the args
879                         args = fix(null, args);
880                         args.type = name;
881                         args.target = target;
882
883                         do {
884                                 // Found an expando that means there is listeners to execute
885                                 id = target[expando];
886                                 if (id) {
887                                         executeHandlers(args, id);
888                                 }
889
890                                 // Walk up the DOM
891                                 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
892                         } while (target && !args.isPropagationStopped());
893
894                         return self;
895                 };
896
897                 /**
898                  * Removes all bound event listeners for the specified target. This will also remove any bound
899                  * listeners to child nodes within that target.
900                  *
901                  * @method clean
902                  * @param {Object} target Target node/window object.
903                  * @return {EventUtils} Event utils instance.
904                  */
905                 self.clean = function(target) {
906                         var i, children, unbind = self.unbind;
907
908                         // Don't bind to text nodes or comments
909                         if (!target || target.nodeType === 3 || target.nodeType === 8) {
910                                 return self;
911                         }
912
913                         // Unbind any element on the specificed target
914                         if (target[expando]) {
915                                 unbind(target);
916                         }
917
918                         // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
919                         if (!target.getElementsByTagName) {
920                                 target = target.document;
921                         }
922
923                         // Remove events from each child element
924                         if (target && target.getElementsByTagName) {
925                                 unbind(target);
926
927                                 children = target.getElementsByTagName('*');
928                                 i = children.length;
929                                 while (i--) {
930                                         target = children[i];
931
932                                         if (target[expando]) {
933                                                 unbind(target);
934                                         }
935                                 }
936                         }
937
938                         return self;
939                 };
940
941                 /**
942                  * Destroys the event object. Call this on IE to remove memory leaks.
943                  */
944                 self.destroy = function() {
945                         events = {};
946                 };
947
948                 // Legacy function for canceling events
949                 self.cancel = function(e) {
950                         if (e) {
951                                 e.preventDefault();
952                                 e.stopImmediatePropagation();
953                         }
954
955                         return false;
956                 };
957         }
958
959         EventUtils.Event = new EventUtils();
960         EventUtils.Event.bind(window, 'ready', function() {});
961
962         return EventUtils;
963 });
964
965 // Included from: js/tinymce/classes/dom/TreeWalker.js
966
967 /**
968  * TreeWalker.js
969  *
970  * Copyright, Moxiecode Systems AB
971  * Released under LGPL License.
972  *
973  * License: http://www.tinymce.com/license
974  * Contributing: http://www.tinymce.com/contributing
975  */
976
977 /**
978  * TreeWalker class enables you to walk the DOM in a linear manner.
979  *
980  * @class tinymce.dom.TreeWalker
981  */
982 define("tinymce/dom/TreeWalker", [], function() {
983         return function(start_node, root_node) {
984                 var node = start_node;
985
986                 function findSibling(node, start_name, sibling_name, shallow) {
987                         var sibling, parent;
988
989                         if (node) {
990                                 // Walk into nodes if it has a start
991                                 if (!shallow && node[start_name]) {
992                                         return node[start_name];
993                                 }
994
995                                 // Return the sibling if it has one
996                                 if (node != root_node) {
997                                         sibling = node[sibling_name];
998                                         if (sibling) {
999                                                 return sibling;
1000                                         }
1001
1002                                         // Walk up the parents to look for siblings
1003                                         for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
1004                                                 sibling = parent[sibling_name];
1005                                                 if (sibling) {
1006                                                         return sibling;
1007                                                 }
1008                                         }
1009                                 }
1010                         }
1011                 }
1012
1013                 /**
1014                  * Returns the current node.
1015                  *
1016                  * @method current
1017                  * @return {Node} Current node where the walker is.
1018                  */
1019                 this.current = function() {
1020                         return node;
1021                 };
1022
1023                 /**
1024                  * Walks to the next node in tree.
1025                  *
1026                  * @method next
1027                  * @return {Node} Current node where the walker is after moving to the next node.
1028                  */
1029                 this.next = function(shallow) {
1030                         node = findSibling(node, 'firstChild', 'nextSibling', shallow);
1031                         return node;
1032                 };
1033
1034                 /**
1035                  * Walks to the previous node in tree.
1036                  *
1037                  * @method prev
1038                  * @return {Node} Current node where the walker is after moving to the previous node.
1039                  */
1040                 this.prev = function(shallow) {
1041                         node = findSibling(node, 'lastChild', 'previousSibling', shallow);
1042                         return node;
1043                 };
1044         };
1045 });
1046
1047 // Included from: js/tinymce/classes/util/Tools.js
1048
1049 /**
1050  * Tools.js
1051  *
1052  * Copyright, Moxiecode Systems AB
1053  * Released under LGPL License.
1054  *
1055  * License: http://www.tinymce.com/license
1056  * Contributing: http://www.tinymce.com/contributing
1057  */
1058
1059 /**
1060  * This class contains various utlity functions. These are also exposed
1061  * directly on the tinymce namespace.
1062  *
1063  * @class tinymce.util.Tools
1064  */
1065 define("tinymce/util/Tools", [], function() {
1066         /**
1067          * Removes whitespace from the beginning and end of a string.
1068          *
1069          * @method trim
1070          * @param {String} s String to remove whitespace from.
1071          * @return {String} New string with removed whitespace.
1072          */
1073         var whiteSpaceRegExp = /^\s*|\s*$/g;
1074         var trim = function(str) {
1075                 return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
1076         };
1077
1078         /**
1079          * Returns true/false if the object is an array or not.
1080          *
1081          * @method isArray
1082          * @param {Object} obj Object to check.
1083          * @return {boolean} true/false state if the object is an array or not.
1084          */
1085         var isArray = Array.isArray || function(obj) {
1086                 return Object.prototype.toString.call(obj) === "[object Array]";
1087         };
1088
1089         /**
1090          * Checks if a object is of a specific type for example an array.
1091          *
1092          * @method is
1093          * @param {Object} o Object to check type of.
1094          * @param {string} t Optional type to check for.
1095          * @return {Boolean} true/false if the object is of the specified type.
1096          */
1097         function is(o, t) {
1098                 if (!t) {
1099                         return o !== undefined;
1100                 }
1101
1102                 if (t == 'array' && isArray(o)) {
1103                         return true;
1104                 }
1105
1106                 return typeof(o) == t;
1107         }
1108
1109         /**
1110          * Converts the specified object into a real JavaScript array.
1111          *
1112          * @method toArray
1113          * @param {Object} obj Object to convert into array.
1114          * @return {Array} Array object based in input.
1115          */
1116         function toArray(obj) {
1117                 var array = [], i, l;
1118
1119                 for (i = 0, l = obj.length; i < l; i++) {
1120                         array[i] = obj[i];
1121                 }
1122
1123                 return array;
1124         }
1125
1126         /**
1127          * Makes a name/object map out of an array with names.
1128          *
1129          * @method makeMap
1130          * @param {Array/String} items Items to make map out of.
1131          * @param {String} delim Optional delimiter to split string by.
1132          * @param {Object} map Optional map to add items to.
1133          * @return {Object} Name/value map of items.
1134          */
1135         function makeMap(items, delim, map) {
1136                 var i;
1137
1138                 items = items || [];
1139                 delim = delim || ',';
1140
1141                 if (typeof(items) == "string") {
1142                         items = items.split(delim);
1143                 }
1144
1145                 map = map || {};
1146
1147                 i = items.length;
1148                 while (i--) {
1149                         map[items[i]] = {};
1150                 }
1151
1152                 return map;
1153         }
1154
1155         /**
1156          * Performs an iteration of all items in a collection such as an object or array. This method will execure the
1157          * callback function for each item in the collection, if the callback returns false the iteration will terminate.
1158          * The callback has the following format: cb(value, key_or_index).
1159          *
1160          * @method each
1161          * @param {Object} o Collection to iterate.
1162          * @param {function} cb Callback function to execute for each item.
1163          * @param {Object} s Optional scope to execute the callback in.
1164          * @example
1165          * // Iterate an array
1166          * tinymce.each([1,2,3], function(v, i) {
1167          *     console.debug("Value: " + v + ", Index: " + i);
1168          * });
1169          *
1170          * // Iterate an object
1171          * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
1172          *     console.debug("Value: " + v + ", Key: " + k);
1173          * });
1174          */
1175         function each(o, cb, s) {
1176                 var n, l;
1177
1178                 if (!o) {
1179                         return 0;
1180                 }
1181
1182                 s = s || o;
1183
1184                 if (o.length !== undefined) {
1185                         // Indexed arrays, needed for Safari
1186                         for (n=0, l = o.length; n < l; n++) {
1187                                 if (cb.call(s, o[n], n, o) === false) {
1188                                         return 0;
1189                                 }
1190                         }
1191                 } else {
1192                         // Hashtables
1193                         for (n in o) {
1194                                 if (o.hasOwnProperty(n)) {
1195                                         if (cb.call(s, o[n], n, o) === false) {
1196                                                 return 0;
1197                                         }
1198                                 }
1199                         }
1200                 }
1201
1202                 return 1;
1203         }
1204
1205         /**
1206          * Creates a new array by the return value of each iteration function call. This enables you to convert
1207          * one array list into another.
1208          *
1209          * @method map
1210          * @param {Array} a Array of items to iterate.
1211          * @param {function} f Function to call for each item. It's return value will be the new value.
1212          * @return {Array} Array with new values based on function return values.
1213          */
1214         function map(a, f) {
1215                 var o = [];
1216
1217                 each(a, function(v) {
1218                         o.push(f(v));
1219                 });
1220
1221                 return o;
1222         }
1223
1224         /**
1225          * Filters out items from the input array by calling the specified function for each item.
1226          * If the function returns false the item will be excluded if it returns true it will be included.
1227          *
1228          * @method grep
1229          * @param {Array} a Array of items to loop though.
1230          * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
1231          * @return {Array} New array with values imported and filtered based in input.
1232          * @example
1233          * // Filter out some items, this will return an array with 4 and 5
1234          * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
1235          */
1236         function grep(a, f) {
1237                 var o = [];
1238
1239                 each(a, function(v) {
1240                         if (!f || f(v)) {
1241                                 o.push(v);
1242                         }
1243                 });
1244
1245                 return o;
1246         }
1247
1248         /**
1249          * Creates a class, subclass or static singleton.
1250          * More details on this method can be found in the Wiki.
1251          *
1252          * @method create
1253          * @param {String} s Class name, inheritage and prefix.
1254          * @param {Object} p Collection of methods to add to the class.
1255          * @param {Object} root Optional root object defaults to the global window object.
1256          * @example
1257          * // Creates a basic class
1258          * tinymce.create('tinymce.somepackage.SomeClass', {
1259          *     SomeClass: function() {
1260          *         // Class constructor
1261          *     },
1262          *
1263          *     method: function() {
1264          *         // Some method
1265          *     }
1266          * });
1267          *
1268          * // Creates a basic subclass class
1269          * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
1270          *     SomeSubClass: function() {
1271          *         // Class constructor
1272          *         this.parent(); // Call parent constructor
1273          *     },
1274          *
1275          *     method: function() {
1276          *         // Some method
1277          *         this.parent(); // Call parent method
1278          *     },
1279          *
1280          *     'static': {
1281          *         staticMethod: function() {
1282          *             // Static method
1283          *         }
1284          *     }
1285          * });
1286          *
1287          * // Creates a singleton/static class
1288          * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
1289          *     method: function() {
1290          *         // Some method
1291          *     }
1292          * });
1293          */
1294         function create(s, p, root) {
1295                 var t = this, sp, ns, cn, scn, c, de = 0;
1296
1297                 // Parse : <prefix> <class>:<super class>
1298                 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
1299                 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
1300
1301                 // Create namespace for new class
1302                 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
1303
1304                 // Class already exists
1305                 if (ns[cn]) {
1306                         return;
1307                 }
1308
1309                 // Make pure static class
1310                 if (s[2] == 'static') {
1311                         ns[cn] = p;
1312
1313                         if (this.onCreate) {
1314                                 this.onCreate(s[2], s[3], ns[cn]);
1315                         }
1316
1317                         return;
1318                 }
1319
1320                 // Create default constructor
1321                 if (!p[cn]) {
1322                         p[cn] = function() {};
1323                         de = 1;
1324                 }
1325
1326                 // Add constructor and methods
1327                 ns[cn] = p[cn];
1328                 t.extend(ns[cn].prototype, p);
1329
1330                 // Extend
1331                 if (s[5]) {
1332                         sp = t.resolve(s[5]).prototype;
1333                         scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
1334
1335                         // Extend constructor
1336                         c = ns[cn];
1337                         if (de) {
1338                                 // Add passthrough constructor
1339                                 ns[cn] = function() {
1340                                         return sp[scn].apply(this, arguments);
1341                                 };
1342                         } else {
1343                                 // Add inherit constructor
1344                                 ns[cn] = function() {
1345                                         this.parent = sp[scn];
1346                                         return c.apply(this, arguments);
1347                                 };
1348                         }
1349                         ns[cn].prototype[cn] = ns[cn];
1350
1351                         // Add super methods
1352                         t.each(sp, function(f, n) {
1353                                 ns[cn].prototype[n] = sp[n];
1354                         });
1355
1356                         // Add overridden methods
1357                         t.each(p, function(f, n) {
1358                                 // Extend methods if needed
1359                                 if (sp[n]) {
1360                                         ns[cn].prototype[n] = function() {
1361                                                 this.parent = sp[n];
1362                                                 return f.apply(this, arguments);
1363                                         };
1364                                 } else {
1365                                         if (n != cn) {
1366                                                 ns[cn].prototype[n] = f;
1367                                         }
1368                                 }
1369                         });
1370                 }
1371
1372                 // Add static methods
1373                 /*jshint sub:true*/
1374                 t.each(p['static'], function(f, n) {
1375                         ns[cn][n] = f;
1376                 });
1377         }
1378
1379         /**
1380          * Returns the index of a value in an array, this method will return -1 if the item wasn't found.
1381          *
1382          * @method inArray
1383          * @param {Array} a Array/Object to search for value in.
1384          * @param {Object} v Value to check for inside the array.
1385          * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
1386          * @example
1387          * // Get index of value in array this will alert 1 since 2 is at that index
1388          * alert(tinymce.inArray([1,2,3], 2));
1389          */
1390         function inArray(a, v) {
1391                 var i, l;
1392
1393                 if (a) {
1394                         for (i = 0, l = a.length; i < l; i++) {
1395                                 if (a[i] === v) {
1396                                         return i;
1397                                 }
1398                         }
1399                 }
1400
1401                 return -1;
1402         }
1403
1404         function extend(obj, ext) {
1405                 var i, l, name, args = arguments, value;
1406
1407                 for (i = 1, l = args.length; i < l; i++) {
1408                         ext = args[i];
1409                         for (name in ext) {
1410                                 if (ext.hasOwnProperty(name)) {
1411                                         value = ext[name];
1412
1413                                         if (value !== undefined) {
1414                                                 obj[name] = value;
1415                                         }
1416                                 }
1417                         }
1418                 }
1419
1420                 return obj;
1421         }
1422
1423         /**
1424          * Executed the specified function for each item in a object tree.
1425          *
1426          * @method walk
1427          * @param {Object} o Object tree to walk though.
1428          * @param {function} f Function to call for each item.
1429          * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
1430          * @param {String} s Optional scope to execute the function in.
1431          */
1432         function walk(o, f, n, s) {
1433                 s = s || this;
1434
1435                 if (o) {
1436                         if (n) {
1437                                 o = o[n];
1438                         }
1439
1440                         each(o, function(o, i) {
1441                                 if (f.call(s, o, i, n) === false) {
1442                                         return false;
1443                                 }
1444
1445                                 walk(o, f, n, s);
1446                         });
1447                 }
1448         }
1449
1450         /**
1451          * Creates a namespace on a specific object.
1452          *
1453          * @method createNS
1454          * @param {String} n Namespace to create for example a.b.c.d.
1455          * @param {Object} o Optional object to add namespace to, defaults to window.
1456          * @return {Object} New namespace object the last item in path.
1457          * @example
1458          * // Create some namespace
1459          * tinymce.createNS('tinymce.somepackage.subpackage');
1460          *
1461          * // Add a singleton
1462          * var tinymce.somepackage.subpackage.SomeSingleton = {
1463          *     method: function() {
1464          *         // Some method
1465          *     }
1466          * };
1467          */
1468         function createNS(n, o) {
1469                 var i, v;
1470
1471                 o = o || window;
1472
1473                 n = n.split('.');
1474                 for (i=0; i<n.length; i++) {
1475                         v = n[i];
1476
1477                         if (!o[v]) {
1478                                 o[v] = {};
1479                         }
1480
1481                         o = o[v];
1482                 }
1483
1484                 return o;
1485         }
1486
1487         /**
1488          * Resolves a string and returns the object from a specific structure.
1489          *
1490          * @method resolve
1491          * @param {String} n Path to resolve for example a.b.c.d.
1492          * @param {Object} o Optional object to search though, defaults to window.
1493          * @return {Object} Last object in path or null if it couldn't be resolved.
1494          * @example
1495          * // Resolve a path into an object reference
1496          * var obj = tinymce.resolve('a.b.c.d');
1497          */
1498         function resolve(n, o) {
1499                 var i, l;
1500
1501                 o = o || window;
1502
1503                 n = n.split('.');
1504                 for (i = 0, l = n.length; i < l; i++) {
1505                         o = o[n[i]];
1506
1507                         if (!o) {
1508                                 break;
1509                         }
1510                 }
1511
1512                 return o;
1513         }
1514
1515         /**
1516          * Splits a string but removes the whitespace before and after each value.
1517          *
1518          * @method explode
1519          * @param {string} s String to split.
1520          * @param {string} d Delimiter to split by.
1521          * @example
1522          * // Split a string into an array with a,b,c
1523          * var arr = tinymce.explode('a, b,   c');
1524          */
1525         function explode(s, d) {
1526                 if (!s || is(s, 'array')) {
1527                         return s;
1528                 }
1529
1530                 return map(s.split(d || ','), trim);
1531         }
1532
1533         return {
1534                 trim: trim,
1535                 isArray: isArray,
1536                 is: is,
1537                 toArray: toArray,
1538                 makeMap: makeMap,
1539                 each: each,
1540                 map: map,
1541                 grep: grep,
1542                 inArray: inArray,
1543                 extend: extend,
1544                 create: create,
1545                 walk: walk,
1546                 createNS: createNS,
1547                 resolve: resolve,
1548                 explode: explode
1549         };
1550 });
1551
1552 // Included from: js/tinymce/classes/dom/Range.js
1553
1554 /**
1555  * Range.js
1556  *
1557  * Copyright, Moxiecode Systems AB
1558  * Released under LGPL License.
1559  *
1560  * License: http://www.tinymce.com/license
1561  * Contributing: http://www.tinymce.com/contributing
1562  */
1563
1564 define("tinymce/dom/Range", [
1565         "tinymce/util/Tools"
1566 ], function(Tools) {
1567         // Range constructor
1568         function Range(dom) {
1569                 var t = this,
1570                         doc = dom.doc,
1571                         EXTRACT = 0,
1572                         CLONE = 1,
1573                         DELETE = 2,
1574                         TRUE = true,
1575                         FALSE = false,
1576                         START_OFFSET = 'startOffset',
1577                         START_CONTAINER = 'startContainer',
1578                         END_CONTAINER = 'endContainer',
1579                         END_OFFSET = 'endOffset',
1580                         extend = Tools.extend,
1581                         nodeIndex = dom.nodeIndex;
1582
1583                 function createDocumentFragment() {
1584                         return doc.createDocumentFragment();
1585                 }
1586
1587                 function setStart(n, o) {
1588                         _setEndPoint(TRUE, n, o);
1589                 }
1590
1591                 function setEnd(n, o) {
1592                         _setEndPoint(FALSE, n, o);
1593                 }
1594
1595                 function setStartBefore(n) {
1596                         setStart(n.parentNode, nodeIndex(n));
1597                 }
1598
1599                 function setStartAfter(n) {
1600                         setStart(n.parentNode, nodeIndex(n) + 1);
1601                 }
1602
1603                 function setEndBefore(n) {
1604                         setEnd(n.parentNode, nodeIndex(n));
1605                 }
1606
1607                 function setEndAfter(n) {
1608                         setEnd(n.parentNode, nodeIndex(n) + 1);
1609                 }
1610
1611                 function collapse(ts) {
1612                         if (ts) {
1613                                 t[END_CONTAINER] = t[START_CONTAINER];
1614                                 t[END_OFFSET] = t[START_OFFSET];
1615                         } else {
1616                                 t[START_CONTAINER] = t[END_CONTAINER];
1617                                 t[START_OFFSET] = t[END_OFFSET];
1618                         }
1619
1620                         t.collapsed = TRUE;
1621                 }
1622
1623                 function selectNode(n) {
1624                         setStartBefore(n);
1625                         setEndAfter(n);
1626                 }
1627
1628                 function selectNodeContents(n) {
1629                         setStart(n, 0);
1630                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
1631                 }
1632
1633                 function compareBoundaryPoints(h, r) {
1634                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
1635                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
1636
1637                         // Check START_TO_START
1638                         if (h === 0) {
1639                                 return _compareBoundaryPoints(sc, so, rsc, rso);
1640                         }
1641
1642                         // Check START_TO_END
1643                         if (h === 1) {
1644                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
1645                         }
1646
1647                         // Check END_TO_END
1648                         if (h === 2) {
1649                                 return _compareBoundaryPoints(ec, eo, rec, reo);
1650                         }
1651
1652                         // Check END_TO_START
1653                         if (h === 3) {
1654                                 return _compareBoundaryPoints(sc, so, rec, reo);
1655                         }
1656                 }
1657
1658                 function deleteContents() {
1659                         _traverse(DELETE);
1660                 }
1661
1662                 function extractContents() {
1663                         return _traverse(EXTRACT);
1664                 }
1665
1666                 function cloneContents() {
1667                         return _traverse(CLONE);
1668                 }
1669
1670                 function insertNode(n) {
1671                         var startContainer = this[START_CONTAINER],
1672                                 startOffset = this[START_OFFSET], nn, o;
1673
1674                         // Node is TEXT_NODE or CDATA
1675                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
1676                                 if (!startOffset) {
1677                                         // At the start of text
1678                                         startContainer.parentNode.insertBefore(n, startContainer);
1679                                 } else if (startOffset >= startContainer.nodeValue.length) {
1680                                         // At the end of text
1681                                         dom.insertAfter(n, startContainer);
1682                                 } else {
1683                                         // Middle, need to split
1684                                         nn = startContainer.splitText(startOffset);
1685                                         startContainer.parentNode.insertBefore(n, nn);
1686                                 }
1687                         } else {
1688                                 // Insert element node
1689                                 if (startContainer.childNodes.length > 0) {
1690                                         o = startContainer.childNodes[startOffset];
1691                                 }
1692
1693                                 if (o) {
1694                                         startContainer.insertBefore(n, o);
1695                                 } else {
1696                                         startContainer.appendChild(n);
1697                                 }
1698                         }
1699                 }
1700
1701                 function surroundContents(n) {
1702                         var f = t.extractContents();
1703
1704                         t.insertNode(n);
1705                         n.appendChild(f);
1706                         t.selectNode(n);
1707                 }
1708
1709                 function cloneRange() {
1710                         return extend(new Range(dom), {
1711                                 startContainer: t[START_CONTAINER],
1712                                 startOffset: t[START_OFFSET],
1713                                 endContainer: t[END_CONTAINER],
1714                                 endOffset: t[END_OFFSET],
1715                                 collapsed: t.collapsed,
1716                                 commonAncestorContainer: t.commonAncestorContainer
1717                         });
1718                 }
1719
1720                 // Private methods
1721
1722                 function _getSelectedNode(container, offset) {
1723                         var child;
1724
1725                         if (container.nodeType == 3 /* TEXT_NODE */) {
1726                                 return container;
1727                         }
1728
1729                         if (offset < 0) {
1730                                 return container;
1731                         }
1732
1733                         child = container.firstChild;
1734                         while (child && offset > 0) {
1735                                 --offset;
1736                                 child = child.nextSibling;
1737                         }
1738
1739                         if (child) {
1740                                 return child;
1741                         }
1742
1743                         return container;
1744                 }
1745
1746                 function _isCollapsed() {
1747                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
1748                 }
1749
1750                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
1751                         var c, offsetC, n, cmnRoot, childA, childB;
1752
1753                         // In the first case the boundary-points have the same container. A is before B
1754                         // if its offset is less than the offset of B, A is equal to B if its offset is
1755                         // equal to the offset of B, and A is after B if its offset is greater than the
1756                         // offset of B.
1757                         if (containerA == containerB) {
1758                                 if (offsetA == offsetB) {
1759                                         return 0; // equal
1760                                 }
1761
1762                                 if (offsetA < offsetB) {
1763                                         return -1; // before
1764                                 }
1765
1766                                 return 1; // after
1767                         }
1768
1769                         // In the second case a child node C of the container of A is an ancestor
1770                         // container of B. In this case, A is before B if the offset of A is less than or
1771                         // equal to the index of the child node C and A is after B otherwise.
1772                         c = containerB;
1773                         while (c && c.parentNode != containerA) {
1774                                 c = c.parentNode;
1775                         }
1776
1777                         if (c) {
1778                                 offsetC = 0;
1779                                 n = containerA.firstChild;
1780
1781                                 while (n != c && offsetC < offsetA) {
1782                                         offsetC++;
1783                                         n = n.nextSibling;
1784                                 }
1785
1786                                 if (offsetA <= offsetC) {
1787                                         return -1; // before
1788                                 }
1789
1790                                 return 1; // after
1791                         }
1792
1793                         // In the third case a child node C of the container of B is an ancestor container
1794                         // of A. In this case, A is before B if the index of the child node C is less than
1795                         // the offset of B and A is after B otherwise.
1796                         c = containerA;
1797                         while (c && c.parentNode != containerB) {
1798                                 c = c.parentNode;
1799                         }
1800
1801                         if (c) {
1802                                 offsetC = 0;
1803                                 n = containerB.firstChild;
1804
1805                                 while (n != c && offsetC < offsetB) {
1806                                         offsetC++;
1807                                         n = n.nextSibling;
1808                                 }
1809
1810                                 if (offsetC < offsetB) {
1811                                         return -1; // before
1812                                 }
1813
1814                                 return 1; // after
1815                         }
1816
1817                         // In the fourth case, none of three other cases hold: the containers of A and B
1818                         // are siblings or descendants of sibling nodes. In this case, A is before B if
1819                         // the container of A is before the container of B in a pre-order traversal of the
1820                         // Ranges' context tree and A is after B otherwise.
1821                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
1822                         childA = containerA;
1823
1824                         while (childA && childA.parentNode != cmnRoot) {
1825                                 childA = childA.parentNode;
1826                         }
1827
1828                         if (!childA) {
1829                                 childA = cmnRoot;
1830                         }
1831
1832                         childB = containerB;
1833                         while (childB && childB.parentNode != cmnRoot) {
1834                                 childB = childB.parentNode;
1835                         }
1836
1837                         if (!childB) {
1838                                 childB = cmnRoot;
1839                         }
1840
1841                         if (childA == childB) {
1842                                 return 0; // equal
1843                         }
1844
1845                         n = cmnRoot.firstChild;
1846                         while (n) {
1847                                 if (n == childA) {
1848                                         return -1; // before
1849                                 }
1850
1851                                 if (n == childB) {
1852                                         return 1; // after
1853                                 }
1854
1855                                 n = n.nextSibling;
1856                         }
1857                 }
1858
1859                 function _setEndPoint(st, n, o) {
1860                         var ec, sc;
1861
1862                         if (st) {
1863                                 t[START_CONTAINER] = n;
1864                                 t[START_OFFSET] = o;
1865                         } else {
1866                                 t[END_CONTAINER] = n;
1867                                 t[END_OFFSET] = o;
1868                         }
1869
1870                         // If one boundary-point of a Range is set to have a root container
1871                         // other than the current one for the Range, the Range is collapsed to
1872                         // the new position. This enforces the restriction that both boundary-
1873                         // points of a Range must have the same root container.
1874                         ec = t[END_CONTAINER];
1875                         while (ec.parentNode) {
1876                                 ec = ec.parentNode;
1877                         }
1878
1879                         sc = t[START_CONTAINER];
1880                         while (sc.parentNode) {
1881                                 sc = sc.parentNode;
1882                         }
1883
1884                         if (sc == ec) {
1885                                 // The start position of a Range is guaranteed to never be after the
1886                                 // end position. To enforce this restriction, if the start is set to
1887                                 // be at a position after the end, the Range is collapsed to that
1888                                 // position.
1889                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) {
1890                                         t.collapse(st);
1891                                 }
1892                         } else {
1893                                 t.collapse(st);
1894                         }
1895
1896                         t.collapsed = _isCollapsed();
1897                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
1898                 }
1899
1900                 function _traverse(how) {
1901                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
1902
1903                         if (t[START_CONTAINER] == t[END_CONTAINER]) {
1904                                 return _traverseSameContainer(how);
1905                         }
1906
1907                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
1908                                 if (p == t[START_CONTAINER]) {
1909                                         return _traverseCommonStartContainer(c, how);
1910                                 }
1911
1912                                 ++endContainerDepth;
1913                         }
1914
1915                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
1916                                 if (p == t[END_CONTAINER]) {
1917                                         return _traverseCommonEndContainer(c, how);
1918                                 }
1919
1920                                 ++startContainerDepth;
1921                         }
1922
1923                         depthDiff = startContainerDepth - endContainerDepth;
1924
1925                         startNode = t[START_CONTAINER];
1926                         while (depthDiff > 0) {
1927                                 startNode = startNode.parentNode;
1928                                 depthDiff--;
1929                         }
1930
1931                         endNode = t[END_CONTAINER];
1932                         while (depthDiff < 0) {
1933                                 endNode = endNode.parentNode;
1934                                 depthDiff++;
1935                         }
1936
1937                         // ascend the ancestor hierarchy until we have a common parent.
1938                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
1939                                 startNode = sp;
1940                                 endNode = ep;
1941                         }
1942
1943                         return _traverseCommonAncestors(startNode, endNode, how);
1944                 }
1945
1946                  function _traverseSameContainer(how) {
1947                         var frag, s, sub, n, cnt, sibling, xferNode, start, len;
1948
1949                         if (how != DELETE) {
1950                                 frag = createDocumentFragment();
1951                         }
1952
1953                         // If selection is empty, just return the fragment
1954                         if (t[START_OFFSET] == t[END_OFFSET]) {
1955                                 return frag;
1956                         }
1957
1958                         // Text node needs special case handling
1959                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
1960                                 // get the substring
1961                                 s = t[START_CONTAINER].nodeValue;
1962                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
1963
1964                                 // set the original text node to its new value
1965                                 if (how != CLONE) {
1966                                         n = t[START_CONTAINER];
1967                                         start = t[START_OFFSET];
1968                                         len = t[END_OFFSET] - t[START_OFFSET];
1969
1970                                         if (start === 0 && len >= n.nodeValue.length - 1) {
1971                                                 n.parentNode.removeChild(n);
1972                                         } else {
1973                                                 n.deleteData(start, len);
1974                                         }
1975
1976                                         // Nothing is partially selected, so collapse to start point
1977                                         t.collapse(TRUE);
1978                                 }
1979
1980                                 if (how == DELETE) {
1981                                         return;
1982                                 }
1983
1984                                 if (sub.length > 0) {
1985                                         frag.appendChild(doc.createTextNode(sub));
1986                                 }
1987
1988                                 return frag;
1989                         }
1990
1991                         // Copy nodes between the start/end offsets.
1992                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
1993                         cnt = t[END_OFFSET] - t[START_OFFSET];
1994
1995                         while (n && cnt > 0) {
1996                                 sibling = n.nextSibling;
1997                                 xferNode = _traverseFullySelected(n, how);
1998
1999                                 if (frag) {
2000                                         frag.appendChild(xferNode);
2001                                 }
2002
2003                                 --cnt;
2004                                 n = sibling;
2005                         }
2006
2007                         // Nothing is partially selected, so collapse to start point
2008                         if (how != CLONE) {
2009                                 t.collapse(TRUE);
2010                         }
2011
2012                         return frag;
2013                 }
2014
2015                 function _traverseCommonStartContainer(endAncestor, how) {
2016                         var frag, n, endIdx, cnt, sibling, xferNode;
2017
2018                         if (how != DELETE) {
2019                                 frag = createDocumentFragment();
2020                         }
2021
2022                         n = _traverseRightBoundary(endAncestor, how);
2023
2024                         if (frag) {
2025                                 frag.appendChild(n);
2026                         }
2027
2028                         endIdx = nodeIndex(endAncestor);
2029                         cnt = endIdx - t[START_OFFSET];
2030
2031                         if (cnt <= 0) {
2032                                 // Collapse to just before the endAncestor, which
2033                                 // is partially selected.
2034                                 if (how != CLONE) {
2035                                         t.setEndBefore(endAncestor);
2036                                         t.collapse(FALSE);
2037                                 }
2038
2039                                 return frag;
2040                         }
2041
2042                         n = endAncestor.previousSibling;
2043                         while (cnt > 0) {
2044                                 sibling = n.previousSibling;
2045                                 xferNode = _traverseFullySelected(n, how);
2046
2047                                 if (frag) {
2048                                         frag.insertBefore(xferNode, frag.firstChild);
2049                                 }
2050
2051                                 --cnt;
2052                                 n = sibling;
2053                         }
2054
2055                         // Collapse to just before the endAncestor, which
2056                         // is partially selected.
2057                         if (how != CLONE) {
2058                                 t.setEndBefore(endAncestor);
2059                                 t.collapse(FALSE);
2060                         }
2061
2062                         return frag;
2063                 }
2064
2065                 function _traverseCommonEndContainer(startAncestor, how) {
2066                         var frag, startIdx, n, cnt, sibling, xferNode;
2067
2068                         if (how != DELETE) {
2069                                 frag = createDocumentFragment();
2070                         }
2071
2072                         n = _traverseLeftBoundary(startAncestor, how);
2073                         if (frag) {
2074                                 frag.appendChild(n);
2075                         }
2076
2077                         startIdx = nodeIndex(startAncestor);
2078                         ++startIdx; // Because we already traversed it
2079
2080                         cnt = t[END_OFFSET] - startIdx;
2081                         n = startAncestor.nextSibling;
2082                         while (n && cnt > 0) {
2083                                 sibling = n.nextSibling;
2084                                 xferNode = _traverseFullySelected(n, how);
2085
2086                                 if (frag) {
2087                                         frag.appendChild(xferNode);
2088                                 }
2089
2090                                 --cnt;
2091                                 n = sibling;
2092                         }
2093
2094                         if (how != CLONE) {
2095                                 t.setStartAfter(startAncestor);
2096                                 t.collapse(TRUE);
2097                         }
2098
2099                         return frag;
2100                 }
2101
2102                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
2103                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
2104
2105                         if (how != DELETE) {
2106                                 frag = createDocumentFragment();
2107                         }
2108
2109                         n = _traverseLeftBoundary(startAncestor, how);
2110                         if (frag) {
2111                                 frag.appendChild(n);
2112                         }
2113
2114                         commonParent = startAncestor.parentNode;
2115                         startOffset = nodeIndex(startAncestor);
2116                         endOffset = nodeIndex(endAncestor);
2117                         ++startOffset;
2118
2119                         cnt = endOffset - startOffset;
2120                         sibling = startAncestor.nextSibling;
2121
2122                         while (cnt > 0) {
2123                                 nextSibling = sibling.nextSibling;
2124                                 n = _traverseFullySelected(sibling, how);
2125
2126                                 if (frag) {
2127                                         frag.appendChild(n);
2128                                 }
2129
2130                                 sibling = nextSibling;
2131                                 --cnt;
2132                         }
2133
2134                         n = _traverseRightBoundary(endAncestor, how);
2135
2136                         if (frag) {
2137                                 frag.appendChild(n);
2138                         }
2139
2140                         if (how != CLONE) {
2141                                 t.setStartAfter(startAncestor);
2142                                 t.collapse(TRUE);
2143                         }
2144
2145                         return frag;
2146                 }
2147
2148                 function _traverseRightBoundary(root, how) {
2149                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent;
2150                         var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
2151
2152                         if (next == root) {
2153                                 return _traverseNode(next, isFullySelected, FALSE, how);
2154                         }
2155
2156                         parent = next.parentNode;
2157                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
2158
2159                         while (parent) {
2160                                 while (next) {
2161                                         prevSibling = next.previousSibling;
2162                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
2163
2164                                         if (how != DELETE) {
2165                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
2166                                         }
2167
2168                                         isFullySelected = TRUE;
2169                                         next = prevSibling;
2170                                 }
2171
2172                                 if (parent == root) {
2173                                         return clonedParent;
2174                                 }
2175
2176                                 next = parent.previousSibling;
2177                                 parent = parent.parentNode;
2178
2179                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
2180
2181                                 if (how != DELETE) {
2182                                         clonedGrandParent.appendChild(clonedParent);
2183                                 }
2184
2185                                 clonedParent = clonedGrandParent;
2186                         }
2187                 }
2188
2189                 function _traverseLeftBoundary(root, how) {
2190                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER];
2191                         var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
2192
2193                         if (next == root) {
2194                                 return _traverseNode(next, isFullySelected, TRUE, how);
2195                         }
2196
2197                         parent = next.parentNode;
2198                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
2199
2200                         while (parent) {
2201                                 while (next) {
2202                                         nextSibling = next.nextSibling;
2203                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
2204
2205                                         if (how != DELETE) {
2206                                                 clonedParent.appendChild(clonedChild);
2207                                         }
2208
2209                                         isFullySelected = TRUE;
2210                                         next = nextSibling;
2211                                 }
2212
2213                                 if (parent == root) {
2214                                         return clonedParent;
2215                                 }
2216
2217                                 next = parent.nextSibling;
2218                                 parent = parent.parentNode;
2219
2220                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
2221
2222                                 if (how != DELETE) {
2223                                         clonedGrandParent.appendChild(clonedParent);
2224                                 }
2225
2226                                 clonedParent = clonedGrandParent;
2227                         }
2228                 }
2229
2230                 function _traverseNode(n, isFullySelected, isLeft, how) {
2231                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
2232
2233                         if (isFullySelected) {
2234                                 return _traverseFullySelected(n, how);
2235                         }
2236
2237                         if (n.nodeType == 3 /* TEXT_NODE */) {
2238                                 txtValue = n.nodeValue;
2239
2240                                 if (isLeft) {
2241                                         offset = t[START_OFFSET];
2242                                         newNodeValue = txtValue.substring(offset);
2243                                         oldNodeValue = txtValue.substring(0, offset);
2244                                 } else {
2245                                         offset = t[END_OFFSET];
2246                                         newNodeValue = txtValue.substring(0, offset);
2247                                         oldNodeValue = txtValue.substring(offset);
2248                                 }
2249
2250                                 if (how != CLONE) {
2251                                         n.nodeValue = oldNodeValue;
2252                                 }
2253
2254                                 if (how == DELETE) {
2255                                         return;
2256                                 }
2257
2258                                 newNode = dom.clone(n, FALSE);
2259                                 newNode.nodeValue = newNodeValue;
2260
2261                                 return newNode;
2262                         }
2263
2264                         if (how == DELETE) {
2265                                 return;
2266                         }
2267
2268                         return dom.clone(n, FALSE);
2269                 }
2270
2271                 function _traverseFullySelected(n, how) {
2272                         if (how != DELETE) {
2273                                 return how == CLONE ? dom.clone(n, TRUE) : n;
2274                         }
2275
2276                         n.parentNode.removeChild(n);
2277                 }
2278
2279                 function toStringIE() {
2280                         return dom.create('body', null, cloneContents()).outerText;
2281                 }
2282
2283                 extend(t, {
2284                         // Inital states
2285                         startContainer: doc,
2286                         startOffset: 0,
2287                         endContainer: doc,
2288                         endOffset: 0,
2289                         collapsed: TRUE,
2290                         commonAncestorContainer: doc,
2291
2292                         // Range constants
2293                         START_TO_START: 0,
2294                         START_TO_END: 1,
2295                         END_TO_END: 2,
2296                         END_TO_START: 3,
2297
2298                         // Public methods
2299                         setStart: setStart,
2300                         setEnd: setEnd,
2301                         setStartBefore: setStartBefore,
2302                         setStartAfter: setStartAfter,
2303                         setEndBefore: setEndBefore,
2304                         setEndAfter: setEndAfter,
2305                         collapse: collapse,
2306                         selectNode: selectNode,
2307                         selectNodeContents: selectNodeContents,
2308                         compareBoundaryPoints: compareBoundaryPoints,
2309                         deleteContents: deleteContents,
2310                         extractContents: extractContents,
2311                         cloneContents: cloneContents,
2312                         insertNode: insertNode,
2313                         surroundContents: surroundContents,
2314                         cloneRange: cloneRange,
2315                         toStringIE: toStringIE
2316                 });
2317
2318                 return t;
2319         }
2320
2321         // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
2322         Range.prototype.toString = function() {
2323                 return this.toStringIE();
2324         };
2325
2326         return Range;
2327 });
2328
2329 // Included from: js/tinymce/classes/html/Entities.js
2330
2331 /**
2332  * Entities.js
2333  *
2334  * Copyright, Moxiecode Systems AB
2335  * Released under LGPL License.
2336  *
2337  * License: http://www.tinymce.com/license
2338  * Contributing: http://www.tinymce.com/contributing
2339  */
2340
2341 /*jshint bitwise:false */
2342
2343 /**
2344  * Entity encoder class.
2345  *
2346  * @class tinymce.html.Entities
2347  * @static
2348  * @version 3.4
2349  */
2350 define("tinymce/html/Entities", [
2351         "tinymce/util/Tools"
2352 ], function(Tools) {
2353         var makeMap = Tools.makeMap;
2354
2355         var namedEntities, baseEntities, reverseEntities,
2356                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2357                 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2358                 rawCharsRegExp = /[<>&\"\']/g,
2359                 entityRegExp = /&(#x|#)?([\w]+);/g,
2360                 asciiMap = {
2361                         128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
2362                         135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
2363                         142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
2364                         150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
2365                         156: "\u0153", 158: "\u017E", 159: "\u0178"
2366                 };
2367
2368         // Raw entities
2369         baseEntities = {
2370                 '\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
2371                 "'": '&#39;',
2372                 '<': '&lt;',
2373                 '>': '&gt;',
2374                 '&': '&amp;'
2375         };
2376
2377         // Reverse lookup table for raw entities
2378         reverseEntities = {
2379                 '&lt;': '<',
2380                 '&gt;': '>',
2381                 '&amp;': '&',
2382                 '&quot;': '"',
2383                 '&apos;': "'"
2384         };
2385
2386         // Decodes text by using the browser
2387         function nativeDecode(text) {
2388                 var elm;
2389
2390                 elm = document.createElement("div");
2391                 elm.innerHTML = text;
2392
2393                 return elm.textContent || elm.innerText || text;
2394         }
2395
2396         // Build a two way lookup table for the entities
2397         function buildEntitiesLookup(items, radix) {
2398                 var i, chr, entity, lookup = {};
2399
2400                 if (items) {
2401                         items = items.split(',');
2402                         radix = radix || 10;
2403
2404                         // Build entities lookup table
2405                         for (i = 0; i < items.length; i += 2) {
2406                                 chr = String.fromCharCode(parseInt(items[i], radix));
2407
2408                                 // Only add non base entities
2409                                 if (!baseEntities[chr]) {
2410                                         entity = '&' + items[i + 1] + ';';
2411                                         lookup[chr] = entity;
2412                                         lookup[entity] = chr;
2413                                 }
2414                         }
2415
2416                         return lookup;
2417                 }
2418         }
2419
2420         // Unpack entities lookup where the numbers are in radix 32 to reduce the size
2421         namedEntities = buildEntitiesLookup(
2422                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
2423                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
2424                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
2425                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
2426                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
2427                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
2428                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
2429                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
2430                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
2431                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
2432                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
2433                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
2434                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
2435                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
2436                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
2437                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
2438                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
2439                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
2440                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
2441                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
2442                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
2443                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
2444                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
2445                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2446                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
2447
2448         var Entities = {
2449                 /**
2450                  * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded.
2451                  *
2452                  * @method encodeRaw
2453                  * @param {String} text Text to encode.
2454                  * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
2455                  * @return {String} Entity encoded text.
2456                  */
2457                 encodeRaw: function(text, attr) {
2458                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2459                                 return baseEntities[chr] || chr;
2460                         });
2461                 },
2462
2463                 /**
2464                  * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
2465                  * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
2466                  * and is exposed as the DOMUtils.encode function.
2467                  *
2468                  * @method encodeAllRaw
2469                  * @param {String} text Text to encode.
2470                  * @return {String} Entity encoded text.
2471                  */
2472                 encodeAllRaw: function(text) {
2473                         return ('' + text).replace(rawCharsRegExp, function(chr) {
2474                                 return baseEntities[chr] || chr;
2475                         });
2476                 },
2477
2478                 /**
2479                  * Encodes the specified string using numeric entities. The core entities will be
2480                  * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
2481                  *
2482                  * @method encodeNumeric
2483                  * @param {String} text Text to encode.
2484                  * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
2485                  * @return {String} Entity encoded text.
2486                  */
2487                 encodeNumeric: function(text, attr) {
2488                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2489                                 // Multi byte sequence convert it to a single entity
2490                                 if (chr.length > 1) {
2491                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
2492                                 }
2493
2494                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
2495                         });
2496                 },
2497
2498                 /**
2499                  * Encodes the specified string using named entities. The core entities will be encoded
2500                  * as named ones but all non lower ascii characters will be encoded into named entities.
2501                  *
2502                  * @method encodeNamed
2503                  * @param {String} text Text to encode.
2504                  * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
2505                  * @param {Object} entities Optional parameter with entities to use.
2506                  * @return {String} Entity encoded text.
2507                  */
2508                 encodeNamed: function(text, attr, entities) {
2509                         entities = entities || namedEntities;
2510
2511                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2512                                 return baseEntities[chr] || entities[chr] || chr;
2513                         });
2514                 },
2515
2516                 /**
2517                  * Returns an encode function based on the name(s) and it's optional entities.
2518                  *
2519                  * @method getEncodeFunc
2520                  * @param {String} name Comma separated list of encoders for example named,numeric.
2521                  * @param {String} entities Optional parameter with entities to use instead of the built in set.
2522                  * @return {function} Encode function to be used.
2523                  */
2524                 getEncodeFunc: function(name, entities) {
2525                         entities = buildEntitiesLookup(entities) || namedEntities;
2526
2527                         function encodeNamedAndNumeric(text, attr) {
2528                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2529                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
2530                                 });
2531                         }
2532
2533                         function encodeCustomNamed(text, attr) {
2534                                 return Entities.encodeNamed(text, attr, entities);
2535                         }
2536
2537                         // Replace + with , to be compatible with previous TinyMCE versions
2538                         name = makeMap(name.replace(/\+/g, ','));
2539
2540                         // Named and numeric encoder
2541                         if (name.named && name.numeric) {
2542                                 return encodeNamedAndNumeric;
2543                         }
2544
2545                         // Named encoder
2546                         if (name.named) {
2547                                 // Custom names
2548                                 if (entities) {
2549                                         return encodeCustomNamed;
2550                                 }
2551
2552                                 return Entities.encodeNamed;
2553                         }
2554
2555                         // Numeric
2556                         if (name.numeric) {
2557                                 return Entities.encodeNumeric;
2558                         }
2559
2560                         // Raw encoder
2561                         return Entities.encodeRaw;
2562                 },
2563
2564                 /**
2565                  * Decodes the specified string, this will replace entities with raw UTF characters.
2566                  *
2567                  * @method decode
2568                  * @param {String} text Text to entity decode.
2569                  * @return {String} Entity decoded string.
2570                  */
2571                 decode: function(text) {
2572                         return text.replace(entityRegExp, function(all, numeric, value) {
2573                                 if (numeric) {
2574                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);
2575
2576                                         // Support upper UTF
2577                                         if (value > 0xFFFF) {
2578                                                 value -= 0x10000;
2579
2580                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
2581                                         } else {
2582                                                 return asciiMap[value] || String.fromCharCode(value);
2583                                         }
2584                                 }
2585
2586                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
2587                         });
2588                 }
2589         };
2590
2591         return Entities;
2592 });
2593
2594 // Included from: js/tinymce/classes/Env.js
2595
2596 /**
2597  * Env.js
2598  *
2599  * Copyright, Moxiecode Systems AB
2600  * Released under LGPL License.
2601  *
2602  * License: http://www.tinymce.com/license
2603  * Contributing: http://www.tinymce.com/contributing
2604  */
2605
2606 /**
2607  * This class contains various environment constrants like browser versions etc.
2608  * Normally you don't want to sniff specific browser versions but sometimes you have
2609  * to when it's impossible to feature detect. So use this with care.
2610  *
2611  * @class tinymce.Env
2612  * @static
2613  */
2614 define("tinymce/Env", [], function() {
2615         var nav = navigator, userAgent = nav.userAgent;
2616         var opera, webkit, ie, ie11, gecko, mac, iDevice;
2617
2618         opera = window.opera && window.opera.buildNumber;
2619         webkit = /WebKit/.test(userAgent);
2620         ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
2621         ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
2622         ie11 = userAgent.indexOf('Trident') != -1 ? 11 : false;
2623         ie = ie || ie11;
2624         gecko = !webkit && /Gecko/.test(userAgent);
2625         mac = userAgent.indexOf('Mac') != -1;
2626         iDevice = /(iPad|iPhone)/.test(userAgent);
2627
2628         // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
2629         // says it has contentEditable support but there is no visible caret.
2630         var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
2631
2632         return {
2633                 /**
2634                  * Constant that is true if the browser is Opera.
2635                  *
2636                  * @property opera
2637                  * @type Boolean
2638                  * @final
2639                  */
2640                 opera: opera,
2641
2642                 /**
2643                  * Constant that is true if the browser is WebKit (Safari/Chrome).
2644                  *
2645                  * @property webKit
2646                  * @type Boolean
2647                  * @final
2648                  */
2649                 webkit: webkit,
2650
2651                 /**
2652                  * Constant that is more than zero if the browser is IE.
2653                  *
2654                  * @property ie
2655                  * @type Boolean
2656                  * @final
2657                  */
2658                 ie: ie,
2659
2660                 /**
2661                  * Constant that is true if the browser is Gecko.
2662                  *
2663                  * @property gecko
2664                  * @type Boolean
2665                  * @final
2666                  */
2667                 gecko: gecko,
2668
2669                 /**
2670                  * Constant that is true if the os is Mac OS.
2671                  *
2672                  * @property mac
2673                  * @type Boolean
2674                  * @final
2675                  */
2676                 mac: mac,
2677
2678                 /**
2679                  * Constant that is true if the os is iOS.
2680                  *
2681                  * @property iOS
2682                  * @type Boolean
2683                  * @final
2684                  */
2685                 iOS: iDevice,
2686
2687                 /**
2688                  * Constant that is true if the browser supports editing.
2689                  *
2690                  * @property contentEditable
2691                  * @type Boolean
2692                  * @final
2693                  */
2694                 contentEditable: contentEditable,
2695
2696                 /**
2697                  * Transparent image data url.
2698                  *
2699                  * @property transparentSrc
2700                  * @type Boolean
2701                  * @final
2702                  */
2703                 transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
2704
2705                 /**
2706                  * Returns true/false if the browser can or can't place the caret after a inline block like an image.
2707                  *
2708                  * @property noCaretAfter
2709                  * @type Boolean
2710                  * @final
2711                  */
2712                 caretAfter: ie != 8,
2713
2714                 /**
2715                  * Constant that is true if the browser supports native DOM Ranges. IE 9+.
2716                  *
2717                  * @property range
2718                  * @type Boolean
2719                  */
2720                 range: window.getSelection && "Range" in window,
2721
2722                 /**
2723                  * Returns the IE document mode for non IE browsers this will fake IE 10.
2724                  *
2725                  * @property documentMode
2726                  * @type Number
2727                  */
2728                 documentMode: ie ? (document.documentMode || 7) : 10
2729         };
2730 });
2731
2732 // Included from: js/tinymce/classes/dom/DOMUtils.js
2733
2734 /**
2735  * DOMUtils.js
2736  *
2737  * Copyright, Moxiecode Systems AB
2738  * Released under LGPL License.
2739  *
2740  * License: http://www.tinymce.com/license
2741  * Contributing: http://www.tinymce.com/contributing
2742  */
2743
2744 /**
2745  * Utility class for various DOM manipulation and retrieval functions.
2746  *
2747  * @class tinymce.dom.DOMUtils
2748  * @example
2749  * // Add a class to an element by id in the page
2750  * tinymce.DOM.addClass('someid', 'someclass');
2751  *
2752  * // Add a class to an element by id inside the editor
2753  * tinymce.activeEditor.dom.addClass('someid', 'someclass');
2754  */
2755 define("tinymce/dom/DOMUtils", [
2756         "tinymce/dom/Sizzle",
2757         "tinymce/html/Styles",
2758         "tinymce/dom/EventUtils",
2759         "tinymce/dom/TreeWalker",
2760         "tinymce/dom/Range",
2761         "tinymce/html/Entities",
2762         "tinymce/Env",
2763         "tinymce/util/Tools"
2764 ], function(Sizzle, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools) {
2765         // Shorten names
2766         var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim, extend = Tools.extend;
2767         var isWebKit = Env.webkit, isIE = Env.ie;
2768         var simpleSelectorRe = /^([a-z0-9],?)+$/i;
2769         var whiteSpaceRegExp = /^[ \t\r\n]*$/;
2770         var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
2771
2772         /**
2773          * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
2774          *
2775          * @constructor
2776          * @method DOMUtils
2777          * @param {Document} d Document reference to bind the utility class to.
2778          * @param {settings} s Optional settings collection.
2779          */
2780         function DOMUtils(doc, settings) {
2781                 var self = this, blockElementsMap;
2782
2783                 self.doc = doc;
2784                 self.win = window;
2785                 self.files = {};
2786                 self.counter = 0;
2787                 self.stdMode = !isIE || doc.documentMode >= 8;
2788                 self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
2789                 self.hasOuterHTML = "outerHTML" in doc.createElement("a");
2790
2791                 self.settings = settings = extend({
2792                         keep_values: false,
2793                         hex_colors: 1
2794                 }, settings);
2795
2796                 self.schema = settings.schema;
2797                 self.styles = new Styles({
2798                         url_converter: settings.url_converter,
2799                         url_converter_scope: settings.url_converter_scope
2800                 }, settings.schema);
2801
2802                 self.fixDoc(doc);
2803                 self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
2804                 blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
2805
2806                 /**
2807                  * Returns true/false if the specified element is a block element or not.
2808                  *
2809                  * @method isBlock
2810                  * @param {Node/String} node Element/Node to check.
2811                  * @return {Boolean} True/False state if the node is a block element or not.
2812                  */
2813                 self.isBlock = function(node) {
2814                         // Fix for #5446
2815                         if (!node) {
2816                                 return false;
2817                         }
2818
2819                         // This function is called in module pattern style since it might be executed with the wrong this scope
2820                         var type = node.nodeType;
2821
2822                         // If it's a node then check the type and use the nodeName
2823                         if (type) {
2824                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
2825                         }
2826
2827                         return !!blockElementsMap[node];
2828                 };
2829         }
2830
2831         DOMUtils.prototype = {
2832                 root: null,
2833                 props: {
2834                         "for": "htmlFor",
2835                         "class": "className",
2836                         className: "className",
2837                         checked: "checked",
2838                         disabled: "disabled",
2839                         maxlength: "maxLength",
2840                         readonly: "readOnly",
2841                         selected: "selected",
2842                         value: "value",
2843                         id: "id",
2844                         name: "name",
2845                         type: "type"
2846                 },
2847
2848                 fixDoc: function(doc) {
2849                         var settings = this.settings, name;
2850
2851                         if (isIE && settings.schema) {
2852                                 // Add missing HTML 4/5 elements to IE
2853                                 ('abbr article aside audio canvas ' +
2854                                 'details figcaption figure footer ' +
2855                                 'header hgroup mark menu meter nav ' +
2856                                 'output progress section summary ' +
2857                                 'time video').replace(/\w+/g, function(name) {
2858                                         doc.createElement(name);
2859                                 });
2860
2861                                 // Create all custom elements
2862                                 for (name in settings.schema.getCustomElements()) {
2863                                         doc.createElement(name);
2864                                 }
2865                         }
2866                 },
2867
2868                 clone: function(node, deep) {
2869                         var self = this, clone, doc;
2870
2871                         // TODO: Add feature detection here in the future
2872                         if (!isIE || node.nodeType !== 1 || deep) {
2873                                 return node.cloneNode(deep);
2874                         }
2875
2876                         doc = self.doc;
2877
2878                         // Make a HTML5 safe shallow copy
2879                         if (!deep) {
2880                                 clone = doc.createElement(node.nodeName);
2881
2882                                 // Copy attribs
2883                                 each(self.getAttribs(node), function(attr) {
2884                                         self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
2885                                 });
2886
2887                                 return clone;
2888                         }
2889 /*
2890                         // Setup HTML5 patched document fragment
2891                         if (!self.frag) {
2892                                 self.frag = doc.createDocumentFragment();
2893                                 self.fixDoc(self.frag);
2894                         }
2895
2896                         // Make a deep copy by adding it to the document fragment then removing it this removed the :section
2897                         clone = doc.createElement('div');
2898                         self.frag.appendChild(clone);
2899                         clone.innerHTML = node.outerHTML;
2900                         self.frag.removeChild(clone);
2901 */
2902                         return clone.firstChild;
2903                 },
2904
2905                 /**
2906                  * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
2907                  * go above the point of this root node.
2908                  *
2909                  * @method getRoot
2910                  * @return {Element} Root element for the utility class.
2911                  */
2912                 getRoot: function() {
2913                         var self = this;
2914
2915                         return self.get(self.settings.root_element) || self.doc.body;
2916                 },
2917
2918                 /**
2919                  * Returns the viewport of the window.
2920                  *
2921                  * @method getViewPort
2922                  * @param {Window} win Optional window to get viewport of.
2923                  * @return {Object} Viewport object with fields x, y, w and h.
2924                  */
2925                 getViewPort: function(win) {
2926                         var doc, rootElm;
2927
2928                         win = !win ? this.win : win;
2929                         doc = win.document;
2930                         rootElm = this.boxModel ? doc.documentElement : doc.body;
2931
2932                         // Returns viewport size excluding scrollbars
2933                         return {
2934                                 x: win.pageXOffset || rootElm.scrollLeft,
2935                                 y: win.pageYOffset || rootElm.scrollTop,
2936                                 w: win.innerWidth || rootElm.clientWidth,
2937                                 h: win.innerHeight || rootElm.clientHeight
2938                         };
2939                 },
2940
2941                 /**
2942                  * Returns the rectangle for a specific element.
2943                  *
2944                  * @method getRect
2945                  * @param {Element/String} elm Element object or element ID to get rectangle from.
2946                  * @return {object} Rectangle for specified element object with x, y, w, h fields.
2947                  */
2948                 getRect: function(elm) {
2949                         var self = this, pos, size;
2950
2951                         elm = self.get(elm);
2952                         pos = self.getPos(elm);
2953                         size = self.getSize(elm);
2954
2955                         return {
2956                                 x: pos.x, y: pos.y,
2957                                 w: size.w, h: size.h
2958                         };
2959                 },
2960
2961                 /**
2962                  * Returns the size dimensions of the specified element.
2963                  *
2964                  * @method getSize
2965                  * @param {Element/String} elm Element object or element ID to get rectangle from.
2966                  * @return {object} Rectangle for specified element object with w, h fields.
2967                  */
2968                 getSize: function(elm) {
2969                         var self = this, w, h;
2970
2971                         elm = self.get(elm);
2972                         w = self.getStyle(elm, 'width');
2973                         h = self.getStyle(elm, 'height');
2974
2975                         // Non pixel value, then force offset/clientWidth
2976                         if (w.indexOf('px') === -1) {
2977                                 w = 0;
2978                         }
2979
2980                         // Non pixel value, then force offset/clientWidth
2981                         if (h.indexOf('px') === -1) {
2982                                 h = 0;
2983                         }
2984
2985                         return {
2986                                 w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
2987                                 h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
2988                         };
2989                 },
2990
2991                 /**
2992                  * Returns a node by the specified selector function. This function will
2993                  * loop through all parent nodes and call the specified function for each node.
2994                  * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
2995                  * and the node it found will be returned.
2996                  *
2997                  * @method getParent
2998                  * @param {Node/String} node DOM node to search parents on or ID string.
2999                  * @param {function} selector Selection function or CSS selector to execute on each node.
3000                  * @param {Node} root Optional root element, never go below this point.
3001                  * @return {Node} DOM Node or null if it wasn't found.
3002                  */
3003                 getParent: function(node, selector, root) {
3004                         return this.getParents(node, selector, root, false);
3005                 },
3006
3007                 /**
3008                  * Returns a node list of all parents matching the specified selector function or pattern.
3009                  * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
3010                  *
3011                  * @method getParents
3012                  * @param {Node/String} node DOM node to search parents on or ID string.
3013                  * @param {function} selector Selection function to execute on each node or CSS pattern.
3014                  * @param {Node} root Optional root element, never go below this point.
3015                  * @return {Array} Array of nodes or null if it wasn't found.
3016                  */
3017                 getParents: function(node, selector, root, collect) {
3018                         var self = this, selectorVal, result = [];
3019
3020                         node = self.get(node);
3021                         collect = collect === undefined;
3022
3023                         // Default root on inline mode
3024                         root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
3025
3026                         // Wrap node name as func
3027                         if (is(selector, 'string')) {
3028                                 selectorVal = selector;
3029
3030                                 if (selector === '*') {
3031                                         selector = function(node) {return node.nodeType == 1;};
3032                                 } else {
3033                                         selector = function(node) {
3034                                                 return self.is(node, selectorVal);
3035                                         };
3036                                 }
3037                         }
3038
3039                         while (node) {
3040                                 if (node == root || !node.nodeType || node.nodeType === 9) {
3041                                         break;
3042                                 }
3043
3044                                 if (!selector || selector(node)) {
3045                                         if (collect) {
3046                                                 result.push(node);
3047                                         } else {
3048                                                 return node;
3049                                         }
3050                                 }
3051
3052                                 node = node.parentNode;
3053                         }
3054
3055                         return collect ? result : null;
3056                 },
3057
3058                 /**
3059                  * Returns the specified element by ID or the input element if it isn't a string.
3060                  *
3061                  * @method get
3062                  * @param {String/Element} n Element id to look for or element to just pass though.
3063                  * @return {Element} Element matching the specified id or null if it wasn't found.
3064                  */
3065                 get: function(elm) {
3066                         var name;
3067
3068                         if (elm && this.doc && typeof(elm) == 'string') {
3069                                 name = elm;
3070                                 elm = this.doc.getElementById(elm);
3071
3072                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3073                                 if (elm && elm.id !== name) {
3074                                         return this.doc.getElementsByName(name)[1];
3075                                 }
3076                         }
3077
3078                         return elm;
3079                 },
3080
3081                 /**
3082                  * Returns the next node that matches selector or function
3083                  *
3084                  * @method getNext
3085                  * @param {Node} node Node to find siblings from.
3086                  * @param {String/function} selector Selector CSS expression or function.
3087                  * @return {Node} Next node item matching the selector or null if it wasn't found.
3088                  */
3089                 getNext: function(node, selector) {
3090                         return this._findSib(node, selector, 'nextSibling');
3091                 },
3092
3093                 /**
3094                  * Returns the previous node that matches selector or function
3095                  *
3096                  * @method getPrev
3097                  * @param {Node} node Node to find siblings from.
3098                  * @param {String/function} selector Selector CSS expression or function.
3099                  * @return {Node} Previous node item matching the selector or null if it wasn't found.
3100                  */
3101                 getPrev: function(node, selector) {
3102                         return this._findSib(node, selector, 'previousSibling');
3103                 },
3104
3105                 // #ifndef jquery
3106
3107                 /**
3108                  * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
3109                  * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
3110                  * on more complex patterns.
3111                  *
3112                  * @method select
3113                  * @param {String} selector CSS level 3 pattern to select/find elements by.
3114                  * @param {Object} scope Optional root element/scope element to search in.
3115                  * @return {Array} Array with all matched elements.
3116                  * @example
3117                  * // Adds a class to all paragraphs in the currently active editor
3118                  * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
3119                  *
3120                  * // Adds a class to all spans that have the test class in the currently active editor
3121                  * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
3122                  */
3123                 select: function(selector, scope) {
3124                         var self = this;
3125
3126                         //Sizzle.selectors.cacheLength = 0;
3127                         return Sizzle(selector, self.get(scope) || self.get(self.settings.root_element) || self.doc, []);
3128                 },
3129
3130                 /**
3131                  * Returns true/false if the specified element matches the specified css pattern.
3132                  *
3133                  * @method is
3134                  * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
3135                  * @param {String} selector CSS pattern to match the element against.
3136                  */
3137                 is: function(elm, selector) {
3138                         var i;
3139
3140                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
3141                         if (elm.length === undefined) {
3142                                 // Simple all selector
3143                                 if (selector === '*') {
3144                                         return elm.nodeType == 1;
3145                                 }
3146
3147                                 // Simple selector just elements
3148                                 if (simpleSelectorRe.test(selector)) {
3149                                         selector = selector.toLowerCase().split(/,/);
3150                                         elm = elm.nodeName.toLowerCase();
3151
3152                                         for (i = selector.length - 1; i >= 0; i--) {
3153                                                 if (selector[i] == elm) {
3154                                                         return true;
3155                                                 }
3156                                         }
3157
3158                                         return false;
3159                                 }
3160                         }
3161
3162                         // Is non element
3163                         if (elm.nodeType && elm.nodeType != 1) {
3164                                 return false;
3165                         }
3166
3167                         return Sizzle.matches(selector, elm.nodeType ? [elm] : elm).length > 0;
3168                 },
3169
3170                 // #endif
3171
3172                 /**
3173                  * Adds the specified element to another element or elements.
3174                  *
3175                  * @method add
3176                  * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
3177                  * @param {String/Element} name Name of new element to add or existing element to add.
3178                  * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
3179                  * @param {String} html Optional inner HTML contents to add for each element.
3180                  * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
3181                  * were passed in.
3182                  * @example
3183                  * // Adds a new paragraph to the end of the active editor
3184                  * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
3185                  */
3186                 add: function(parentElm, name, attrs, html, create) {
3187                         var self = this;
3188
3189                         return this.run(parentElm, function(parentElm) {
3190                                 var newElm;
3191
3192                                 newElm = is(name, 'string') ? self.doc.createElement(name) : name;
3193                                 self.setAttribs(newElm, attrs);
3194
3195                                 if (html) {
3196                                         if (html.nodeType) {
3197                                                 newElm.appendChild(html);
3198                                         } else {
3199                                                 self.setHTML(newElm, html);
3200                                         }
3201                                 }
3202
3203                                 return !create ? parentElm.appendChild(newElm) : newElm;
3204                         });
3205                 },
3206
3207                 /**
3208                  * Creates a new element.
3209                  *
3210                  * @method create
3211                  * @param {String} name Name of new element.
3212                  * @param {Object} attrs Optional object name/value collection with element attributes.
3213                  * @param {String} html Optional HTML string to set as inner HTML of the element.
3214                  * @return {Element} HTML DOM node element that got created.
3215                  * @example
3216                  * // Adds an element where the caret/selection is in the active editor
3217                  * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
3218                  * tinymce.activeEditor.selection.setNode(el);
3219                  */
3220                 create: function(name, attrs, html) {
3221                         return this.add(this.doc.createElement(name), name, attrs, html, 1);
3222                 },
3223
3224                 /**
3225                  * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
3226                  *
3227                  * @method createHTML
3228                  * @param {String} name Name of new element.
3229                  * @param {Object} attrs Optional object name/value collection with element attributes.
3230                  * @param {String} html Optional HTML string to set as inner HTML of the element.
3231                  * @return {String} String with new HTML element, for example: <a href="#">test</a>.
3232                  * @example
3233                  * // Creates a html chunk and inserts it at the current selection/caret location
3234                  * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
3235                  */
3236                 createHTML: function(name, attrs, html) {
3237                         var outHtml = '', key;
3238
3239                         outHtml += '<' + name;
3240
3241                         for (key in attrs) {
3242                                 if (attrs.hasOwnProperty(key) && attrs[key] !== null) {
3243                                         outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
3244                                 }
3245                         }
3246
3247                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3248                         if (typeof(html) != "undefined") {
3249                                 return outHtml + '>' + html + '</' + name + '>';
3250                         }
3251
3252                         return outHtml + ' />';
3253                 },
3254
3255                 /**
3256                  * Creates a document fragment out of the specified HTML string.
3257                  *
3258                  * @method createFragment
3259                  * @param {String} html Html string to create fragment from.
3260                  * @return {DocumentFragment} Document fragment node.
3261                  */
3262                 createFragment: function(html) {
3263                         var frag, node, doc = this.doc, container;
3264
3265                         container = doc.createElement("div");
3266                         frag = doc.createDocumentFragment();
3267
3268                         if (html) {
3269                                 container.innerHTML = html;
3270                         }
3271
3272                         while ((node = container.firstChild)) {
3273                                 frag.appendChild(node);
3274                         }
3275
3276                         return frag;
3277                 },
3278
3279                 /**
3280                  * Removes/deletes the specified element(s) from the DOM.
3281                  *
3282                  * @method remove
3283                  * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
3284                  * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be
3285                  * placed at the location of the removed element.
3286                  * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
3287                  * were passed in.
3288                  * @example
3289                  * // Removes all paragraphs in the active editor
3290                  * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
3291                  *
3292                  * // Removes an element by id in the document
3293                  * tinymce.DOM.remove('mydiv');
3294                  */
3295                 remove: function(node, keep_children) {
3296                         return this.run(node, function(node) {
3297                                 var child, parent = node.parentNode;
3298
3299                                 if (!parent) {
3300                                         return null;
3301                                 }
3302
3303                                 if (keep_children) {
3304                                         while ((child = node.firstChild)) {
3305                                                 // IE 8 will crash if you don't remove completely empty text nodes
3306                                                 if (!isIE || child.nodeType !== 3 || child.nodeValue) {
3307                                                         parent.insertBefore(child, node);
3308                                                 } else {
3309                                                         node.removeChild(child);
3310                                                 }
3311                                         }
3312                                 }
3313
3314                                 return parent.removeChild(node);
3315                         });
3316                 },
3317
3318                 /**
3319                  * Sets the CSS style value on a HTML element. The name can be a camelcase string
3320                  * or the CSS style name like background-color.
3321                  *
3322                  * @method setStyle
3323                  * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
3324                  * @param {String} na Name of the style value to set.
3325                  * @param {String} v Value to set on the style.
3326                  * @example
3327                  * // Sets a style value on all paragraphs in the currently active editor
3328                  * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
3329                  *
3330                  * // Sets a style value to an element by id in the current document
3331                  * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
3332                  */
3333                 setStyle: function(elm, name, value) {
3334                         return this.run(elm, function(elm) {
3335                                 var self = this, style, key;
3336
3337                                 if (name) {
3338                                         if (typeof(name) === 'string') {
3339                                                 style = elm.style;
3340
3341                                                 // Camelcase it, if needed
3342                                                 name = name.replace(/-(\D)/g, function(a, b) {
3343                                                         return b.toUpperCase();
3344                                                 });
3345
3346                                                 // Default px suffix on these
3347                                                 if (typeof(value) === 'number' && !numericCssMap[name]) {
3348                                                         value += 'px';
3349                                                 }
3350
3351                                                 // IE specific opacity
3352                                                 if (name === "opacity" && elm.runtimeStyle && typeof(elm.runtimeStyle.opacity) === "undefined") {
3353                                                         style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
3354                                                 }
3355
3356                                                 if (name == "float") {
3357                                                         // Old IE vs modern browsers
3358                                                         name = "cssFloat" in elm.style ? "cssFloat" : "styleFloat";
3359                                                 }
3360
3361                                                 try {
3362                                                         style[name] = value;
3363                                                 } catch (ex) {
3364                                                         // Ignore IE errors
3365                                                 }
3366
3367                                                 // Force update of the style data
3368                                                 if (self.settings.update_styles) {
3369                                                         elm.removeAttribute('data-mce-style');
3370                                                 }
3371                                         } else {
3372                                                 for (key in name) {
3373                                                         self.setStyle(elm, key, name[key]);
3374                                                 }
3375                                         }
3376                                 }
3377                         });
3378                 },
3379
3380                 /**
3381                  * Returns the current style or runtime/computed value of an element.
3382                  *
3383                  * @method getStyle
3384                  * @param {String/Element} elm HTML element or element id string to get style from.
3385                  * @param {String} name Style name to return.
3386                  * @param {Boolean} computed Computed style.
3387                  * @return {String} Current style or computed style value of an element.
3388                  */
3389                 getStyle: function(elm, name, computed) {
3390                         elm = this.get(elm);
3391
3392                         if (!elm) {
3393                                 return;
3394                         }
3395
3396                         // W3C
3397                         if (this.doc.defaultView && computed) {
3398                                 // Remove camelcase
3399                                 name = name.replace(/[A-Z]/g, function(a){
3400                                         return '-' + a;
3401                                 });
3402
3403                                 try {
3404                                         return this.doc.defaultView.getComputedStyle(elm, null).getPropertyValue(name);
3405                                 } catch (ex) {
3406                                         // Old safari might fail
3407                                         return null;
3408                                 }
3409                         }
3410
3411                         // Camelcase it, if needed
3412                         name = name.replace(/-(\D)/g, function(a, b) {
3413                                 return b.toUpperCase();
3414                         });
3415
3416                         if (name == 'float') {
3417                                 name = isIE ? 'styleFloat' : 'cssFloat';
3418                         }
3419
3420                         // IE & Opera
3421                         if (elm.currentStyle && computed) {
3422                                 return elm.currentStyle[name];
3423                         }
3424
3425                         return elm.style ? elm.style[name] : undefined;
3426                 },
3427
3428                 /**
3429                  * Sets multiple styles on the specified element(s).
3430                  *
3431                  * @method setStyles
3432                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
3433                  * @param {Object} o Name/Value collection of style items to add to the element(s).
3434                  * @example
3435                  * // Sets styles on all paragraphs in the currently active editor
3436                  * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
3437                  *
3438                  * // Sets styles to an element by id in the current document
3439                  * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
3440                  */
3441                 setStyles: function(elm, styles) {
3442                         this.setStyle(elm, styles);
3443                 },
3444
3445                 css: function(elm, name, value) {
3446                         this.setStyle(elm, name, value);
3447                 },
3448
3449                 /**
3450                  * Removes all attributes from an element or elements.
3451                  *
3452                  * @method removeAllAttribs
3453                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
3454                  */
3455                 removeAllAttribs: function(e) {
3456                         return this.run(e, function(e) {
3457                                 var i, attrs = e.attributes;
3458                                 for (i = attrs.length - 1; i >= 0; i--) {
3459                                         e.removeAttributeNode(attrs.item(i));
3460                                 }
3461                         });
3462                 },
3463
3464                 /**
3465                  * Sets the specified attribute of an element or elements.
3466                  *
3467                  * @method setAttrib
3468                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
3469                  * @param {String} n Name of attribute to set.
3470                  * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
3471                  * @example
3472                  * // Sets class attribute on all paragraphs in the active editor
3473                  * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
3474                  *
3475                  * // Sets class attribute on a specific element in the current page
3476                  * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
3477                  */
3478                 setAttrib: function(e, n, v) {
3479                         var t = this;
3480
3481                         // What's the point
3482                         if (!e || !n) {
3483                                 return;
3484                         }
3485
3486                         return this.run(e, function(e) {
3487                                 var s = t.settings;
3488                                 var originalValue = e.getAttribute(n);
3489                                 if (v !== null) {
3490                                         switch (n) {
3491                                                 case "style":
3492                                                         if (!is(v, 'string')) {
3493                                                                 each(v, function(v, n) {
3494                                                                         t.setStyle(e, n, v);
3495                                                                 });
3496
3497                                                                 return;
3498                                                         }
3499
3500                                                         // No mce_style for elements with these since they might get resized by the user
3501                                                         if (s.keep_values) {
3502                                                                 if (v) {
3503                                                                         e.setAttribute('data-mce-style', v, 2);
3504                                                                 } else {
3505                                                                         e.removeAttribute('data-mce-style', 2);
3506                                                                 }
3507                                                         }
3508
3509                                                         e.style.cssText = v;
3510                                                         break;
3511
3512                                                 case "class":
3513                                                         e.className = v || ''; // Fix IE null bug
3514                                                         break;
3515
3516                                                 case "src":
3517                                                 case "href":
3518                                                         if (s.keep_values) {
3519                                                                 if (s.url_converter) {
3520                                                                         v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3521                                                                 }
3522
3523                                                                 t.setAttrib(e, 'data-mce-' + n, v, 2);
3524                                                         }
3525
3526                                                         break;
3527
3528                                                 case "shape":
3529                                                         e.setAttribute('data-mce-style', v);
3530                                                         break;
3531                                         }
3532                                 }
3533                                 if (is(v) && v !== null && v.length !== 0) {
3534                                         e.setAttribute(n, '' + v, 2);
3535                                 } else {
3536                                         e.removeAttribute(n, 2);
3537                                 }
3538
3539                                 // fire onChangeAttrib event for attributes that have changed
3540                                 if (originalValue != v && s.onSetAttrib) {
3541                                         s.onSetAttrib({attrElm: e, attrName: n, attrValue: v});
3542                                 }
3543                         });
3544                 },
3545
3546                 /**
3547                  * Sets two or more specified attributes of an element or elements.
3548                  *
3549                  * @method setAttribs
3550                  * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
3551                  * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
3552                  * @example
3553                  * // Sets class and title attributes on all paragraphs in the active editor
3554                  * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
3555                  *
3556                  * // Sets class and title attributes on a specific element in the current page
3557                  * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
3558                  */
3559                 setAttribs: function(elm, attrs) {
3560                         var self = this;
3561
3562                         return this.run(elm, function(elm) {
3563                                 each(attrs, function(value, name) {
3564                                         self.setAttrib(elm, name, value);
3565                                 });
3566                         });
3567                 },
3568
3569                 /**
3570                  * Returns the specified attribute by name.
3571                  *
3572                  * @method getAttrib
3573                  * @param {String/Element} elm Element string id or DOM element to get attribute from.
3574                  * @param {String} name Name of attribute to get.
3575                  * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
3576                  * @return {String} Attribute value string, default value or null if the attribute wasn't found.
3577                  */
3578                 getAttrib: function(elm, name, defaultVal) {
3579                         var value, self = this, undef;
3580
3581                         elm = self.get(elm);
3582
3583                         if (!elm || elm.nodeType !== 1) {
3584                                 return defaultVal === undef ? false : defaultVal;
3585                         }
3586
3587                         if (!is(defaultVal)) {
3588                                 defaultVal = '';
3589                         }
3590
3591                         // Try the mce variant for these
3592                         if (/^(src|href|style|coords|shape)$/.test(name)) {
3593                                 value = elm.getAttribute("data-mce-" + name);
3594
3595                                 if (value) {
3596                                         return value;
3597                                 }
3598                         }
3599
3600                         if (isIE && self.props[name]) {
3601                                 value = elm[self.props[name]];
3602                                 value = value && value.nodeValue ? value.nodeValue : value;
3603                         }
3604
3605                         if (!value) {
3606                                 value = elm.getAttribute(name, 2);
3607                         }
3608
3609                         // Check boolean attribs
3610                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(name)) {
3611                                 if (elm[self.props[name]] === true && value === '') {
3612                                         return name;
3613                                 }
3614
3615                                 return value ? name : '';
3616                         }
3617
3618                         // Inner input elements will override attributes on form elements
3619                         if (elm.nodeName === "FORM" && elm.getAttributeNode(name)) {
3620                                 return elm.getAttributeNode(name).nodeValue;
3621                         }
3622
3623                         if (name === 'style') {
3624                                 value = value || elm.style.cssText;
3625
3626                                 if (value) {
3627                                         value = self.serializeStyle(self.parseStyle(value), elm.nodeName);
3628
3629                                         if (self.settings.keep_values) {
3630                                                 elm.setAttribute('data-mce-style', value);
3631                                         }
3632                                 }
3633                         }
3634
3635                         // Remove Apple and WebKit stuff
3636                         if (isWebKit && name === "class" && value) {
3637                                 value = value.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3638                         }
3639
3640                         // Handle IE issues
3641                         if (isIE) {
3642                                 switch (name) {
3643                                         case 'rowspan':
3644                                         case 'colspan':
3645                                                 // IE returns 1 as default value
3646                                                 if (value === 1) {
3647                                                         value = '';
3648                                                 }
3649
3650                                                 break;
3651
3652                                         case 'size':
3653                                                 // IE returns +0 as default value for size
3654                                                 if (value === '+0' || value === 20 || value === 0) {
3655                                                         value = '';
3656                                                 }
3657
3658                                                 break;
3659
3660                                         case 'width':
3661                                         case 'height':
3662                                         case 'vspace':
3663                                         case 'checked':
3664                                         case 'disabled':
3665                                         case 'readonly':
3666                                                 if (value === 0) {
3667                                                         value = '';
3668                                                 }
3669
3670                                                 break;
3671
3672                                         case 'hspace':
3673                                                 // IE returns -1 as default value
3674                                                 if (value === -1) {
3675                                                         value = '';
3676                                                 }
3677
3678                                                 break;
3679
3680                                         case 'maxlength':
3681                                         case 'tabindex':
3682                                                 // IE returns default value
3683                                                 if (value === 32768 || value === 2147483647 || value === '32768') {
3684                                                         value = '';
3685                                                 }
3686
3687                                                 break;
3688
3689                                         case 'multiple':
3690                                         case 'compact':
3691                                         case 'noshade':
3692                                         case 'nowrap':
3693                                                 if (value === 65535) {
3694                                                         return name;
3695                                                 }
3696
3697                                                 return defaultVal;
3698
3699                                         case 'shape':
3700                                                 value = value.toLowerCase();
3701                                                 break;
3702
3703                                         default:
3704                                                 // IE has odd anonymous function for event attributes
3705                                                 if (name.indexOf('on') === 0 && value) {
3706                                                         value = ('' + value).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
3707                                                 }
3708                                 }
3709                         }
3710
3711                         return (value !== undef && value !== null && value !== '') ? '' + value : defaultVal;
3712                 },
3713
3714                 /**
3715                  * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
3716                  *
3717                  * @method getPos
3718                  * @param {Element/String} n HTML element or element id to get x, y position from.
3719                  * @param {Element} ro Optional root element to stop calculations at.
3720                  * @return {object} Absolute position of the specified element object with x, y fields.
3721                  */
3722                 getPos: function(elm, rootElm) {
3723                         var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos;
3724
3725                         elm = self.get(elm);
3726                         rootElm = rootElm || doc.body;
3727
3728                         if (elm) {
3729                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
3730                                 if (rootElm === doc.body && elm.getBoundingClientRect) {
3731                                         pos = elm.getBoundingClientRect();
3732                                         rootElm = self.boxModel ? doc.documentElement : doc.body;
3733
3734                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
3735                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
3736                                         x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientTop;
3737                                         y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientLeft;
3738
3739                                         return {x: x, y: y};
3740                                 }
3741
3742                                 offsetParent = elm;
3743                                 while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
3744                                         x += offsetParent.offsetLeft || 0;
3745                                         y += offsetParent.offsetTop || 0;
3746                                         offsetParent = offsetParent.offsetParent;
3747                                 }
3748
3749                                 offsetParent = elm.parentNode;
3750                                 while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
3751                                         x -= offsetParent.scrollLeft || 0;
3752                                         y -= offsetParent.scrollTop || 0;
3753                                         offsetParent = offsetParent.parentNode;
3754                                 }
3755                         }
3756
3757                         return {x: x, y: y};
3758                 },
3759
3760                 /**
3761                  * Parses the specified style value into an object collection. This parser will also
3762                  * merge and remove any redundant items that browsers might have added. It will also convert non-hex
3763                  * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
3764                  *
3765                  * @method parseStyle
3766                  * @param {String} cssText Style value to parse, for example: border:1px solid red;.
3767                  * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
3768                  */
3769                 parseStyle: function(cssText) {
3770                         return this.styles.parse(cssText);
3771                 },
3772
3773                 /**
3774                  * Serializes the specified style object into a string.
3775                  *
3776                  * @method serializeStyle
3777                  * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
3778                  * @param {String} name Optional element name.
3779                  * @return {String} String representation of the style object, for example: border: 1px solid red.
3780                  */
3781                 serializeStyle: function(styles, name) {
3782                         return this.styles.serialize(styles, name);
3783                 },
3784
3785                 /**
3786                  * Adds a style element at the top of the document with the specified cssText content.
3787                  *
3788                  * @method addStyle
3789                  * @param {String} cssText CSS Text style to add to top of head of document.
3790                  */
3791                 addStyle: function(cssText) {
3792                         var self = this, doc = self.doc, head, styleElm;
3793
3794                         // Prevent inline from loading the same styles twice
3795                         if (self !== DOMUtils.DOM && doc === document) {
3796                                 var addedStyles = DOMUtils.DOM.addedStyles;
3797
3798                                 addedStyles = addedStyles || [];
3799                                 if (addedStyles[cssText]) {
3800                                         return;
3801                                 }
3802
3803                                 addedStyles[cssText] = true;
3804                                 DOMUtils.DOM.addedStyles = addedStyles;
3805                         }
3806
3807                         // Create style element if needed
3808                         styleElm = doc.getElementById('mceDefaultStyles');
3809                         if (!styleElm) {
3810                                 styleElm = doc.createElement('style');
3811                                 styleElm.id = 'mceDefaultStyles';
3812                                 styleElm.type = 'text/css';
3813
3814                                 head = doc.getElementsByTagName('head')[0];
3815                                 if (head.firstChild) {
3816                                         head.insertBefore(styleElm, head.firstChild);
3817                                 } else {
3818                                         head.appendChild(styleElm);
3819                                 }
3820                         }
3821
3822                         // Append style data to old or new style element
3823                         if (styleElm.styleSheet) {
3824                                 styleElm.styleSheet.cssText += cssText;
3825                         } else {
3826                                 styleElm.appendChild(doc.createTextNode(cssText));
3827                         }
3828                 },
3829
3830                 /**
3831                  * Imports/loads the specified CSS file into the document bound to the class.
3832                  *
3833                  * @method loadCSS
3834                  * @param {String} u URL to CSS file to load.
3835                  * @example
3836                  * // Loads a CSS file dynamically into the current document
3837                  * tinymce.DOM.loadCSS('somepath/some.css');
3838                  *
3839                  * // Loads a CSS file into the currently active editor instance
3840                  * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
3841                  *
3842                  * // Loads a CSS file into an editor instance by id
3843                  * tinymce.get('someid').dom.loadCSS('somepath/some.css');
3844                  *
3845                  * // Loads multiple CSS files into the current document
3846                  * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
3847                  */
3848                 loadCSS: function(url) {
3849                         var self = this, doc = self.doc, head;
3850
3851                         // Prevent inline from loading the same CSS file twice
3852                         if (self !== DOMUtils.DOM && doc === document) {
3853                                 DOMUtils.DOM.loadCSS(url);
3854                                 return;
3855                         }
3856
3857                         if (!url) {
3858                                 url = '';
3859                         }
3860
3861                         head = doc.getElementsByTagName('head')[0];
3862
3863                         each(url.split(','), function(url) {
3864                                 var link;
3865
3866                                 if (self.files[url]) {
3867                                         return;
3868                                 }
3869
3870                                 self.files[url] = true;
3871                                 link = self.create('link', {rel: 'stylesheet', href: url});
3872
3873                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
3874                                 // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
3875                                 // It's ugly but it seems to work fine.
3876                                 if (isIE && doc.documentMode && doc.recalc) {
3877                                         link.onload = function() {
3878                                                 if (doc.recalc) {
3879                                                         doc.recalc();
3880                                                 }
3881
3882                                                 link.onload = null;
3883                                         };
3884                                 }
3885
3886                                 head.appendChild(link);
3887                         });
3888                 },
3889
3890                 /**
3891                  * Adds a class to the specified element or elements.
3892                  *
3893                  * @method addClass
3894                  * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
3895                  * @param {String} cls Class name to add to each element.
3896                  * @return {String/Array} String with new class value or array with new class values for all elements.
3897                  * @example
3898                  * // Adds a class to all paragraphs in the active editor
3899                  * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
3900                  *
3901                  * // Adds a class to a specific element in the current page
3902                  * tinymce.DOM.addClass('mydiv', 'myclass');
3903                  */
3904                 addClass: function(elm, cls) {
3905                         return this.run(elm, function(elm) {
3906                                 var clsVal;
3907
3908                                 if (!cls) {
3909                                         return 0;
3910                                 }
3911
3912                                 if (this.hasClass(elm, cls)) {
3913                                         return elm.className;
3914                                 }
3915
3916                                 clsVal = this.removeClass(elm, cls);
3917                                 elm.className = clsVal = (clsVal !== '' ? (clsVal + ' ') : '') + cls;
3918
3919                                 return clsVal;
3920                         });
3921                 },
3922
3923                 /**
3924                  * Removes a class from the specified element or elements.
3925                  *
3926                  * @method removeClass
3927                  * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
3928                  * @param {String} cls Class name to remove from each element.
3929                  * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
3930                  * were passed in.
3931                  * @example
3932                  * // Removes a class from all paragraphs in the active editor
3933                  * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
3934                  *
3935                  * // Removes a class from a specific element in the current page
3936                  * tinymce.DOM.removeClass('mydiv', 'myclass');
3937                  */
3938                 removeClass: function(elm, cls) {
3939                         var self = this, re;
3940
3941                         return self.run(elm, function(elm) {
3942                                 var val;
3943
3944                                 if (self.hasClass(elm, cls)) {
3945                                         if (!re) {
3946                                                 re = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", "g");
3947                                         }
3948
3949                                         val = elm.className.replace(re, ' ');
3950                                         val = trim(val != ' ' ? val : '');
3951
3952                                         elm.className = val;
3953
3954                                         // Empty class attr
3955                                         if (!val) {
3956                                                 elm.removeAttribute('class');
3957                                                 elm.removeAttribute('className');
3958                                         }
3959
3960                                         return val;
3961                                 }
3962
3963                                 return elm.className;
3964                         });
3965                 },
3966
3967                 /**
3968                  * Returns true if the specified element has the specified class.
3969                  *
3970                  * @method hasClass
3971                  * @param {String/Element} n HTML element or element id string to check CSS class on.
3972                  * @param {String} c CSS class to check for.
3973                  * @return {Boolean} true/false if the specified element has the specified class.
3974                  */
3975                 hasClass: function(elm, cls) {
3976                         elm = this.get(elm);
3977
3978                         if (!elm || !cls) {
3979                                 return false;
3980                         }
3981
3982                         return (' ' + elm.className + ' ').indexOf(' ' + cls + ' ') !== -1;
3983                 },
3984
3985                 /**
3986                  * Toggles the specified class on/off.
3987                  *
3988                  * @method toggleClass
3989                  * @param {Element} elm Element to toggle class on.
3990                  * @param {[type]} cls Class to toggle on/off.
3991                  * @param {[type]} state Optional state to set.
3992                  */
3993                 toggleClass: function(elm, cls, state) {
3994                         state = state === undefined ? !this.hasClass(elm, cls) : state;
3995
3996                         if (this.hasClass(elm, cls) !== state) {
3997                                 if (state) {
3998                                         this.addClass(elm, cls);
3999                                 } else {
4000                                         this.removeClass(elm, cls);
4001                                 }
4002                         }
4003                 },
4004
4005                 /**
4006                  * Shows the specified element(s) by ID by setting the "display" style.
4007                  *
4008                  * @method show
4009                  * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
4010                  */
4011                 show: function(elm) {
4012                         return this.setStyle(elm, 'display', 'block');
4013                 },
4014
4015                 /**
4016                  * Hides the specified element(s) by ID by setting the "display" style.
4017                  *
4018                  * @method hide
4019                  * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
4020                  * @example
4021                  * // Hides an element by id in the document
4022                  * tinymce.DOM.hide('myid');
4023                  */
4024                 hide: function(elm) {
4025                         return this.setStyle(elm, 'display', 'none');
4026                 },
4027
4028                 /**
4029                  * Returns true/false if the element is hidden or not by checking the "display" style.
4030                  *
4031                  * @method isHidden
4032                  * @param {String/Element} e Id or element to check display state on.
4033                  * @return {Boolean} true/false if the element is hidden or not.
4034                  */
4035                 isHidden: function(elm) {
4036                         elm = this.get(elm);
4037
4038                         return !elm || elm.style.display == 'none' || this.getStyle(elm, 'display') == 'none';
4039                 },
4040
4041                 /**
4042                  * Returns a unique id. This can be useful when generating elements on the fly.
4043                  * This method will not check if the element already exists.
4044                  *
4045                  * @method uniqueId
4046                  * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
4047                  * @return {String} Unique id.
4048                  */
4049                 uniqueId: function(prefix) {
4050                         return (!prefix ? 'mce_' : prefix) + (this.counter++);
4051                 },
4052
4053                 /**
4054                  * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
4055                  * URLs will get converted, hex color values fixed etc. Check processHTML for details.
4056                  *
4057                  * @method setHTML
4058                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside of.
4059                  * @param {String} h HTML content to set as inner HTML of the element.
4060                  * @example
4061                  * // Sets the inner HTML of all paragraphs in the active editor
4062                  * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
4063                  *
4064                  * // Sets the inner HTML of an element by id in the document
4065                  * tinymce.DOM.setHTML('mydiv', 'some inner html');
4066                  */
4067                 setHTML: function(element, html) {
4068                         var self = this;
4069
4070                         return self.run(element, function(element) {
4071                                 if (isIE) {
4072                                         // Remove all child nodes, IE keeps empty text nodes in DOM
4073                                         while (element.firstChild) {
4074                                                 element.removeChild(element.firstChild);
4075                                         }
4076
4077                                         try {
4078                                                 // IE will remove comments from the beginning
4079                                                 // unless you padd the contents with something
4080                                                 element.innerHTML = '<br />' + html;
4081                                                 element.removeChild(element.firstChild);
4082                                         } catch (ex) {
4083                                                 // IE sometimes produces an unknown runtime error on innerHTML if it's a block element
4084                                                 // within a block element for example a div inside a p
4085                                                 // This seems to fix this problem
4086
4087                                                 // Create new div with HTML contents and a BR in front to keep comments
4088                                                 var newElement = self.create('div');
4089                                                 newElement.innerHTML = '<br />' + html;
4090
4091                                                 // Add all children from div to target
4092                                                 each (grep(newElement.childNodes), function(node, i) {
4093                                                         // Skip br element
4094                                                         if (i && element.canHaveHTML) {
4095                                                                 element.appendChild(node);
4096                                                         }
4097                                                 });
4098                                         }
4099                                 } else {
4100                                         element.innerHTML = html;
4101                                 }
4102
4103                                 return html;
4104                         });
4105                 },
4106
4107                 /**
4108                  * Returns the outer HTML of an element.
4109                  *
4110                  * @method getOuterHTML
4111                  * @param {String/Element} elm Element ID or element object to get outer HTML from.
4112                  * @return {String} Outer HTML string.
4113                  * @example
4114                  * tinymce.DOM.getOuterHTML(editorElement);
4115                  * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
4116                  */
4117                 getOuterHTML: function(elm) {
4118                         var doc, self = this;
4119
4120                         elm = self.get(elm);
4121
4122                         if (!elm) {
4123                                 return null;
4124                         }
4125
4126                         if (elm.nodeType === 1 && self.hasOuterHTML) {
4127                                 return elm.outerHTML;
4128                         }
4129
4130                         doc = (elm.ownerDocument || self.doc).createElement("body");
4131                         doc.appendChild(elm.cloneNode(true));
4132
4133                         return doc.innerHTML;
4134                 },
4135
4136                 /**
4137                  * Sets the specified outer HTML on an element or elements.
4138                  *
4139                  * @method setOuterHTML
4140                  * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
4141                  * @param {Object} html HTML code to set as outer value for the element.
4142                  * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
4143                  * @example
4144                  * // Sets the outer HTML of all paragraphs in the active editor
4145                  * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
4146                  *
4147                  * // Sets the outer HTML of an element by id in the document
4148                  * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
4149                  */
4150                 setOuterHTML: function(elm, html, doc) {
4151                         var self = this;
4152
4153                         return self.run(elm, function(elm) {
4154                                 function set() {
4155                                         var node, tempElm;
4156
4157                                         tempElm = doc.createElement("body");
4158                                         tempElm.innerHTML = html;
4159
4160                                         node = tempElm.lastChild;
4161                                         while (node) {
4162                                                 self.insertAfter(node.cloneNode(true), elm);
4163                                                 node = node.previousSibling;
4164                                         }
4165
4166                                         self.remove(elm);
4167                                 }
4168
4169                                 // Only set HTML on elements
4170                                 if (elm.nodeType == 1) {
4171                                         doc = doc || elm.ownerDocument || self.doc;
4172
4173                                         if (isIE) {
4174                                                 try {
4175                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
4176                                                         if (elm.nodeType == 1 && self.hasOuterHTML) {
4177                                                                 elm.outerHTML = html;
4178                                                         } else {
4179                                                                 set();
4180                                                         }
4181                                                 } catch (ex) {
4182                                                         // Fix for unknown runtime error
4183                                                         set();
4184                                                 }
4185                                         } else {
4186                                                 set();
4187                                         }
4188                                 }
4189                         });
4190                 },
4191
4192                 /**
4193                  * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
4194                  *
4195                  * @method decode
4196                  * @param {String} s String to decode entities on.
4197                  * @return {String} Entity decoded string.
4198                  */
4199                 decode: Entities.decode,
4200
4201                 /**
4202                  * Entity encodes a string. This method encodes the most common entities, such as <>"&.
4203                  *
4204                  * @method encode
4205                  * @param {String} text String to encode with entities.
4206                  * @return {String} Entity encoded string.
4207                  */
4208                 encode: Entities.encodeAllRaw,
4209
4210                 /**
4211                  * Inserts an element after the reference element.
4212                  *
4213                  * @method insertAfter
4214                  * @param {Element} node Element to insert after the reference.
4215                  * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
4216                  * @return {Element/Array} Element that got added or an array with elements.
4217                  */
4218                 insertAfter: function(node, reference_node) {
4219                         reference_node = this.get(reference_node);
4220
4221                         return this.run(node, function(node) {
4222                                 var parent, nextSibling;
4223
4224                                 parent = reference_node.parentNode;
4225                                 nextSibling = reference_node.nextSibling;
4226
4227                                 if (nextSibling) {
4228                                         parent.insertBefore(node, nextSibling);
4229                                 } else {
4230                                         parent.appendChild(node);
4231                                 }
4232
4233                                 return node;
4234                         });
4235                 },
4236
4237                 /**
4238                  * Replaces the specified element or elements with the new element specified. The new element will
4239                  * be cloned if multiple input elements are passed in.
4240                  *
4241                  * @method replace
4242                  * @param {Element} newElm New element to replace old ones with.
4243                  * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
4244                  * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
4245                  */
4246                 replace: function(newElm, oldElm, keepChildren) {
4247                         var self = this;
4248
4249                         return self.run(oldElm, function(oldElm) {
4250                                 if (is(oldElm, 'array')) {
4251                                         newElm = newElm.cloneNode(true);
4252                                 }
4253
4254                                 if (keepChildren) {
4255                                         each(grep(oldElm.childNodes), function(node) {
4256                                                 newElm.appendChild(node);
4257                                         });
4258                                 }
4259
4260                                 return oldElm.parentNode.replaceChild(newElm, oldElm);
4261                         });
4262                 },
4263
4264                 /**
4265                  * Renames the specified element and keeps its attributes and children.
4266                  *
4267                  * @method rename
4268                  * @param {Element} elm Element to rename.
4269                  * @param {String} name Name of the new element.
4270                  * @return {Element} New element or the old element if it needed renaming.
4271                  */
4272                 rename: function(elm, name) {
4273                         var self = this, newElm;
4274
4275                         if (elm.nodeName != name.toUpperCase()) {
4276                                 // Rename block element
4277                                 newElm = self.create(name);
4278
4279                                 // Copy attribs to new block
4280                                 each(self.getAttribs(elm), function(attr_node) {
4281                                         self.setAttrib(newElm, attr_node.nodeName, self.getAttrib(elm, attr_node.nodeName));
4282                                 });
4283
4284                                 // Replace block
4285                                 self.replace(newElm, elm, 1);
4286                         }
4287
4288                         return newElm || elm;
4289                 },
4290
4291                 /**
4292                  * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
4293                  *
4294                  * @method findCommonAncestor
4295                  * @param {Element} a Element to find common ancestor of.
4296                  * @param {Element} b Element to find common ancestor of.
4297                  * @return {Element} Common ancestor element of the two input elements.
4298                  */
4299                 findCommonAncestor: function(a, b) {
4300                         var ps = a, pe;
4301
4302                         while (ps) {
4303                                 pe = b;
4304
4305                                 while (pe && ps != pe) {
4306                                         pe = pe.parentNode;
4307                                 }
4308
4309                                 if (ps == pe) {
4310                                         break;
4311                                 }
4312
4313                                 ps = ps.parentNode;
4314                         }
4315
4316                         if (!ps && a.ownerDocument) {
4317                                 return a.ownerDocument.documentElement;
4318                         }
4319
4320                         return ps;
4321                 },
4322
4323                 /**
4324                  * Parses the specified RGB color value and returns a hex version of that color.
4325                  *
4326                  * @method toHex
4327                  * @param {String} rgbVal RGB string value like rgb(1,2,3)
4328                  * @return {String} Hex version of that RGB value like #FF00FF.
4329                  */
4330                 toHex: function(rgbVal) {
4331                         return this.styles.toHex(Tools.trim(rgbVal));
4332                 },
4333
4334                 /**
4335                  * Executes the specified function on the element by id or dom element node or array of elements/id.
4336                  *
4337                  * @method run
4338                  * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
4339                  * @param {function} f Function to execute for each item.
4340                  * @param {Object} s Optional scope to execute the function in.
4341                  * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
4342                  */
4343                 run: function(elm, func, scope) {
4344                         var self = this, result;
4345
4346                         if (typeof(elm) === 'string') {
4347                                 elm = self.get(elm);
4348                         }
4349
4350                         if (!elm) {
4351                                 return false;
4352                         }
4353
4354                         scope = scope || this;
4355                         if (!elm.nodeType && (elm.length || elm.length === 0)) {
4356                                 result = [];
4357
4358                                 each(elm, function(elm, i) {
4359                                         if (elm) {
4360                                                 if (typeof(elm) == 'string') {
4361                                                         elm = self.get(elm);
4362                                                 }
4363
4364                                                 result.push(func.call(scope, elm, i));
4365                                         }
4366                                 });
4367
4368                                 return result;
4369                         }
4370
4371                         return func.call(scope, elm);
4372                 },
4373
4374                 /**
4375                  * Returns a NodeList with attributes for the element.
4376                  *
4377                  * @method getAttribs
4378                  * @param {HTMLElement/string} elm Element node or string id to get attributes from.
4379                  * @return {NodeList} NodeList with attributes.
4380                  */
4381                 getAttribs: function(elm) {
4382                         var attrs;
4383
4384                         elm = this.get(elm);
4385
4386                         if (!elm) {
4387                                 return [];
4388                         }
4389
4390                         if (isIE) {
4391                                 attrs = [];
4392
4393                                 // Object will throw exception in IE
4394                                 if (elm.nodeName == 'OBJECT') {
4395                                         return elm.attributes;
4396                                 }
4397
4398                                 // IE doesn't keep the selected attribute if you clone option elements
4399                                 if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
4400                                         attrs.push({specified: 1, nodeName: 'selected'});
4401                                 }
4402
4403                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
4404                                 var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
4405                                 elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
4406                                         attrs.push({specified: 1, nodeName: a});
4407                                 });
4408
4409                                 return attrs;
4410                         }
4411
4412                         return elm.attributes;
4413                 },
4414
4415                 /**
4416                  * Returns true/false if the specified node is to be considered empty or not.
4417                  *
4418                  * @example
4419                  * tinymce.DOM.isEmpty(node, {img: true});
4420                  * @method isEmpty
4421                  * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
4422                  * @return {Boolean} true/false if the node is empty or not.
4423                  */
4424                 isEmpty: function(node, elements) {
4425                         var self = this, i, attributes, type, walker, name, brCount = 0;
4426
4427                         node = node.firstChild;
4428                         if (node) {
4429                                 walker = new TreeWalker(node, node.parentNode);
4430                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4431
4432                                 do {
4433                                         type = node.nodeType;
4434
4435                                         if (type === 1) {
4436                                                 // Ignore bogus elements
4437                                                 if (node.getAttribute('data-mce-bogus')) {
4438                                                         continue;
4439                                                 }
4440
4441                                                 // Keep empty elements like <img />
4442                                                 name = node.nodeName.toLowerCase();
4443                                                 if (elements && elements[name]) {
4444                                                         // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
4445                                                         if (name === 'br') {
4446                                                                 brCount++;
4447                                                                 continue;
4448                                                         }
4449
4450                                                         return false;
4451                                                 }
4452
4453                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
4454                                                 attributes = self.getAttribs(node);
4455                                                 i = node.attributes.length;
4456                                                 while (i--) {
4457                                                         name = node.attributes[i].nodeName;
4458                                                         if (name === "name" || name === 'data-mce-bookmark') {
4459                                                                 return false;
4460                                                         }
4461                                                 }
4462                                         }
4463
4464                                         // Keep comment nodes
4465                                         if (type == 8) {
4466                                                 return false;
4467                                         }
4468
4469                                         // Keep non whitespace text nodes
4470                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
4471                                                 return false;
4472                                         }
4473                                 } while ((node = walker.next()));
4474                         }
4475
4476                         return brCount <= 1;
4477                 },
4478
4479                 /**
4480                  * Creates a new DOM Range object. This will use the native DOM Range API if it's
4481                  * available. If it's not, it will fall back to the custom TinyMCE implementation.
4482                  *
4483                  * @method createRng
4484                  * @return {DOMRange} DOM Range object.
4485                  * @example
4486                  * var rng = tinymce.DOM.createRng();
4487                  * alert(rng.startContainer + "," + rng.startOffset);
4488                  */
4489                 createRng: function() {
4490                         var doc = this.doc;
4491
4492                         return doc.createRange ? doc.createRange() : new Range(this);
4493                 },
4494
4495                 /**
4496                  * Returns the index of the specified node within its parent.
4497                  *
4498                  * @method nodeIndex
4499                  * @param {Node} node Node to look for.
4500                  * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
4501                  * @return {Number} Index of the specified node.
4502                  */
4503                 nodeIndex: function(node, normalized) {
4504                         var idx = 0, lastNodeType, lastNode, nodeType;
4505
4506                         if (node) {
4507                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4508                                         nodeType = node.nodeType;
4509
4510                                         // Normalize text nodes
4511                                         if (normalized && nodeType == 3) {
4512                                                 if (nodeType == lastNodeType || !node.nodeValue.length) {
4513                                                         continue;
4514                                                 }
4515                                         }
4516                                         idx++;
4517                                         lastNodeType = nodeType;
4518                                 }
4519                         }
4520
4521                         return idx;
4522                 },
4523
4524                 /**
4525                  * Splits an element into two new elements and places the specified split
4526                  * element or elements between the new ones. For example splitting the paragraph at the bold element in
4527                  * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
4528                  *
4529                  * @method split
4530                  * @param {Element} parentElm Parent element to split.
4531                  * @param {Element} splitElm Element to split at.
4532                  * @param {Element} replacementElm Optional replacement element to replace the split element with.
4533                  * @return {Element} Returns the split element or the replacement element if that is specified.
4534                  */
4535                 split: function(parentElm, splitElm, replacementElm) {
4536                         var self = this, r = self.createRng(), bef, aft, pa;
4537
4538                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
4539                         // but we don't want that in our code since it serves no purpose for the end user
4540                         // For example splitting this html at the bold element:
4541                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4542                         // would produce:
4543                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4544                         // this function will then trim off empty edges and produce:
4545                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4546                         function trimNode(node) {
4547                                 var i, children = node.childNodes, type = node.nodeType;
4548
4549                                 function surroundedBySpans(node) {
4550                                         var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
4551                                         var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
4552                                         return previousIsSpan && nextIsSpan;
4553                                 }
4554
4555                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
4556                                         return;
4557                                 }
4558
4559                                 for (i = children.length - 1; i >= 0; i--) {
4560                                         trimNode(children[i]);
4561                                 }
4562
4563                                 if (type != 9) {
4564                                         // Keep non whitespace text nodes
4565                                         if (type == 3 && node.nodeValue.length > 0) {
4566                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4567                                                 // Also keep text nodes with only spaces if surrounded by spans.
4568                                                 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
4569                                                 var trimmedLength = trim(node.nodeValue).length;
4570                                                 if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
4571                                                         return;
4572                                                 }
4573                                         } else if (type == 1) {
4574                                                 // If the only child is a bookmark then move it up
4575                                                 children = node.childNodes;
4576
4577                                                 // TODO fix this complex if
4578                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
4579                                                         children[0].getAttribute('data-mce-type') == 'bookmark') {
4580                                                         node.parentNode.insertBefore(children[0], node);
4581                                                 }
4582
4583                                                 // Keep non empty elements or img, hr etc
4584                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
4585                                                         return;
4586                                                 }
4587                                         }
4588
4589                                         self.remove(node);
4590                                 }
4591
4592                                 return node;
4593                         }
4594
4595                         if (parentElm && splitElm) {
4596                                 // Get before chunk
4597                                 r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
4598                                 r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
4599                                 bef = r.extractContents();
4600
4601                                 // Get after chunk
4602                                 r = self.createRng();
4603                                 r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
4604                                 r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
4605                                 aft = r.extractContents();
4606
4607                                 // Insert before chunk
4608                                 pa = parentElm.parentNode;
4609                                 pa.insertBefore(trimNode(bef), parentElm);
4610
4611                                 // Insert middle chunk
4612                                 if (replacementElm) {
4613                                         pa.replaceChild(replacementElm, splitElm);
4614                                 } else {
4615                                         pa.insertBefore(splitElm, parentElm);
4616                                 }
4617
4618                                 // Insert after chunk
4619                                 pa.insertBefore(trimNode(aft), parentElm);
4620                                 self.remove(parentElm);
4621
4622                                 return replacementElm || splitElm;
4623                         }
4624                 },
4625
4626                 /**
4627                  * Adds an event handler to the specified object.
4628                  *
4629                  * @method bind
4630                  * @param {Element/Document/Window/Array/String} o Object or element id string to add event
4631                  * handler to or an array of elements/ids/documents.
4632                  * @param {String} n Name of event handler to add, for example: click.
4633                  * @param {function} f Function to execute when the event occurs.
4634                  * @param {Object} s Optional scope to execute the function in.
4635                  * @return {function} Function callback handler the same as the one passed in.
4636                  */
4637                 bind: function(target, name, func, scope) {
4638                         return this.events.bind(target, name, func, scope || this);
4639                 },
4640
4641                 /**
4642                  * Removes the specified event handler by name and function from an element or collection of elements.
4643                  *
4644                  * @method unbind
4645                  * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from.
4646                  * @param {String} n Event handler name, for example: "click"
4647                  * @param {function} f Function to remove.
4648                  * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
4649                  * were passed in.
4650                  */
4651                 unbind: function(target, name, func) {
4652                         return this.events.unbind(target, name, func);
4653                 },
4654
4655                 /**
4656                  * Fires the specified event name with object on target.
4657                  *
4658                  * @method fire
4659                  * @param {Node/Document/Window} target Target element or object to fire event on.
4660                  * @param {String} name Name of the event to fire.
4661                  * @param {Object} evt Event object to send.
4662                  * @return {Event} Event object.
4663                  */
4664                 fire: function(target, name, evt) {
4665                         return this.events.fire(target, name, evt);
4666                 },
4667
4668                 // Returns the content editable state of a node
4669                 getContentEditable: function(node) {
4670                         var contentEditable;
4671
4672                         // Check type
4673                         if (node.nodeType != 1) {
4674                                 return null;
4675                         }
4676
4677                         // Check for fake content editable
4678                         contentEditable = node.getAttribute("data-mce-contenteditable");
4679                         if (contentEditable && contentEditable !== "inherit") {
4680                                 return contentEditable;
4681                         }
4682
4683                         // Check for real content editable
4684                         return node.contentEditable !== "inherit" ? node.contentEditable : null;
4685                 },
4686
4687                 /**
4688                  * Destroys all internal references to the DOM to solve IE leak issues.
4689                  *
4690                  * @method destroy
4691                  */
4692                 destroy: function() {
4693                         var self = this;
4694
4695                         self.win = self.doc = self.root = self.events = self.frag = null;
4696                 },
4697
4698                 // #ifdef debug
4699
4700                 dumpRng: function(r) {
4701                         return (
4702                                 'startContainer: ' + r.startContainer.nodeName +
4703                                 ', startOffset: ' + r.startOffset +
4704                                 ', endContainer: ' + r.endContainer.nodeName +
4705                                 ', endOffset: ' + r.endOffset
4706                         );
4707                 },
4708
4709                 // #endif
4710
4711                 _findSib: function(node, selector, name) {
4712                         var self = this, func = selector;
4713
4714                         if (node) {
4715                                 // If expression make a function of it using is
4716                                 if (typeof(func) == 'string') {
4717                                         func = function(node) {
4718                                                 return self.is(node, selector);
4719                                         };
4720                                 }
4721
4722                                 // Loop all siblings
4723                                 for (node = node[name]; node; node = node[name]) {
4724                                         if (func(node)) {
4725                                                 return node;
4726                                         }
4727                                 }
4728                         }
4729
4730                         return null;
4731                 }
4732         };
4733
4734         /**
4735          * Instance of DOMUtils for the current document.
4736          *
4737          * @static
4738          * @property DOM
4739          * @type tinymce.dom.DOMUtils
4740          * @example
4741          * // Example of how to add a class to some element by id
4742          * tinymce.DOM.addClass('someid', 'someclass');
4743          */
4744         DOMUtils.DOM = new DOMUtils(document);
4745
4746         return DOMUtils;
4747 });
4748
4749 // Included from: js/tinymce/classes/dom/ScriptLoader.js
4750
4751 /**
4752  * ScriptLoader.js
4753  *
4754  * Copyright, Moxiecode Systems AB
4755  * Released under LGPL License.
4756  *
4757  * License: http://www.tinymce.com/license
4758  * Contributing: http://www.tinymce.com/contributing
4759  */
4760
4761 /*globals console*/
4762
4763 /**
4764  * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
4765  * when various items gets loaded. This class is useful to load external JavaScript files.
4766  *
4767  * @class tinymce.dom.ScriptLoader
4768  * @example
4769  * // Load a script from a specific URL using the global script loader
4770  * tinymce.ScriptLoader.load('somescript.js');
4771  *
4772  * // Load a script using a unique instance of the script loader
4773  * var scriptLoader = new tinymce.dom.ScriptLoader();
4774  *
4775  * scriptLoader.load('somescript.js');
4776  *
4777  * // Load multiple scripts
4778  * var scriptLoader = new tinymce.dom.ScriptLoader();
4779  *
4780  * scriptLoader.add('somescript1.js');
4781  * scriptLoader.add('somescript2.js');
4782  * scriptLoader.add('somescript3.js');
4783  *
4784  * scriptLoader.loadQueue(function() {
4785  *    alert('All scripts are now loaded.');
4786  * });
4787  */
4788 define("tinymce/dom/ScriptLoader", [
4789         "tinymce/dom/DOMUtils",
4790         "tinymce/util/Tools"
4791 ], function(DOMUtils, Tools) {
4792         var DOM = DOMUtils.DOM;
4793         var each = Tools.each, grep = Tools.grep;
4794
4795         function ScriptLoader() {
4796                 var QUEUED = 0,
4797                         LOADING = 1,
4798                         LOADED = 2,
4799                         states = {},
4800                         queue = [],
4801                         scriptLoadedCallbacks = {},
4802                         queueLoadedCallbacks = [],
4803                         loading = 0,
4804                         undef;
4805
4806                 /**
4807                  * Loads a specific script directly without adding it to the load queue.
4808                  *
4809                  * @method load
4810                  * @param {String} url Absolute URL to script to add.
4811                  * @param {function} callback Optional callback function to execute ones this script gets loaded.
4812                  * @param {Object} scope Optional scope to execute callback in.
4813                  */
4814                 function loadScript(url, callback) {
4815                         var dom = DOM, elm, id;
4816
4817                         // Execute callback when script is loaded
4818                         function done() {
4819                                 dom.remove(id);
4820
4821                                 if (elm) {
4822                                         elm.onreadystatechange = elm.onload = elm = null;
4823                                 }
4824
4825                                 callback();
4826                         }
4827
4828                         function error() {
4829                                 // Report the error so it's easier for people to spot loading errors
4830                                 if (typeof(console) !== "undefined" && console.log) {
4831                                         console.log("Failed to load: " + url);
4832                                 }
4833
4834                                 // We can't mark it as done if there is a load error since
4835                                 // A) We don't want to produce 404 errors on the server and
4836                                 // B) the onerror event won't fire on all browsers.
4837                                 // done();
4838                         }
4839
4840                         id = dom.uniqueId();
4841
4842                         // Create new script element
4843                         elm = document.createElement('script');
4844                         elm.id = id;
4845                         elm.type = 'text/javascript';
4846                         elm.src = url;
4847
4848                         // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
4849                         if ("onreadystatechange" in elm) {
4850                                 elm.onreadystatechange = function() {
4851                                         if (/loaded|complete/.test(elm.readyState)) {
4852                                                 done();
4853                                         }
4854                                 };
4855                         } else {
4856                                 elm.onload = done;
4857                         }
4858
4859                         // Add onerror event will get fired on some browsers but not all of them
4860                         elm.onerror = error;
4861
4862                         // Add script to document
4863                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
4864                 }
4865
4866                 /**
4867                  * Returns true/false if a script has been loaded or not.
4868                  *
4869                  * @method isDone
4870                  * @param {String} url URL to check for.
4871                  * @return {Boolean} true/false if the URL is loaded.
4872                  */
4873                 this.isDone = function(url) {
4874                         return states[url] == LOADED;
4875                 };
4876
4877                 /**
4878                  * Marks a specific script to be loaded. This can be useful if a script got loaded outside
4879                  * the script loader or to skip it from loading some script.
4880                  *
4881                  * @method markDone
4882                  * @param {string} u Absolute URL to the script to mark as loaded.
4883                  */
4884                 this.markDone = function(url) {
4885                         states[url] = LOADED;
4886                 };
4887
4888                 /**
4889                  * Adds a specific script to the load queue of the script loader.
4890                  *
4891                  * @method add
4892                  * @param {String} url Absolute URL to script to add.
4893                  * @param {function} callback Optional callback function to execute ones this script gets loaded.
4894                  * @param {Object} scope Optional scope to execute callback in.
4895                  */
4896                 this.add = this.load = function(url, callback, scope) {
4897                         var state = states[url];
4898
4899                         // Add url to load queue
4900                         if (state == undef) {
4901                                 queue.push(url);
4902                                 states[url] = QUEUED;
4903                         }
4904
4905                         if (callback) {
4906                                 // Store away callback for later execution
4907                                 if (!scriptLoadedCallbacks[url]) {
4908                                         scriptLoadedCallbacks[url] = [];
4909                                 }
4910
4911                                 scriptLoadedCallbacks[url].push({
4912                                         func: callback,
4913                                         scope: scope || this
4914                                 });
4915                         }
4916                 };
4917
4918                 /**
4919                  * Starts the loading of the queue.
4920                  *
4921                  * @method loadQueue
4922                  * @param {function} callback Optional callback to execute when all queued items are loaded.
4923                  * @param {Object} scope Optional scope to execute the callback in.
4924                  */
4925                 this.loadQueue = function(callback, scope) {
4926                         this.loadScripts(queue, callback, scope);
4927                 };
4928
4929                 /**
4930                  * Loads the specified queue of files and executes the callback ones they are loaded.
4931                  * This method is generally not used outside this class but it might be useful in some scenarios.
4932                  *
4933                  * @method loadScripts
4934                  * @param {Array} scripts Array of queue items to load.
4935                  * @param {function} callback Optional callback to execute ones all items are loaded.
4936                  * @param {Object} scope Optional scope to execute callback in.
4937                  */
4938                 this.loadScripts = function(scripts, callback, scope) {
4939                         var loadScripts;
4940
4941                         function execScriptLoadedCallbacks(url) {
4942                                 // Execute URL callback functions
4943                                 each(scriptLoadedCallbacks[url], function(callback) {
4944                                         callback.func.call(callback.scope);
4945                                 });
4946
4947                                 scriptLoadedCallbacks[url] = undef;
4948                         }
4949
4950                         queueLoadedCallbacks.push({
4951                                 func: callback,
4952                                 scope: scope || this
4953                         });
4954
4955                         loadScripts = function() {
4956                                 var loadingScripts = grep(scripts);
4957
4958                                 // Current scripts has been handled
4959                                 scripts.length = 0;
4960
4961                                 // Load scripts that needs to be loaded
4962                                 each(loadingScripts, function(url) {
4963                                         // Script is already loaded then execute script callbacks directly
4964                                         if (states[url] == LOADED) {
4965                                                 execScriptLoadedCallbacks(url);
4966                                                 return;
4967                                         }
4968
4969                                         // Is script not loading then start loading it
4970                                         if (states[url] != LOADING) {
4971                                                 states[url] = LOADING;
4972                                                 loading++;
4973
4974                                                 loadScript(url, function() {
4975                                                         states[url] = LOADED;
4976                                                         loading--;
4977
4978                                                         execScriptLoadedCallbacks(url);
4979
4980                                                         // Load more scripts if they where added by the recently loaded script
4981                                                         loadScripts();
4982                                                 });
4983                                         }
4984                                 });
4985
4986                                 // No scripts are currently loading then execute all pending queue loaded callbacks
4987                                 if (!loading) {
4988                                         each(queueLoadedCallbacks, function(callback) {
4989                                                 callback.func.call(callback.scope);
4990                                         });
4991
4992                                         queueLoadedCallbacks.length = 0;
4993                                 }
4994                         };
4995
4996                         loadScripts();
4997                 };
4998         }
4999
5000         ScriptLoader.ScriptLoader = new ScriptLoader();
5001
5002         return ScriptLoader;
5003 });
5004
5005 // Included from: js/tinymce/classes/AddOnManager.js
5006
5007 /**
5008  * AddOnManager.js
5009  *
5010  * Copyright, Moxiecode Systems AB
5011  * Released under LGPL License.
5012  *
5013  * License: http://www.tinymce.com/license
5014  * Contributing: http://www.tinymce.com/contributing
5015  */
5016
5017 /**
5018  * This class handles the loading of themes/plugins or other add-ons and their language packs.
5019  *
5020  * @class tinymce.AddOnManager
5021  */
5022 define("tinymce/AddOnManager", [
5023     "tinymce/dom/ScriptLoader",
5024     "tinymce/util/Tools"
5025 ], function(ScriptLoader, Tools) {
5026     var each = Tools.each;
5027
5028     function AddOnManager() {
5029         var self = this;
5030
5031         self.items = [];
5032         self.urls = {};
5033         self.lookup = {};
5034     }
5035
5036     AddOnManager.prototype = {
5037         /**
5038          * Returns the specified add on by the short name.
5039          *
5040          * @method get
5041          * @param {String} name Add-on to look for.
5042          * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
5043          */
5044         get: function(name) {
5045             if (this.lookup[name]) {
5046                 return this.lookup[name].instance;
5047             } else {
5048                 return undefined;
5049             }
5050         },
5051
5052         dependencies: function(name) {
5053             var result;
5054
5055             if (this.lookup[name]) {
5056                 result = this.lookup[name].dependencies;
5057             }
5058
5059             return result || [];
5060         },
5061
5062         /**
5063          * Loads a language pack for the specified add-on.
5064          *
5065          * @method requireLangPack
5066          * @param {String} name Short name of the add-on.
5067          */
5068         requireLangPack: function(name) {
5069             var settings = AddOnManager.settings;
5070
5071             if (settings && settings.language && settings.language_load !== false) {
5072                 ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + settings.language + '.js');
5073             }
5074         },
5075
5076         /**
5077          * Adds a instance of the add-on by it's short name.
5078          *
5079          * @method add
5080          * @param {String} id Short name/id for the add-on.
5081          * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
5082          * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
5083          * @example
5084          * // Create a simple plugin
5085          * tinymce.create('tinymce.plugins.TestPlugin', {
5086          *     TestPlugin: function(ed, url) {
5087          *         ed.on('click', function(e) {
5088          *             ed.windowManager.alert('Hello World!');
5089          *         });
5090          *     }
5091          * });
5092          *
5093          * // Register plugin using the add method
5094          * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
5095          *
5096          * // Initialize TinyMCE
5097          * tinymce.init({
5098          *    ...
5099          *    plugins: '-test' // Init the plugin but don't try to load it
5100          * });
5101          */
5102         add: function(id, addOn, dependencies) {
5103             this.items.push(addOn);
5104             this.lookup[id] = {instance: addOn, dependencies: dependencies};
5105
5106             return addOn;
5107         },
5108
5109         createUrl: function(baseUrl, dep) {
5110             if (typeof dep === "object") {
5111                 return dep;
5112             } else {
5113                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
5114             }
5115         },
5116
5117         /**
5118          * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
5119          * This should be used in development mode.  A new compressor/javascript munger process will ensure that the
5120          * components are put together into the plugin.js file and compressed correctly.
5121          *
5122          * @method addComponents
5123          * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
5124          * @param {Array} scripts Array containing the names of the scripts to load.
5125          */
5126         addComponents: function(pluginName, scripts) {
5127             var pluginUrl = this.urls[pluginName];
5128
5129             each(scripts, function(script) {
5130                 ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
5131             });
5132         },
5133
5134         /**
5135          * Loads an add-on from a specific url.
5136          *
5137          * @method load
5138          * @param {String} n Short name of the add-on that gets loaded.
5139          * @param {String} u URL to the add-on that will get loaded.
5140          * @param {function} cb Optional callback to execute ones the add-on is loaded.
5141          * @param {Object} s Optional scope to execute the callback in.
5142          * @example
5143          * // Loads a plugin from an external URL
5144          * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
5145          *
5146          * // Initialize TinyMCE
5147          * tinymce.init({
5148          *    ...
5149          *    plugins: '-myplugin' // Don't try to load it again
5150          * });
5151          */
5152         load: function(n, u, cb, s) {
5153             var t = this, url = u;
5154
5155             function loadDependencies() {
5156                 var dependencies = t.dependencies(n);
5157
5158                 each(dependencies, function(dep) {
5159                     var newUrl = t.createUrl(u, dep);
5160
5161                     t.load(newUrl.resource, newUrl, undefined, undefined);
5162                 });
5163
5164                 if (cb) {
5165                     if (s) {
5166                         cb.call(s);
5167                     } else {
5168                         cb.call(ScriptLoader);
5169                     }
5170                 }
5171             }
5172
5173             if (t.urls[n]) {
5174                 return;
5175             }
5176
5177             if (typeof u === "object") {
5178                 url = u.prefix + u.resource + u.suffix;
5179             }
5180
5181             if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
5182                 url = AddOnManager.baseURL + '/' + url;
5183             }
5184
5185             t.urls[n] = url.substring(0, url.lastIndexOf('/'));
5186
5187             if (t.lookup[n]) {
5188                 loadDependencies();
5189             } else {
5190                 ScriptLoader.ScriptLoader.add(url, loadDependencies, s);
5191             }
5192         }
5193     };
5194
5195     AddOnManager.PluginManager = new AddOnManager();
5196     AddOnManager.ThemeManager = new AddOnManager();
5197
5198     return AddOnManager;
5199 });
5200
5201 /**
5202  * TinyMCE theme class.
5203  *
5204  * @class tinymce.Theme
5205  */
5206
5207 /**
5208  * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
5209  *
5210  * @method renderUI
5211  * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
5212  * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
5213  */
5214
5215 /**
5216  * 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.
5217  *
5218  * @class tinymce.Plugin
5219  * @example
5220  * tinymce.PluginManager.add('example', function(editor, url) {
5221  *     // Add a button that opens a window
5222  *     editor.addButton('example', {
5223  *         text: 'My button',
5224  *         icon: false,
5225  *         onclick: function() {
5226  *             // Open window
5227  *             editor.windowManager.open({
5228  *                 title: 'Example plugin',
5229  *                 body: [
5230  *                     {type: 'textbox', name: 'title', label: 'Title'}
5231  *                 ],
5232  *                 onsubmit: function(e) {
5233  *                     // Insert content when the window form is submitted
5234  *                     editor.insertContent('Title: ' + e.data.title);
5235  *                 }
5236  *             });
5237  *         }
5238  *     });
5239  *
5240  *     // Adds a menu item to the tools menu
5241  *     editor.addMenuItem('example', {
5242  *         text: 'Example plugin',
5243  *         context: 'tools',
5244  *         onclick: function() {
5245  *             // Open window with a specific url
5246  *             editor.windowManager.open({
5247  *                 title: 'TinyMCE site',
5248  *                 url: 'http://www.tinymce.com',
5249  *                 width: 800,
5250  *                 height: 600,
5251  *                 buttons: [{
5252  *                     text: 'Close',
5253  *                     onclick: 'close'
5254  *                 }]
5255  *             });
5256  *         }
5257  *     });
5258  * });
5259  */
5260
5261 // Included from: js/tinymce/classes/html/Node.js
5262
5263 /**
5264  * Node.js
5265  *
5266  * Copyright, Moxiecode Systems AB
5267  * Released under LGPL License.
5268  *
5269  * License: http://www.tinymce.com/license
5270  * Contributing: http://www.tinymce.com/contributing
5271  */
5272
5273 /**
5274  * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
5275  *
5276  * @example
5277  * var node = new tinymce.html.Node('strong', 1);
5278  * someRoot.append(node);
5279  *
5280  * @class tinymce.html.Node
5281  * @version 3.4
5282  */
5283 define("tinymce/html/Node", [], function() {
5284         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
5285                 '#text': 3,
5286                 '#comment': 8,
5287                 '#cdata': 4,
5288                 '#pi': 7,
5289                 '#doctype': 10,
5290                 '#document-fragment': 11
5291         };
5292
5293         // Walks the tree left/right
5294         function walk(node, root_node, prev) {
5295                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
5296
5297                 // Walk into nodes if it has a start
5298                 if (node[startName]) {
5299                         return node[startName];
5300                 }
5301
5302                 // Return the sibling if it has one
5303                 if (node !== root_node) {
5304                         sibling = node[siblingName];
5305
5306                         if (sibling) {
5307                                 return sibling;
5308                         }
5309
5310                         // Walk up the parents to look for siblings
5311                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
5312                                 sibling = parent[siblingName];
5313
5314                                 if (sibling) {
5315                                         return sibling;
5316                                 }
5317                         }
5318                 }
5319         }
5320
5321         /**
5322          * Constructs a new Node instance.
5323          *
5324          * @constructor
5325          * @method Node
5326          * @param {String} name Name of the node type.
5327          * @param {Number} type Numeric type representing the node.
5328          */
5329         function Node(name, type) {
5330                 this.name = name;
5331                 this.type = type;
5332
5333                 if (type === 1) {
5334                         this.attributes = [];
5335                         this.attributes.map = {};
5336                 }
5337         }
5338
5339         Node.prototype = {
5340                 /**
5341                  * Replaces the current node with the specified one.
5342                  *
5343                  * @example
5344                  * someNode.replace(someNewNode);
5345                  *
5346                  * @method replace
5347                  * @param {tinymce.html.Node} node Node to replace the current node with.
5348                  * @return {tinymce.html.Node} The old node that got replaced.
5349                  */
5350                 replace: function(node) {
5351                         var self = this;
5352
5353                         if (node.parent) {
5354                                 node.remove();
5355                         }
5356
5357                         self.insert(node, self);
5358                         self.remove();
5359
5360                         return self;
5361                 },
5362
5363                 /**
5364                  * Gets/sets or removes an attribute by name.
5365                  *
5366                  * @example
5367                  * someNode.attr("name", "value"); // Sets an attribute
5368                  * console.log(someNode.attr("name")); // Gets an attribute
5369                  * someNode.attr("name", null); // Removes an attribute
5370                  *
5371                  * @method attr
5372                  * @param {String} name Attribute name to set or get.
5373                  * @param {String} value Optional value to set.
5374                  * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
5375                  */
5376                 attr: function(name, value) {
5377                         var self = this, attrs, i, undef;
5378
5379                         if (typeof name !== "string") {
5380                                 for (i in name) {
5381                                         self.attr(i, name[i]);
5382                                 }
5383
5384                                 return self;
5385                         }
5386
5387                         if ((attrs = self.attributes)) {
5388                                 if (value !== undef) {
5389                                         // Remove attribute
5390                                         if (value === null) {
5391                                                 if (name in attrs.map) {
5392                                                         delete attrs.map[name];
5393
5394                                                         i = attrs.length;
5395                                                         while (i--) {
5396                                                                 if (attrs[i].name === name) {
5397                                                                         attrs = attrs.splice(i, 1);
5398                                                                         return self;
5399                                                                 }
5400                                                         }
5401                                                 }
5402
5403                                                 return self;
5404                                         }
5405
5406                                         // Set attribute
5407                                         if (name in attrs.map) {
5408                                                 // Set attribute
5409                                                 i = attrs.length;
5410                                                 while (i--) {
5411                                                         if (attrs[i].name === name) {
5412                                                                 attrs[i].value = value;
5413                                                                 break;
5414                                                         }
5415                                                 }
5416                                         } else {
5417                                                 attrs.push({name: name, value: value});
5418                                         }
5419
5420                                         attrs.map[name] = value;
5421
5422                                         return self;
5423                                 } else {
5424                                         return attrs.map[name];
5425                                 }
5426                         }
5427                 },
5428
5429                 /**
5430                  * Does a shallow clones the node into a new node. It will also exclude id attributes since
5431                  * there should only be one id per document.
5432                  *
5433                  * @example
5434                  * var clonedNode = node.clone();
5435                  *
5436                  * @method clone
5437                  * @return {tinymce.html.Node} New copy of the original node.
5438                  */
5439                 clone: function() {
5440                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
5441
5442                         // Clone element attributes
5443                         if ((selfAttrs = self.attributes)) {
5444                                 cloneAttrs = [];
5445                                 cloneAttrs.map = {};
5446
5447                                 for (i = 0, l = selfAttrs.length; i < l; i++) {
5448                                         selfAttr = selfAttrs[i];
5449
5450                                         // Clone everything except id
5451                                         if (selfAttr.name !== 'id') {
5452                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
5453                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;
5454                                         }
5455                                 }
5456
5457                                 clone.attributes = cloneAttrs;
5458                         }
5459
5460                         clone.value = self.value;
5461                         clone.shortEnded = self.shortEnded;
5462
5463                         return clone;
5464                 },
5465
5466                 /**
5467                  * Wraps the node in in another node.
5468                  *
5469                  * @example
5470                  * node.wrap(wrapperNode);
5471                  *
5472                  * @method wrap
5473                  */
5474                 wrap: function(wrapper) {
5475                         var self = this;
5476
5477                         self.parent.insert(wrapper, self);
5478                         wrapper.append(self);
5479
5480                         return self;
5481                 },
5482
5483                 /**
5484                  * Unwraps the node in other words it removes the node but keeps the children.
5485                  *
5486                  * @example
5487                  * node.unwrap();
5488                  *
5489                  * @method unwrap
5490                  */
5491                 unwrap: function() {
5492                         var self = this, node, next;
5493
5494                         for (node = self.firstChild; node; ) {
5495                                 next = node.next;
5496                                 self.insert(node, self, true);
5497                                 node = next;
5498                         }
5499
5500                         self.remove();
5501                 },
5502
5503                 /**
5504                  * Removes the node from it's parent.
5505                  *
5506                  * @example
5507                  * node.remove();
5508                  *
5509                  * @method remove
5510                  * @return {tinymce.html.Node} Current node that got removed.
5511                  */
5512                 remove: function() {
5513                         var self = this, parent = self.parent, next = self.next, prev = self.prev;
5514
5515                         if (parent) {
5516                                 if (parent.firstChild === self) {
5517                                         parent.firstChild = next;
5518
5519                                         if (next) {
5520                                                 next.prev = null;
5521                                         }
5522                                 } else {
5523                                         prev.next = next;
5524                                 }
5525
5526                                 if (parent.lastChild === self) {
5527                                         parent.lastChild = prev;
5528
5529                                         if (prev) {
5530                                                 prev.next = null;
5531                                         }
5532                                 } else {
5533                                         next.prev = prev;
5534                                 }
5535
5536                                 self.parent = self.next = self.prev = null;
5537                         }
5538
5539                         return self;
5540                 },
5541
5542                 /**
5543                  * Appends a new node as a child of the current node.
5544                  *
5545                  * @example
5546                  * node.append(someNode);
5547                  *
5548                  * @method append
5549                  * @param {tinymce.html.Node} node Node to append as a child of the current one.
5550                  * @return {tinymce.html.Node} The node that got appended.
5551                  */
5552                 append: function(node) {
5553                         var self = this, last;
5554
5555                         if (node.parent) {
5556                                 node.remove();
5557                         }
5558
5559                         last = self.lastChild;
5560                         if (last) {
5561                                 last.next = node;
5562                                 node.prev = last;
5563                                 self.lastChild = node;
5564                         } else {
5565                                 self.lastChild = self.firstChild = node;
5566                         }
5567
5568                         node.parent = self;
5569
5570                         return node;
5571                 },
5572
5573                 /**
5574                  * Inserts a node at a specific position as a child of the current node.
5575                  *
5576                  * @example
5577                  * parentNode.insert(newChildNode, oldChildNode);
5578                  *
5579                  * @method insert
5580                  * @param {tinymce.html.Node} node Node to insert as a child of the current node.
5581                  * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
5582                  * @param {Boolean} before Optional state to insert the node before the reference node.
5583                  * @return {tinymce.html.Node} The node that got inserted.
5584                  */
5585                 insert: function(node, ref_node, before) {
5586                         var parent;
5587
5588                         if (node.parent) {
5589                                 node.remove();
5590                         }
5591
5592                         parent = ref_node.parent || this;
5593
5594                         if (before) {
5595                                 if (ref_node === parent.firstChild) {
5596                                         parent.firstChild = node;
5597                                 } else {
5598                                         ref_node.prev.next = node;
5599                                 }
5600
5601                                 node.prev = ref_node.prev;
5602                                 node.next = ref_node;
5603                                 ref_node.prev = node;
5604                         } else {
5605                                 if (ref_node === parent.lastChild) {
5606                                         parent.lastChild = node;
5607                                 } else {
5608                                         ref_node.next.prev = node;
5609                                 }
5610
5611                                 node.next = ref_node.next;
5612                                 node.prev = ref_node;
5613                                 ref_node.next = node;
5614                         }
5615
5616                         node.parent = parent;
5617
5618                         return node;
5619                 },
5620
5621                 /**
5622                  * Get all children by name.
5623                  *
5624                  * @method getAll
5625                  * @param {String} name Name of the child nodes to collect.
5626                  * @return {Array} Array with child nodes matchin the specified name.
5627                  */
5628                 getAll: function(name) {
5629                         var self = this, node, collection = [];
5630
5631                         for (node = self.firstChild; node; node = walk(node, self)) {
5632                                 if (node.name === name) {
5633                                         collection.push(node);
5634                                 }
5635                         }
5636
5637                         return collection;
5638                 },
5639
5640                 /**
5641                  * Removes all children of the current node.
5642                  *
5643                  * @method empty
5644                  * @return {tinymce.html.Node} The current node that got cleared.
5645                  */
5646                 empty: function() {
5647                         var self = this, nodes, i, node;
5648
5649                         // Remove all children
5650                         if (self.firstChild) {
5651                                 nodes = [];
5652
5653                                 // Collect the children
5654                                 for (node = self.firstChild; node; node = walk(node, self)) {
5655                                         nodes.push(node);
5656                                 }
5657
5658                                 // Remove the children
5659                                 i = nodes.length;
5660                                 while (i--) {
5661                                         node = nodes[i];
5662                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
5663                                 }
5664                         }
5665
5666                         self.firstChild = self.lastChild = null;
5667
5668                         return self;
5669                 },
5670
5671                 /**
5672                  * Returns true/false if the node is to be considered empty or not.
5673                  *
5674                  * @example
5675                  * node.isEmpty({img: true});
5676                  * @method isEmpty
5677                  * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
5678                  * @return {Boolean} true/false if the node is empty or not.
5679                  */
5680                 isEmpty: function(elements) {
5681                         var self = this, node = self.firstChild, i, name;
5682
5683                         if (node) {
5684                                 do {
5685                                         if (node.type === 1) {
5686                                                 // Ignore bogus elements
5687                                                 if (node.attributes.map['data-mce-bogus']) {
5688                                                         continue;
5689                                                 }
5690
5691                                                 // Keep empty elements like <img />
5692                                                 if (elements[node.name]) {
5693                                                         return false;
5694                                                 }
5695
5696                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
5697                                                 i = node.attributes.length;
5698                                                 while (i--) {
5699                                                         name = node.attributes[i].name;
5700                                                         if (name === "name" || name.indexOf('data-mce-') === 0) {
5701                                                                 return false;
5702                                                         }
5703                                                 }
5704                                         }
5705
5706                                         // Keep comments
5707                                         if (node.type === 8) {
5708                                                 return false;
5709                                         }
5710
5711                                         // Keep non whitespace text nodes
5712                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
5713                                                 return false;
5714                                         }
5715                                 } while ((node = walk(node, self)));
5716                         }
5717
5718                         return true;
5719                 },
5720
5721                 /**
5722                  * Walks to the next or previous node and returns that node or null if it wasn't found.
5723                  *
5724                  * @method walk
5725                  * @param {Boolean} prev Optional previous node state defaults to false.
5726                  * @return {tinymce.html.Node} Node that is next to or previous of the current node.
5727                  */
5728                 walk: function(prev) {
5729                         return walk(this, null, prev);
5730                 }
5731         };
5732
5733         /**
5734          * Creates a node of a specific type.
5735          *
5736          * @static
5737          * @method create
5738          * @param {String} name Name of the node type to create for example "b" or "#text".
5739          * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
5740          */
5741         Node.create = function(name, attrs) {
5742                 var node, attrName;
5743
5744                 // Create node
5745                 node = new Node(name, typeLookup[name] || 1);
5746
5747                 // Add attributes if needed
5748                 if (attrs) {
5749                         for (attrName in attrs) {
5750                                 node.attr(attrName, attrs[attrName]);
5751                         }
5752                 }
5753
5754                 return node;
5755         };
5756
5757         return Node;
5758 });
5759
5760 // Included from: js/tinymce/classes/html/Schema.js
5761
5762 /**
5763  * Schema.js
5764  *
5765  * Copyright, Moxiecode Systems AB
5766  * Released under LGPL License.
5767  *
5768  * License: http://www.tinymce.com/license
5769  * Contributing: http://www.tinymce.com/contributing
5770  */
5771
5772 /**
5773  * Schema validator class.
5774  *
5775  * @class tinymce.html.Schema
5776  * @example
5777  *  if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
5778  *    alert('span is valid child of p.');
5779  *
5780  *  if (tinymce.activeEditor.schema.getElementRule('p'))
5781  *    alert('P is a valid element.');
5782  *
5783  * @class tinymce.html.Schema
5784  * @version 3.4
5785  */
5786 define("tinymce/html/Schema", [
5787         "tinymce/util/Tools"
5788 ], function(Tools) {
5789         var mapCache = {};
5790         var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
5791
5792         function split(items, delim) {
5793                 return items ? items.split(delim || ' ') : [];
5794         }
5795
5796         /**
5797          * Builds a schema lookup table
5798          *
5799          * @private
5800          * @param {String} type html4, html5 or html5-strict schema type.
5801          * @return {Object} Schema lookup table.
5802          */
5803         function compileSchema(type) {
5804                 var schema = {}, globalAttributes, eventAttributes, blockContent;
5805                 var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
5806
5807                 function add(name, attributes, children) {
5808                         var ni, i, attributesOrder, args = arguments;
5809
5810                         function arrayToMap(array) {
5811                                 var map = {}, i, l;
5812
5813                                 for (i = 0, l = array.length; i < l; i++) {
5814                                         map[array[i]] = {};
5815                                 }
5816
5817                                 return map;
5818                         }
5819
5820                         children = children || [];
5821                         attributes = attributes || "";
5822
5823                         if (typeof(children) === "string") {
5824                                 children = split(children);
5825                         }
5826
5827                         // Split string children
5828                         for (i = 3; i < args.length; i++) {
5829                                 if (typeof(args[i]) === "string") {
5830                                         args[i] = split(args[i]);
5831                                 }
5832
5833                                 children.push.apply(children, args[i]);
5834                         }
5835
5836                         name = split(name);
5837                         ni = name.length;
5838                         while (ni--) {
5839                                 attributesOrder = [].concat(globalAttributes, split(attributes));
5840                                 schema[name[ni]] = {
5841                                         attributes: arrayToMap(attributesOrder),
5842                                         attributesOrder: attributesOrder,
5843                                         children: arrayToMap(children)
5844                                 };
5845                         }
5846                 }
5847
5848                 function addAttrs(name, attributes) {
5849                         var ni, schemaItem, i, l;
5850
5851                         name = split(name);
5852                         ni = name.length;
5853                         attributes = split(attributes);
5854                         while (ni--) {
5855                                 schemaItem = schema[name[ni]];
5856                                 for (i = 0, l = attributes.length; i < l; i++) {
5857                                         schemaItem.attributes[attributes[i]] = {};
5858                                         schemaItem.attributesOrder.push(attributes[i]);
5859                                 }
5860                         }
5861                 }
5862
5863                 // Use cached schema
5864                 if (mapCache[type]) {
5865                         return mapCache[type];
5866                 }
5867
5868                 // Attributes present on all elements
5869                 globalAttributes = split("id accesskey class dir lang style tabindex title");
5870
5871                 // Event attributes can be opt-in/opt-out
5872                 eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
5873                                 "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
5874                                 "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
5875                                 "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
5876                                 "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
5877                                 "onwaiting"
5878                 );
5879
5880                 // Block content elements
5881                 blockContent = split(
5882                         "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
5883                 );
5884
5885                 // Phrasing content elements from the HTML5 spec (inline)
5886                 phrasingContent = split(
5887                         "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
5888                         "label map noscript object q s samp script select small span strong sub sup " +
5889                         "textarea u var #text #comment"
5890                 );
5891
5892                 // Add HTML5 items to globalAttributes, blockContent, phrasingContent
5893                 if (type != "html4") {
5894                         globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
5895                                 "hidden spellcheck translate"));
5896                         blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
5897                         phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
5898                                 "video ruby bdi keygen"));
5899                 }
5900
5901                 // Add HTML4 elements unless it's html5-strict
5902                 if (type != "html5-strict") {
5903                         globalAttributes.push("xml:lang");
5904
5905                         html4PhrasingContent = split("acronym applet basefont big font strike tt");
5906                         phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
5907
5908                         each(html4PhrasingContent, function(name) {
5909                                 add(name, "", phrasingContent);
5910                         });
5911
5912                         html4BlockContent = split("center dir isindex noframes");
5913                         blockContent.push.apply(blockContent, html4BlockContent);
5914
5915                         // Flow content elements from the HTML5 spec (block+inline)
5916                         flowContent = [].concat(blockContent, phrasingContent);
5917
5918                         each(html4BlockContent, function(name) {
5919                                 add(name, "", flowContent);
5920                         });
5921                 }
5922
5923                 // Flow content elements from the HTML5 spec (block+inline)
5924                 flowContent = flowContent || [].concat(blockContent, phrasingContent);
5925
5926                 // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
5927                 // Schema items <element name>, <specific attributes>, <children ..>
5928                 add("html", "manifest", "head body");
5929                 add("head", "", "base command link meta noscript script style title");
5930                 add("title hr noscript br");
5931                 add("base", "href target");
5932                 add("link", "href rel media hreflang type sizes hreflang");
5933                 add("meta", "name http-equiv content charset");
5934                 add("style", "media type scoped");
5935                 add("script", "src async defer type charset");
5936                 add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
5937                                 "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
5938                                 "onpopstate onresize onscroll onstorage onunload", flowContent);
5939                 add("address dt dd div caption", "", flowContent);
5940                 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);
5941                 add("blockquote", "cite", flowContent);
5942                 add("ol", "reversed start type", "li");
5943                 add("ul", "", "li");
5944                 add("li", "value", flowContent);
5945                 add("dl", "", "dt dd");
5946                 add("a", "href target rel media hreflang type", phrasingContent);
5947                 add("q", "cite", phrasingContent);
5948                 add("ins del", "cite datetime", flowContent);
5949                 add("img", "src alt usemap ismap width height");
5950                 add("iframe", "src name width height", flowContent);
5951                 add("embed", "src type width height");
5952                 add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
5953                 add("param", "name value");
5954                 add("map", "name", flowContent, "area");
5955                 add("area", "alt coords shape href target rel media hreflang type");
5956                 add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
5957                 add("colgroup", "span", "col");
5958                 add("col", "span");
5959                 add("tbody thead tfoot", "", "tr");
5960                 add("tr", "", "td th");
5961                 add("td", "colspan rowspan headers", flowContent);
5962                 add("th", "colspan rowspan headers scope abbr", flowContent);
5963                 add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
5964                 add("fieldset", "disabled form name", flowContent, "legend");
5965                 add("label", "form for", phrasingContent);
5966                 add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
5967                                 "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
5968                 );
5969                 add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
5970                         type == "html4" ? flowContent : phrasingContent);
5971                 add("select", "disabled form multiple name required size", "option optgroup");
5972                 add("optgroup", "disabled label", "option");
5973                 add("option", "disabled label selected value");
5974                 add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
5975                 add("menu", "type label", flowContent, "li");
5976                 add("noscript", "", flowContent);
5977
5978                 // Extend with HTML5 elements
5979                 if (type != "html4") {
5980                         add("wbr");
5981                         add("ruby", "", phrasingContent, "rt rp");
5982                         add("figcaption", "", flowContent);
5983                         add("mark rt rp summary bdi", "", phrasingContent);
5984                         add("canvas", "width height", flowContent);
5985                         add("video", "src crossorigin poster preload autoplay mediagroup loop " +
5986                                 "muted controls width height", flowContent, "track source");
5987                         add("audio", "src crossorigin preload autoplay mediagroup loop muted controls", flowContent, "track source");
5988                         add("source", "src type media");
5989                         add("track", "kind src srclang label default");
5990                         add("datalist", "", phrasingContent, "option");
5991                         add("article section nav aside header footer", "", flowContent);
5992                         add("hgroup", "", "h1 h2 h3 h4 h5 h6");
5993                         add("figure", "", flowContent, "figcaption");
5994                         add("time", "datetime", phrasingContent);
5995                         add("dialog", "open", flowContent);
5996                         add("command", "type label icon disabled checked radiogroup command");
5997                         add("output", "for form name", phrasingContent);
5998                         add("progress", "value max", phrasingContent);
5999                         add("meter", "value min max low high optimum", phrasingContent);
6000                         add("details", "open", flowContent, "summary");
6001                         add("keygen", "autofocus challenge disabled form keytype name");
6002                 }
6003
6004                 // Extend with HTML4 attributes unless it's html5-strict
6005                 if (type != "html5-strict") {
6006                         addAttrs("script", "language xml:space");
6007                         addAttrs("style", "xml:space");
6008                         addAttrs("object", "declare classid codebase codetype archive standby align border hspace vspace");
6009                         addAttrs("param", "valuetype type");
6010                         addAttrs("a", "charset name rev shape coords");
6011                         addAttrs("br", "clear");
6012                         addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
6013                         addAttrs("img", "name longdesc align border hspace vspace");
6014                         addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
6015                         addAttrs("font basefont", "size color face");
6016                         addAttrs("input", "usemap align");
6017                         addAttrs("select", "onchange");
6018                         addAttrs("textarea");
6019                         addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
6020                         addAttrs("ul", "type compact");
6021                         addAttrs("li", "type");
6022                         addAttrs("ol dl menu dir", "compact");
6023                         addAttrs("pre", "width xml:space");
6024                         addAttrs("hr", "align noshade size width");
6025                         addAttrs("isindex", "prompt");
6026                         addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
6027                         addAttrs("col", "width align char charoff valign");
6028                         addAttrs("colgroup", "width align char charoff valign");
6029                         addAttrs("thead", "align char charoff valign");
6030                         addAttrs("tr", "align char charoff valign bgcolor");
6031                         addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
6032                         addAttrs("form", "accept");
6033                         addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
6034                         addAttrs("tfoot", "align char charoff valign");
6035                         addAttrs("tbody", "align char charoff valign");
6036                         addAttrs("area", "nohref");
6037                         addAttrs("body", "background bgcolor text link vlink alink");
6038                 }
6039
6040                 // Extend with HTML5 attributes unless it's html4
6041                 if (type != "html4") {
6042                         addAttrs("input button select textarea", "autofocus");
6043                         addAttrs("input textarea", "placeholder");
6044                         addAttrs("a", "download");
6045                         addAttrs("link script img", "crossorigin");
6046                         addAttrs("iframe", "srcdoc sandbox seamless allowfullscreen");
6047                 }
6048
6049                 // Special: iframe, ruby, video, audio, label
6050
6051                 // Delete children of the same name from it's parent
6052                 // For example: form can't have a child of the name form
6053                 each(split('a form meter progress dfn'), function(name) {
6054                         if (schema[name]) {
6055                                 delete schema[name].children[name];
6056                         }
6057                 });
6058
6059                 // Delete header, footer, sectioning and heading content descendants
6060                 /*each('dt th address', function(name) {
6061                         delete schema[name].children[name];
6062                 });*/
6063
6064                 // Caption can't have tables
6065                 delete schema.caption.children.table;
6066
6067                 // TODO: LI:s can only have value if parent is OL
6068
6069                 // TODO: Handle transparent elements
6070                 // a ins del canvas map
6071
6072                 mapCache[type] = schema;
6073
6074                 return schema;
6075         }
6076
6077         /**
6078          * Constructs a new Schema instance.
6079          *
6080          * @constructor
6081          * @method Schema
6082          * @param {Object} settings Name/value settings object.
6083          */
6084         return function(settings) {
6085                 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
6086                 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap;
6087                 var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, customElementsMap = {}, specialElements = {};
6088
6089                 // Creates an lookup table map object for the specified option or the default value
6090                 function createLookupTable(option, default_value, extendWith) {
6091                         var value = settings[option];
6092
6093                         if (!value) {
6094                                 // Get cached default map or make it if needed
6095                                 value = mapCache[option];
6096
6097                                 if (!value) {
6098                                         value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
6099                                         value = extend(value, extendWith);
6100
6101                                         mapCache[option] = value;
6102                                 }
6103                         } else {
6104                                 // Create custom map
6105                                 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
6106                         }
6107
6108                         return value;
6109                 }
6110
6111                 settings = settings || {};
6112                 schemaItems = compileSchema(settings.schema);
6113
6114                 // Allow all elements and attributes if verify_html is set to false
6115                 if (settings.verify_html === false) {
6116                         settings.valid_elements = '*[*]';
6117                 }
6118
6119                 // Build styles list
6120                 if (settings.valid_styles) {
6121                         validStyles = {};
6122
6123                         // Convert styles into a rule list
6124                         each(settings.valid_styles, function(value, key) {
6125                                 validStyles[key] = explode(value);
6126                         });
6127                 }
6128
6129                 // Setup map objects
6130                 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
6131                 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
6132                 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
6133                         'meta param embed source wbr track');
6134                 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
6135                         'noshade nowrap readonly selected autoplay loop controls');
6136                 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
6137                 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
6138                                                 'blockquote center dir fieldset header footer article section hgroup aside nav figure');
6139                 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
6140                                                 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option ' +
6141                                                 'datalist select optgroup', textBlockElementsMap);
6142
6143                 each((settings.special || 'script noscript style textarea').split(' '), function(name) {
6144                         specialElements[name] = new RegExp('<\/' + name + '[^>]*>','gi');
6145                 });
6146
6147                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
6148                 function patternToRegExp(str) {
6149                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
6150                 }
6151
6152                 // Parses the specified valid_elements string and adds to the current rules
6153                 // This function is a bit hard to read since it's heavily optimized for speed
6154                 function addValidElements(valid_elements) {
6155                         var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
6156                                 prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
6157                                 elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
6158                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
6159                                 hasPatternsRegExp = /[*?+]/;
6160
6161                         if (valid_elements) {
6162                                 // Split valid elements into an array with rules
6163                                 valid_elements = split(valid_elements, ',');
6164
6165                                 if (elements['@']) {
6166                                         globalAttributes = elements['@'].attributes;
6167                                         globalAttributesOrder = elements['@'].attributesOrder;
6168                                 }
6169
6170                                 // Loop all rules
6171                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
6172                                         // Parse element rule
6173                                         matches = elementRuleRegExp.exec(valid_elements[ei]);
6174                                         if (matches) {
6175                                                 // Setup local names for matches
6176                                                 prefix = matches[1];
6177                                                 elementName = matches[2];
6178                                                 outputName = matches[3];
6179                                                 attrData = matches[5];
6180
6181                                                 // Create new attributes and attributesOrder
6182                                                 attributes = {};
6183                                                 attributesOrder = [];
6184
6185                                                 // Create the new element
6186                                                 element = {
6187                                                         attributes: attributes,
6188                                                         attributesOrder: attributesOrder
6189                                                 };
6190
6191                                                 // Padd empty elements prefix
6192                                                 if (prefix === '#') {
6193                                                         element.paddEmpty = true;
6194                                                 }
6195
6196                                                 // Remove empty elements prefix
6197                                                 if (prefix === '-') {
6198                                                         element.removeEmpty = true;
6199                                                 }
6200
6201                                                 if (matches[4] === '!') {
6202                                                         element.removeEmptyAttrs = true;
6203                                                 }
6204
6205                                                 // Copy attributes from global rule into current rule
6206                                                 if (globalAttributes) {
6207                                                         for (key in globalAttributes) {
6208                                                                 attributes[key] = globalAttributes[key];
6209                                                         }
6210
6211                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
6212                                                 }
6213
6214                                                 // Attributes defined
6215                                                 if (attrData) {
6216                                                         attrData = split(attrData, '|');
6217                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {
6218                                                                 matches = attrRuleRegExp.exec(attrData[ai]);
6219                                                                 if (matches) {
6220                                                                         attr = {};
6221                                                                         attrType = matches[1];
6222                                                                         attrName = matches[2].replace(/::/g, ':');
6223                                                                         prefix = matches[3];
6224                                                                         value = matches[4];
6225
6226                                                                         // Required
6227                                                                         if (attrType === '!') {
6228                                                                                 element.attributesRequired = element.attributesRequired || [];
6229                                                                                 element.attributesRequired.push(attrName);
6230                                                                                 attr.required = true;
6231                                                                         }
6232
6233                                                                         // Denied from global
6234                                                                         if (attrType === '-') {
6235                                                                                 delete attributes[attrName];
6236                                                                                 attributesOrder.splice(inArray(attributesOrder, attrName), 1);
6237                                                                                 continue;
6238                                                                         }
6239
6240                                                                         // Default value
6241                                                                         if (prefix) {
6242                                                                                 // Default value
6243                                                                                 if (prefix === '=') {
6244                                                                                         element.attributesDefault = element.attributesDefault || [];
6245                                                                                         element.attributesDefault.push({name: attrName, value: value});
6246                                                                                         attr.defaultValue = value;
6247                                                                                 }
6248
6249                                                                                 // Forced value
6250                                                                                 if (prefix === ':') {
6251                                                                                         element.attributesForced = element.attributesForced || [];
6252                                                                                         element.attributesForced.push({name: attrName, value: value});
6253                                                                                         attr.forcedValue = value;
6254                                                                                 }
6255
6256                                                                                 // Required values
6257                                                                                 if (prefix === '<') {
6258                                                                                         attr.validValues = makeMap(value, '?');
6259                                                                                 }
6260                                                                         }
6261
6262                                                                         // Check for attribute patterns
6263                                                                         if (hasPatternsRegExp.test(attrName)) {
6264                                                                                 element.attributePatterns = element.attributePatterns || [];
6265                                                                                 attr.pattern = patternToRegExp(attrName);
6266                                                                                 element.attributePatterns.push(attr);
6267                                                                         } else {
6268                                                                                 // Add attribute to order list if it doesn't already exist
6269                                                                                 if (!attributes[attrName]) {
6270                                                                                         attributesOrder.push(attrName);
6271                                                                                 }
6272
6273                                                                                 attributes[attrName] = attr;
6274                                                                         }
6275                                                                 }
6276                                                         }
6277                                                 }
6278
6279                                                 // Global rule, store away these for later usage
6280                                                 if (!globalAttributes && elementName == '@') {
6281                                                         globalAttributes = attributes;
6282                                                         globalAttributesOrder = attributesOrder;
6283                                                 }
6284
6285                                                 // Handle substitute elements such as b/strong
6286                                                 if (outputName) {
6287                                                         element.outputName = elementName;
6288                                                         elements[outputName] = element;
6289                                                 }
6290
6291                                                 // Add pattern or exact element
6292                                                 if (hasPatternsRegExp.test(elementName)) {
6293                                                         element.pattern = patternToRegExp(elementName);
6294                                                         patternElements.push(element);
6295                                                 } else {
6296                                                         elements[elementName] = element;
6297                                                 }
6298                                         }
6299                                 }
6300                         }
6301                 }
6302
6303                 function setValidElements(valid_elements) {
6304                         elements = {};
6305                         patternElements = [];
6306
6307                         addValidElements(valid_elements);
6308
6309                         each(schemaItems, function(element, name) {
6310                                 children[name] = element.children;
6311                         });
6312                 }
6313
6314                 // Adds custom non HTML elements to the schema
6315                 function addCustomElements(custom_elements) {
6316                         var customElementRegExp = /^(~)?(.+)$/;
6317
6318                         if (custom_elements) {
6319                                 each(split(custom_elements, ','), function(rule) {
6320                                         var matches = customElementRegExp.exec(rule),
6321                                                 inline = matches[1] === '~',
6322                                                 cloneName = inline ? 'span' : 'div',
6323                                                 name = matches[2];
6324
6325                                         children[name] = children[cloneName];
6326                                         customElementsMap[name] = cloneName;
6327
6328                                         // If it's not marked as inline then add it to valid block elements
6329                                         if (!inline) {
6330                                                 blockElementsMap[name.toUpperCase()] = {};
6331                                                 blockElementsMap[name] = {};
6332                                         }
6333
6334                                         // Add elements clone if needed
6335                                         if (!elements[name]) {
6336                                                 var customRule = elements[cloneName];
6337
6338                                                 customRule = extend({}, customRule);
6339                                                 delete customRule.removeEmptyAttrs;
6340                                                 delete customRule.removeEmpty;
6341
6342                                                 elements[name] = customRule;
6343                                         }
6344
6345                                         // Add custom elements at span/div positions
6346                                         each(children, function(element) {
6347                                                 if (element[cloneName]) {
6348                                                         element[name] = element[cloneName];
6349                                                 }
6350                                         });
6351                                 });
6352                         }
6353                 }
6354
6355                 // Adds valid children to the schema object
6356                 function addValidChildren(valid_children) {
6357                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
6358
6359                         if (valid_children) {
6360                                 each(split(valid_children, ','), function(rule) {
6361                                         var matches = childRuleRegExp.exec(rule), parent, prefix;
6362
6363                                         if (matches) {
6364                                                 prefix = matches[1];
6365
6366                                                 // Add/remove items from default
6367                                                 if (prefix) {
6368                                                         parent = children[matches[2]];
6369                                                 } else {
6370                                                         parent = children[matches[2]] = {'#comment': {}};
6371                                                 }
6372
6373                                                 parent = children[matches[2]];
6374
6375                                                 each(split(matches[3], '|'), function(child) {
6376                                                         if (prefix === '-') {
6377                                                                 delete parent[child];
6378                                                         } else {
6379                                                                 parent[child] = {};
6380                                                         }
6381                                                 });
6382                                         }
6383                                 });
6384                         }
6385                 }
6386
6387                 function getElementRule(name) {
6388                         var element = elements[name], i;
6389
6390                         // Exact match found
6391                         if (element) {
6392                                 return element;
6393                         }
6394
6395                         // No exact match then try the patterns
6396                         i = patternElements.length;
6397                         while (i--) {
6398                                 element = patternElements[i];
6399
6400                                 if (element.pattern.test(name)) {
6401                                         return element;
6402                                 }
6403                         }
6404                 }
6405
6406                 if (!settings.valid_elements) {
6407                         // No valid elements defined then clone the elements from the schema spec
6408                         each(schemaItems, function(element, name) {
6409                                 elements[name] = {
6410                                         attributes: element.attributes,
6411                                         attributesOrder: element.attributesOrder
6412                                 };
6413
6414                                 children[name] = element.children;
6415                         });
6416
6417                         // Switch these on HTML4
6418                         if (settings.schema != "html5") {
6419                                 each(split('strong/b em/i'), function(item) {
6420                                         item = split(item, '/');
6421                                         elements[item[1]].outputName = item[0];
6422                                 });
6423                         }
6424
6425                         // Add default alt attribute for images
6426                         elements.img.attributesDefault = [{name: 'alt', value: ''}];
6427
6428                         // Remove these if they are empty by default
6429                         each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
6430                                 if (elements[name]) {
6431                                         elements[name].removeEmpty = true;
6432                                 }
6433                         });
6434
6435                         // Padd these by default
6436                         each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
6437                                 elements[name].paddEmpty = true;
6438                         });
6439
6440                         // Remove these if they have no attributes
6441                         each(split('span'), function(name) {
6442                                 elements[name].removeEmptyAttrs = true;
6443                         });
6444                 } else {
6445                         setValidElements(settings.valid_elements);
6446                 }
6447
6448                 addCustomElements(settings.custom_elements);
6449                 addValidChildren(settings.valid_children);
6450                 addValidElements(settings.extended_valid_elements);
6451
6452                 // Todo: Remove this when we fix list handling to be valid
6453                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
6454
6455                 // Delete invalid elements
6456                 if (settings.invalid_elements) {
6457                         each(explode(settings.invalid_elements), function(item) {
6458                                 if (elements[item]) {
6459                                         delete elements[item];
6460                                 }
6461                         });
6462                 }
6463
6464                 // If the user didn't allow span only allow internal spans
6465                 if (!getElementRule('span')) {
6466                         addValidElements('span[!data-mce-type|*]');
6467                 }
6468
6469                 /**
6470                  * Name/value map object with valid parents and children to those parents.
6471                  *
6472                  * @example
6473                  * children = {
6474                  *    div:{p:{}, h1:{}}
6475                  * };
6476                  * @field children
6477                  * @type Object
6478                  */
6479                 self.children = children;
6480
6481                 /**
6482                  * Name/value map object with valid styles for each element.
6483                  *
6484                  * @field styles
6485                  * @type Object
6486                  */
6487                 self.styles = validStyles;
6488
6489                 /**
6490                  * Returns a map with boolean attributes.
6491                  *
6492                  * @method getBoolAttrs
6493                  * @return {Object} Name/value lookup map for boolean attributes.
6494                  */
6495                 self.getBoolAttrs = function() {
6496                         return boolAttrMap;
6497                 };
6498
6499                 /**
6500                  * Returns a map with block elements.
6501                  *
6502                  * @method getBlockElements
6503                  * @return {Object} Name/value lookup map for block elements.
6504                  */
6505                 self.getBlockElements = function() {
6506                         return blockElementsMap;
6507                 };
6508
6509                 /**
6510                  * Returns a map with text block elements. Such as: p,h1-h6,div,address
6511                  *
6512                  * @method getTextBlockElements
6513                  * @return {Object} Name/value lookup map for block elements.
6514                  */
6515                 self.getTextBlockElements = function() {
6516                         return textBlockElementsMap;
6517                 };
6518
6519                 /**
6520                  * Returns a map with short ended elements such as BR or IMG.
6521                  *
6522                  * @method getShortEndedElements
6523                  * @return {Object} Name/value lookup map for short ended elements.
6524                  */
6525                 self.getShortEndedElements = function() {
6526                         return shortEndedElementsMap;
6527                 };
6528
6529                 /**
6530                  * Returns a map with self closing tags such as <li>.
6531                  *
6532                  * @method getSelfClosingElements
6533                  * @return {Object} Name/value lookup map for self closing tags elements.
6534                  */
6535                 self.getSelfClosingElements = function() {
6536                         return selfClosingElementsMap;
6537                 };
6538
6539                 /**
6540                  * Returns a map with elements that should be treated as contents regardless if it has text
6541                  * content in them or not such as TD, VIDEO or IMG.
6542                  *
6543                  * @method getNonEmptyElements
6544                  * @return {Object} Name/value lookup map for non empty elements.
6545                  */
6546                 self.getNonEmptyElements = function() {
6547                         return nonEmptyElementsMap;
6548                 };
6549
6550                 /**
6551                  * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
6552                  *
6553                  * @method getWhiteSpaceElements
6554                  * @return {Object} Name/value lookup map for white space elements.
6555                  */
6556                 self.getWhiteSpaceElements = function() {
6557                         return whiteSpaceElementsMap;
6558                 };
6559
6560                 /**
6561                  * Returns a map with special elements. These are elements that needs to be parsed
6562                  * in a special way such as script, style, textarea etc. The map object values
6563                  * are regexps used to find the end of the element.
6564                  *
6565                  * @method getSpecialElements
6566                  * @return {Object} Name/value lookup map for special elements.
6567                  */
6568                 self.getSpecialElements = function() {
6569                         return specialElements;
6570                 };
6571
6572                 /**
6573                  * Returns true/false if the specified element and it's child is valid or not
6574                  * according to the schema.
6575                  *
6576                  * @method isValidChild
6577                  * @param {String} name Element name to check for.
6578                  * @param {String} child Element child to verify.
6579                  * @return {Boolean} True/false if the element is a valid child of the specified parent.
6580                  */
6581                 self.isValidChild = function(name, child) {
6582                         var parent = children[name];
6583
6584                         return !!(parent && parent[child]);
6585                 };
6586
6587                 /**
6588                  * Returns true/false if the specified element name and optional attribute is
6589                  * valid according to the schema.
6590                  *
6591                  * @method isValid
6592                  * @param {String} name Name of element to check.
6593                  * @param {String} attr Optional attribute name to check for.
6594                  * @return {Boolean} True/false if the element and attribute is valid.
6595                  */
6596                 self.isValid = function(name, attr) {
6597                         var attrPatterns, i, rule = getElementRule(name);
6598
6599                         // Check if it's a valid element
6600                         if (rule) {
6601                                 if (attr) {
6602                                         // Check if attribute name exists
6603                                         if (rule.attributes[attr]) {
6604                                                 return true;
6605                                         }
6606
6607                                         // Check if attribute matches a regexp pattern
6608                                         attrPatterns = rule.attributePatterns;
6609                                         if (attrPatterns) {
6610                                                 i = attrPatterns.length;
6611                                                 while (i--) {
6612                                                         if (attrPatterns[i].pattern.test(name)) {
6613                                                                 return true;
6614                                                         }
6615                                                 }
6616                                         }
6617                                 } else {
6618                                         return true;
6619                                 }
6620                         }
6621
6622                         // No match
6623                         return false;
6624                 };
6625
6626                 /**
6627                  * Returns true/false if the specified element is valid or not
6628                  * according to the schema.
6629                  *
6630                  * @method getElementRule
6631                  * @param {String} name Element name to check for.
6632                  * @return {Object} Element object or undefined if the element isn't valid.
6633                  */
6634                 self.getElementRule = getElementRule;
6635
6636                 /**
6637                  * Returns an map object of all custom elements.
6638                  *
6639                  * @method getCustomElements
6640                  * @return {Object} Name/value map object of all custom elements.
6641                  */
6642                 self.getCustomElements = function() {
6643                         return customElementsMap;
6644                 };
6645
6646                 /**
6647                  * Parses a valid elements string and adds it to the schema. The valid elements
6648                   format is for example "element[attr=default|otherattr]".
6649                  * Existing rules will be replaced with the ones specified, so this extends the schema.
6650                  *
6651                  * @method addValidElements
6652                  * @param {String} valid_elements String in the valid elements format to be parsed.
6653                  */
6654                 self.addValidElements = addValidElements;
6655
6656                 /**
6657                  * Parses a valid elements string and sets it to the schema. The valid elements
6658                  * format is for example "element[attr=default|otherattr]".
6659                  * Existing rules will be replaced with the ones specified, so this extends the schema.
6660                  *
6661                  * @method setValidElements
6662                  * @param {String} valid_elements String in the valid elements format to be parsed.
6663                  */
6664                 self.setValidElements = setValidElements;
6665
6666                 /**
6667                  * Adds custom non HTML elements to the schema.
6668                  *
6669                  * @method addCustomElements
6670                  * @param {String} custom_elements Comma separated list of custom elements to add.
6671                  */
6672                 self.addCustomElements = addCustomElements;
6673
6674                 /**
6675                  * Parses a valid children string and adds them to the schema structure. The valid children
6676                  * format is for example: "element[child1|child2]".
6677                  *
6678                  * @method addValidChildren
6679                  * @param {String} valid_children Valid children elements string to parse
6680                  */
6681                 self.addValidChildren = addValidChildren;
6682
6683                 self.elements = elements;
6684         };
6685 });
6686
6687 // Included from: js/tinymce/classes/html/SaxParser.js
6688
6689 /**
6690  * SaxParser.js
6691  *
6692  * Copyright, Moxiecode Systems AB
6693  * Released under LGPL License.
6694  *
6695  * License: http://www.tinymce.com/license
6696  * Contributing: http://www.tinymce.com/contributing
6697  */
6698
6699 /**
6700  * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
6701  * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
6702  * and attributes that doesn't fit the schema if the validate setting is enabled.
6703  *
6704  * @example
6705  * var parser = new tinymce.html.SaxParser({
6706  *     validate: true,
6707  *
6708  *     comment: function(text) {
6709  *         console.log('Comment:', text);
6710  *     },
6711  *
6712  *     cdata: function(text) {
6713  *         console.log('CDATA:', text);
6714  *     },
6715  *
6716  *     text: function(text, raw) {
6717  *         console.log('Text:', text, 'Raw:', raw);
6718  *     },
6719  *
6720  *     start: function(name, attrs, empty) {
6721  *         console.log('Start:', name, attrs, empty);
6722  *     },
6723  *
6724  *     end: function(name) {
6725  *         console.log('End:', name);
6726  *     },
6727  *
6728  *     pi: function(name, text) {
6729  *         console.log('PI:', name, text);
6730  *     },
6731  *
6732  *     doctype: function(text) {
6733  *         console.log('DocType:', text);
6734  *     }
6735  * }, schema);
6736  * @class tinymce.html.SaxParser
6737  * @version 3.4
6738  */
6739 define("tinymce/html/SaxParser", [
6740         "tinymce/html/Schema",
6741         "tinymce/html/Entities",
6742         "tinymce/util/Tools"
6743 ], function(Schema, Entities, Tools) {
6744         var each = Tools.each;
6745
6746         /**
6747          * Constructs a new SaxParser instance.
6748          *
6749          * @constructor
6750          * @method SaxParser
6751          * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
6752          * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
6753          */
6754         return function(settings, schema) {
6755                 var self = this, noop = function() {};
6756
6757                 settings = settings || {};
6758                 self.schema = schema = schema || new Schema();
6759
6760                 if (settings.fix_self_closing !== false) {
6761                         settings.fix_self_closing = true;
6762                 }
6763
6764                 // Add handler functions from settings and setup default handlers
6765                 each('comment cdata text start end pi doctype'.split(' '), function(name) {
6766                         if (name) {
6767                                 self[name] = settings[name] || noop;
6768                         }
6769                 });
6770
6771                 /**
6772                  * Parses the specified HTML string and executes the callbacks for each item it finds.
6773                  *
6774                  * @example
6775                  * new SaxParser({...}).parse('<b>text</b>');
6776                  * @method parse
6777                  * @param {String} html Html string to sax parse.
6778                  */
6779                 self.parse = function(html) {
6780                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
6781                         var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
6782                         var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
6783                         var attributesRequired, attributesDefault, attributesForced;
6784                         var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
6785                         var decode = Entities.decode, fixSelfClosing;
6786
6787                         function processEndTag(name) {
6788                                 var pos, i;
6789
6790                                 // Find position of parent of the same type
6791                                 pos = stack.length;
6792                                 while (pos--) {
6793                                         if (stack[pos].name === name) {
6794                                                 break;
6795                                         }
6796                                 }
6797
6798                                 // Found parent
6799                                 if (pos >= 0) {
6800                                         // Close all the open elements
6801                                         for (i = stack.length - 1; i >= pos; i--) {
6802                                                 name = stack[i];
6803
6804                                                 if (name.valid) {
6805                                                         self.end(name.name);
6806                                                 }
6807                                         }
6808
6809                                         // Remove the open elements from the stack
6810                                         stack.length = pos;
6811                                 }
6812                         }
6813
6814                         function parseAttribute(match, name, value, val2, val3) {
6815                                 var attrRule, i;
6816
6817                                 name = name.toLowerCase();
6818                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
6819
6820                                 // Validate name and value pass through all data- attributes
6821                                 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
6822                                         attrRule = validAttributesMap[name];
6823
6824                                         // Find rule by pattern matching
6825                                         if (!attrRule && validAttributePatterns) {
6826                                                 i = validAttributePatterns.length;
6827                                                 while (i--) {
6828                                                         attrRule = validAttributePatterns[i];
6829                                                         if (attrRule.pattern.test(name)) {
6830                                                                 break;
6831                                                         }
6832                                                 }
6833
6834                                                 // No rule matched
6835                                                 if (i === -1) {
6836                                                         attrRule = null;
6837                                                 }
6838                                         }
6839
6840                                         // No attribute rule found
6841                                         if (!attrRule) {
6842                                                 return;
6843                                         }
6844
6845                                         // Validate value
6846                                         if (attrRule.validValues && !(value in attrRule.validValues)) {
6847                                                 return;
6848                                         }
6849                                 }
6850
6851                                 // Add attribute to list and map
6852                                 attrList.map[name] = value;
6853                                 attrList.push({
6854                                         name: name,
6855                                         value: value
6856                                 });
6857                         }
6858
6859                         // Precompile RegExps and map objects
6860                         tokenRegExp = new RegExp('<(?:' +
6861                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
6862                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
6863                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
6864                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
6865                                 '(?:\\/([^>]+)>)|' + // End element
6866                                 '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
6867                         ')', 'g');
6868
6869                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
6870
6871                         // Setup lookup tables for empty elements and boolean attributes
6872                         shortEndedElements = schema.getShortEndedElements();
6873                         selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
6874                         fillAttrsMap = schema.getBoolAttrs();
6875                         validate = settings.validate;
6876                         removeInternalElements = settings.remove_internals;
6877                         fixSelfClosing = settings.fix_self_closing;
6878                         specialElements = schema.getSpecialElements();
6879
6880                         while ((matches = tokenRegExp.exec(html))) {
6881                                 // Text
6882                                 if (index < matches.index) {
6883                                         self.text(decode(html.substr(index, matches.index - index)));
6884                                 }
6885
6886                                 if ((value = matches[6])) { // End element
6887                                         value = value.toLowerCase();
6888
6889                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
6890                                         if (value.charAt(0) === ':') {
6891                                                 value = value.substr(1);
6892                                         }
6893
6894                                         processEndTag(value);
6895                                 } else if ((value = matches[7])) { // Start element
6896                                         value = value.toLowerCase();
6897
6898                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
6899                                         if (value.charAt(0) === ':') {
6900                                                 value = value.substr(1);
6901                                         }
6902
6903                                         isShortEnded = value in shortEndedElements;
6904
6905                                         // Is self closing tag for example an <li> after an open <li>
6906                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
6907                                                 processEndTag(value);
6908                                         }
6909
6910                                         // Validate element
6911                                         if (!validate || (elementRule = schema.getElementRule(value))) {
6912                                                 isValidElement = true;
6913
6914                                                 // Grab attributes map and patters when validation is enabled
6915                                                 if (validate) {
6916                                                         validAttributesMap = elementRule.attributes;
6917                                                         validAttributePatterns = elementRule.attributePatterns;
6918                                                 }
6919
6920                                                 // Parse attributes
6921                                                 if ((attribsValue = matches[8])) {
6922                                                         isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
6923
6924                                                         // If the element has internal attributes then remove it if we are told to do so
6925                                                         if (isInternalElement && removeInternalElements) {
6926                                                                 isValidElement = false;
6927                                                         }
6928
6929                                                         attrList = [];
6930                                                         attrList.map = {};
6931
6932                                                         attribsValue.replace(attrRegExp, parseAttribute);
6933                                                 } else {
6934                                                         attrList = [];
6935                                                         attrList.map = {};
6936                                                 }
6937
6938                                                 // Process attributes if validation is enabled
6939                                                 if (validate && !isInternalElement) {
6940                                                         attributesRequired = elementRule.attributesRequired;
6941                                                         attributesDefault = elementRule.attributesDefault;
6942                                                         attributesForced = elementRule.attributesForced;
6943                                                         anyAttributesRequired = elementRule.removeEmptyAttrs;
6944
6945                                                         // Check if any attribute exists
6946                                                         if (anyAttributesRequired && !attrList.length) {
6947                                                                 isValidElement = false;
6948                                                         }
6949
6950                                                         // Handle forced attributes
6951                                                         if (attributesForced) {
6952                                                                 i = attributesForced.length;
6953                                                                 while (i--) {
6954                                                                         attr = attributesForced[i];
6955                                                                         name = attr.name;
6956                                                                         attrValue = attr.value;
6957
6958                                                                         if (attrValue === '{$uid}') {
6959                                                                                 attrValue = 'mce_' + idCount++;
6960                                                                         }
6961
6962                                                                         attrList.map[name] = attrValue;
6963                                                                         attrList.push({name: name, value: attrValue});
6964                                                                 }
6965                                                         }
6966
6967                                                         // Handle default attributes
6968                                                         if (attributesDefault) {
6969                                                                 i = attributesDefault.length;
6970                                                                 while (i--) {
6971                                                                         attr = attributesDefault[i];
6972                                                                         name = attr.name;
6973
6974                                                                         if (!(name in attrList.map)) {
6975                                                                                 attrValue = attr.value;
6976
6977                                                                                 if (attrValue === '{$uid}') {
6978                                                                                         attrValue = 'mce_' + idCount++;
6979                                                                                 }
6980
6981                                                                                 attrList.map[name] = attrValue;
6982                                                                                 attrList.push({name: name, value: attrValue});
6983                                                                         }
6984                                                                 }
6985                                                         }
6986
6987                                                         // Handle required attributes
6988                                                         if (attributesRequired) {
6989                                                                 i = attributesRequired.length;
6990                                                                 while (i--) {
6991                                                                         if (attributesRequired[i] in attrList.map) {
6992                                                                                 break;
6993                                                                         }
6994                                                                 }
6995
6996                                                                 // None of the required attributes where found
6997                                                                 if (i === -1) {
6998                                                                         isValidElement = false;
6999                                                                 }
7000                                                         }
7001
7002                                                         // Invalidate element if it's marked as bogus
7003                                                         if (attrList.map['data-mce-bogus']) {
7004                                                                 isValidElement = false;
7005                                                         }
7006                                                 }
7007
7008                                                 if (isValidElement) {
7009                                                         self.start(value, attrList, isShortEnded);
7010                                                 }
7011                                         } else {
7012                                                 isValidElement = false;
7013                                         }
7014
7015                                         // Treat script, noscript and style a bit different since they may include code that looks like elements
7016                                         if ((endRegExp = specialElements[value])) {
7017                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;
7018
7019                                                 if ((matches = endRegExp.exec(html))) {
7020                                                         if (isValidElement) {
7021                                                                 text = html.substr(index, matches.index - index);
7022                                                         }
7023
7024                                                         index = matches.index + matches[0].length;
7025                                                 } else {
7026                                                         text = html.substr(index);
7027                                                         index = html.length;
7028                                                 }
7029
7030                                                 if (isValidElement) {
7031                                                         if (text.length > 0) {
7032                                                                 self.text(text, true);
7033                                                         }
7034
7035                                                         self.end(value);
7036                                                 }
7037
7038                                                 tokenRegExp.lastIndex = index;
7039                                                 continue;
7040                                         }
7041
7042                                         // Push value on to stack
7043                                         if (!isShortEnded) {
7044                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
7045                                                         stack.push({name: value, valid: isValidElement});
7046                                                 } else if (isValidElement) {
7047                                                         self.end(value);
7048                                                 }
7049                                         }
7050                                 } else if ((value = matches[1])) { // Comment
7051                                         self.comment(value);
7052                                 } else if ((value = matches[2])) { // CDATA
7053                                         self.cdata(value);
7054                                 } else if ((value = matches[3])) { // DOCTYPE
7055                                         self.doctype(value);
7056                                 } else if ((value = matches[4])) { // PI
7057                                         self.pi(value, matches[5]);
7058                                 }
7059
7060                                 index = matches.index + matches[0].length;
7061                         }
7062
7063                         // Text
7064                         if (index < html.length) {
7065                                 self.text(decode(html.substr(index)));
7066                         }
7067
7068                         // Close any open elements
7069                         for (i = stack.length - 1; i >= 0; i--) {
7070                                 value = stack[i];
7071
7072                                 if (value.valid) {
7073                                         self.end(value.name);
7074                                 }
7075                         }
7076                 };
7077         };
7078 });
7079
7080 // Included from: js/tinymce/classes/html/DomParser.js
7081
7082 /**
7083  * DomParser.js
7084  *
7085  * Copyright, Moxiecode Systems AB
7086  * Released under LGPL License.
7087  *
7088  * License: http://www.tinymce.com/license
7089  * Contributing: http://www.tinymce.com/contributing
7090  */
7091
7092 /**
7093  * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
7094  * sure that the node tree is valid according to the specified schema.
7095  * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
7096  *
7097  * @example
7098  * var parser = new tinymce.html.DomParser({validate: true}, schema);
7099  * var rootNode = parser.parse('<h1>content</h1>');
7100  *
7101  * @class tinymce.html.DomParser
7102  * @version 3.4
7103  */
7104 define("tinymce/html/DomParser", [
7105         "tinymce/html/Node",
7106         "tinymce/html/Schema",
7107         "tinymce/html/SaxParser",
7108         "tinymce/util/Tools"
7109 ], function(Node, Schema, SaxParser, Tools) {
7110         var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
7111
7112         /**
7113          * Constructs a new DomParser instance.
7114          *
7115          * @constructor
7116          * @method DomParser
7117          * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
7118          * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
7119          */
7120         return function(settings, schema) {
7121                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
7122
7123                 settings = settings || {};
7124                 settings.validate = "validate" in settings ? settings.validate : true;
7125                 settings.root_name = settings.root_name || 'body';
7126                 self.schema = schema = schema || new Schema();
7127
7128                 function fixInvalidChildren(nodes) {
7129                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
7130                         var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
7131
7132                         nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
7133                         nonEmptyElements = schema.getNonEmptyElements();
7134                         textBlockElements = schema.getTextBlockElements();
7135
7136                         for (ni = 0; ni < nodes.length; ni++) {
7137                                 node = nodes[ni];
7138
7139                                 // Already removed or fixed
7140                                 if (!node.parent || node.fixed) {
7141                                         continue;
7142                                 }
7143
7144                                 // If the invalid element is a text block and the text block is within a parent LI element
7145                                 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
7146                                 if (textBlockElements[node.name] && node.parent.name == 'li') {
7147                                         // Move sibling text blocks after LI element
7148                                         sibling = node.next;
7149                                         while (sibling) {
7150                                                 if (textBlockElements[sibling.name]) {
7151                                                         sibling.name = 'li';
7152                                                         sibling.fixed = true;
7153                                                         node.parent.insert(sibling, node.parent);
7154                                                 } else {
7155                                                         break;
7156                                                 }
7157
7158                                                 sibling = sibling.next;
7159                                         }
7160
7161                                         // Unwrap current text block
7162                                         node.unwrap(node);
7163                                         continue;
7164                                 }
7165
7166                                 // Get list of all parent nodes until we find a valid parent to stick the child into
7167                                 parents = [node];
7168                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
7169                                         !nonSplitableElements[parent.name]; parent = parent.parent) {
7170                                         parents.push(parent);
7171                                 }
7172
7173                                 // Found a suitable parent
7174                                 if (parent && parents.length > 1) {
7175                                         // Reverse the array since it makes looping easier
7176                                         parents.reverse();
7177
7178                                         // Clone the related parent and insert that after the moved node
7179                                         newParent = currentNode = self.filterNode(parents[0].clone());
7180
7181                                         // Start cloning and moving children on the left side of the target node
7182                                         for (i = 0; i < parents.length - 1; i++) {
7183                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {
7184                                                         tempNode = self.filterNode(parents[i].clone());
7185                                                         currentNode.append(tempNode);
7186                                                 } else {
7187                                                         tempNode = currentNode;
7188                                                 }
7189
7190                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
7191                                                         nextNode = childNode.next;
7192                                                         tempNode.append(childNode);
7193                                                         childNode = nextNode;
7194                                                 }
7195
7196                                                 currentNode = tempNode;
7197                                         }
7198
7199                                         if (!newParent.isEmpty(nonEmptyElements)) {
7200                                                 parent.insert(newParent, parents[0], true);
7201                                                 parent.insert(node, newParent);
7202                                         } else {
7203                                                 parent.insert(node, parents[0], true);
7204                                         }
7205
7206                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
7207                                         parent = parents[0];
7208                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
7209                                                 parent.empty().remove();
7210                                         }
7211                                 } else if (node.parent) {
7212                                         // If it's an LI try to find a UL/OL for it or wrap it
7213                                         if (node.name === 'li') {
7214                                                 sibling = node.prev;
7215                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
7216                                                         sibling.append(node);
7217                                                         continue;
7218                                                 }
7219
7220                                                 sibling = node.next;
7221                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
7222                                                         sibling.insert(node, sibling.firstChild, true);
7223                                                         continue;
7224                                                 }
7225
7226                                                 node.wrap(self.filterNode(new Node('ul', 1)));
7227                                                 continue;
7228                                         }
7229
7230                                         // Try wrapping the element in a DIV
7231                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
7232                                                 node.wrap(self.filterNode(new Node('div', 1)));
7233                                         } else {
7234                                                 // We failed wrapping it, then remove or unwrap it
7235                                                 if (node.name === 'style' || node.name === 'script') {
7236                                                         node.empty().remove();
7237                                                 } else {
7238                                                         node.unwrap();
7239                                                 }
7240                                         }
7241                                 }
7242                         }
7243                 }
7244
7245                 /**
7246                  * Runs the specified node though the element and attributes filters.
7247                  *
7248                  * @method filterNode
7249                  * @param {tinymce.html.Node} Node the node to run filters on.
7250                  * @return {tinymce.html.Node} The passed in node.
7251                  */
7252                 self.filterNode = function(node) {
7253                         var i, name, list;
7254
7255                         // Run element filters
7256                         if (name in nodeFilters) {
7257                                 list = matchedNodes[name];
7258
7259                                 if (list) {
7260                                         list.push(node);
7261                                 } else {
7262                                         matchedNodes[name] = [node];
7263                                 }
7264                         }
7265
7266                         // Run attribute filters
7267                         i = attributeFilters.length;
7268                         while (i--) {
7269                                 name = attributeFilters[i].name;
7270
7271                                 if (name in node.attributes.map) {
7272                                         list = matchedAttributes[name];
7273
7274                                         if (list) {
7275                                                 list.push(node);
7276                                         } else {
7277                                                 matchedAttributes[name] = [node];
7278                                         }
7279                                 }
7280                         }
7281
7282                         return node;
7283                 };
7284
7285                 /**
7286                  * Adds a node filter function to the parser, the parser will collect the specified nodes by name
7287                  * and then execute the callback ones it has finished parsing the document.
7288                  *
7289                  * @example
7290                  * parser.addNodeFilter('p,h1', function(nodes, name) {
7291                  *              for (var i = 0; i < nodes.length; i++) {
7292                  *                      console.log(nodes[i].name);
7293                  *              }
7294                  * });
7295                  * @method addNodeFilter
7296                  * @method {String} name Comma separated list of nodes to collect.
7297                  * @param {function} callback Callback function to execute once it has collected nodes.
7298                  */
7299                 self.addNodeFilter = function(name, callback) {
7300                         each(explode(name), function(name) {
7301                                 var list = nodeFilters[name];
7302
7303                                 if (!list) {
7304                                         nodeFilters[name] = list = [];
7305                                 }
7306
7307                                 list.push(callback);
7308                         });
7309                 };
7310
7311                 /**
7312                  * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
7313                  * and then execute the callback ones it has finished parsing the document.
7314                  *
7315                  * @example
7316                  * parser.addAttributeFilter('src,href', function(nodes, name) {
7317                  *              for (var i = 0; i < nodes.length; i++) {
7318                  *                      console.log(nodes[i].name);
7319                  *              }
7320                  * });
7321                  * @method addAttributeFilter
7322                  * @method {String} name Comma separated list of nodes to collect.
7323                  * @param {function} callback Callback function to execute once it has collected nodes.
7324                  */
7325                 self.addAttributeFilter = function(name, callback) {
7326                         each(explode(name), function(name) {
7327                                 var i;
7328
7329                                 for (i = 0; i < attributeFilters.length; i++) {
7330                                         if (attributeFilters[i].name === name) {
7331                                                 attributeFilters[i].callbacks.push(callback);
7332                                                 return;
7333                                         }
7334                                 }
7335
7336                                 attributeFilters.push({name: name, callbacks: [callback]});
7337                         });
7338                 };
7339
7340                 /**
7341                  * Parses the specified HTML string into a DOM like node tree and returns the result.
7342                  *
7343                  * @example
7344                  * var rootNode = new DomParser({...}).parse('<b>text</b>');
7345                  * @method parse
7346                  * @param {String} html Html string to sax parse.
7347                  * @param {Object} args Optional args object that gets passed to all filter functions.
7348                  * @return {tinymce.html.Node} Root node containing the tree.
7349                  */
7350                 self.parse = function(html, args) {
7351                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
7352                         var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
7353                         var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
7354                         var children, nonEmptyElements, rootBlockName;
7355
7356                         args = args || {};
7357                         matchedNodes = {};
7358                         matchedAttributes = {};
7359                         blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
7360                         nonEmptyElements = schema.getNonEmptyElements();
7361                         children = schema.children;
7362                         validate = settings.validate;
7363                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
7364
7365                         whiteSpaceElements = schema.getWhiteSpaceElements();
7366                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;
7367                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;
7368                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;
7369                         isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
7370
7371                         function addRootBlocks() {
7372                                 var node = rootNode.firstChild, next, rootBlockNode;
7373
7374                                 // Removes whitespace at beginning and end of block so:
7375                                 // <p> x </p> -> <p>x</p>
7376                                 function trim(rootBlockNode) {
7377                                         if (rootBlockNode) {
7378                                                 node = rootBlockNode.firstChild;
7379                                                 if (node && node.type == 3) {
7380                                                         node.value = node.value.replace(startWhiteSpaceRegExp, '');
7381                                                 }
7382
7383                                                 node = rootBlockNode.lastChild;
7384                                                 if (node && node.type == 3) {
7385                                                         node.value = node.value.replace(endWhiteSpaceRegExp, '');
7386                                                 }
7387                                         }
7388                                 }
7389
7390                                 // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
7391                                 if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
7392                                         return;
7393                                 }
7394
7395                                 while (node) {
7396                                         next = node.next;
7397
7398                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
7399                                                 !blockElements[node.name] && !node.attr('data-mce-type'))) {
7400                                                 if (!rootBlockNode) {
7401                                                         // Create a new root block element
7402                                                         rootBlockNode = createNode(rootBlockName, 1);
7403                                                         rootNode.insert(rootBlockNode, node);
7404                                                         rootBlockNode.append(node);
7405                                                 } else {
7406                                                         rootBlockNode.append(node);
7407                                                 }
7408                                         } else {
7409                                                 trim(rootBlockNode);
7410                                                 rootBlockNode = null;
7411                                         }
7412
7413                                         node = next;
7414                                 }
7415
7416                                 trim(rootBlockNode);
7417                         }
7418
7419                         function createNode(name, type) {
7420                                 var node = new Node(name, type), list;
7421
7422                                 if (name in nodeFilters) {
7423                                         list = matchedNodes[name];
7424
7425                                         if (list) {
7426                                                 list.push(node);
7427                                         } else {
7428                                                 matchedNodes[name] = [node];
7429                                         }
7430                                 }
7431
7432                                 return node;
7433                         }
7434
7435                         function removeWhitespaceBefore(node) {
7436                                 var textNode, textVal, sibling;
7437
7438                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
7439                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
7440
7441                                         if (textVal.length > 0) {
7442                                                 textNode.value = textVal;
7443                                                 textNode = textNode.prev;
7444                                         } else {
7445                                                 sibling = textNode.prev;
7446                                                 textNode.remove();
7447                                                 textNode = sibling;
7448                                         }
7449                                 }
7450                         }
7451
7452                         function cloneAndExcludeBlocks(input) {
7453                                 var name, output = {};
7454
7455                                 for (name in input) {
7456                                         if (name !== 'li' && name != 'p') {
7457                                                 output[name] = input[name];
7458                                         }
7459                                 }
7460
7461                                 return output;
7462                         }
7463
7464                         parser = new SaxParser({
7465                                 validate: validate,
7466
7467                                 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
7468                                 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
7469
7470                                 cdata: function(text) {
7471                                         node.append(createNode('#cdata', 4)).value = text;
7472                                 },
7473
7474                                 text: function(text, raw) {
7475                                         var textNode;
7476
7477                                         // Trim all redundant whitespace on non white space elements
7478                                         if (!isInWhiteSpacePreservedElement) {
7479                                                 text = text.replace(allWhiteSpaceRegExp, ' ');
7480
7481                                                 if (node.lastChild && blockElements[node.lastChild.name]) {
7482                                                         text = text.replace(startWhiteSpaceRegExp, '');
7483                                                 }
7484                                         }
7485
7486                                         // Do we need to create the node
7487                                         if (text.length !== 0) {
7488                                                 textNode = createNode('#text', 3);
7489                                                 textNode.raw = !!raw;
7490                                                 node.append(textNode).value = text;
7491                                         }
7492                                 },
7493
7494                                 comment: function(text) {
7495                                         node.append(createNode('#comment', 8)).value = text;
7496                                 },
7497
7498                                 pi: function(name, text) {
7499                                         node.append(createNode(name, 7)).value = text;
7500                                         removeWhitespaceBefore(node);
7501                                 },
7502
7503                                 doctype: function(text) {
7504                                         var newNode;
7505
7506                                         newNode = node.append(createNode('#doctype', 10));
7507                                         newNode.value = text;
7508                                         removeWhitespaceBefore(node);
7509                                 },
7510
7511                                 start: function(name, attrs, empty) {
7512                                         var newNode, attrFiltersLen, elementRule, attrName, parent;
7513
7514                                         elementRule = validate ? schema.getElementRule(name) : {};
7515                                         if (elementRule) {
7516                                                 newNode = createNode(elementRule.outputName || name, 1);
7517                                                 newNode.attributes = attrs;
7518                                                 newNode.shortEnded = empty;
7519
7520                                                 node.append(newNode);
7521
7522                                                 // Check if node is valid child of the parent node is the child is
7523                                                 // unknown we don't collect it since it's probably a custom element
7524                                                 parent = children[node.name];
7525                                                 if (parent && children[newNode.name] && !parent[newNode.name]) {
7526                                                         invalidChildren.push(newNode);
7527                                                 }
7528
7529                                                 attrFiltersLen = attributeFilters.length;
7530                                                 while (attrFiltersLen--) {
7531                                                         attrName = attributeFilters[attrFiltersLen].name;
7532
7533                                                         if (attrName in attrs.map) {
7534                                                                 list = matchedAttributes[attrName];
7535
7536                                                                 if (list) {
7537                                                                         list.push(newNode);
7538                                                                 } else {
7539                                                                         matchedAttributes[attrName] = [newNode];
7540                                                                 }
7541                                                         }
7542                                                 }
7543
7544                                                 // Trim whitespace before block
7545                                                 if (blockElements[name]) {
7546                                                         removeWhitespaceBefore(newNode);
7547                                                 }
7548
7549                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />
7550                                                 if (!empty) {
7551                                                         node = newNode;
7552                                                 }
7553
7554                                                 // Check if we are inside a whitespace preserved element
7555                                                 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
7556                                                         isInWhiteSpacePreservedElement = true;
7557                                                 }
7558                                         }
7559                                 },
7560
7561                                 end: function(name) {
7562                                         var textNode, elementRule, text, sibling, tempNode;
7563
7564                                         elementRule = validate ? schema.getElementRule(name) : {};
7565                                         if (elementRule) {
7566                                                 if (blockElements[name]) {
7567                                                         if (!isInWhiteSpacePreservedElement) {
7568                                                                 // Trim whitespace of the first node in a block
7569                                                                 textNode = node.firstChild;
7570                                                                 if (textNode && textNode.type === 3) {
7571                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');
7572
7573                                                                         // Any characters left after trim or should we remove it
7574                                                                         if (text.length > 0) {
7575                                                                                 textNode.value = text;
7576                                                                                 textNode = textNode.next;
7577                                                                         } else {
7578                                                                                 sibling = textNode.next;
7579                                                                                 textNode.remove();
7580                                                                                 textNode = sibling;
7581
7582                                                                                 // Remove any pure whitespace siblings
7583                                                                                 while (textNode && textNode.type === 3) {
7584                                                                                         text = textNode.value;
7585                                                                                         sibling = textNode.next;
7586
7587                                                                                         if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
7588                                                                                                 textNode.remove();
7589                                                                                                 textNode = sibling;
7590                                                                                         }
7591
7592                                                                                         textNode = sibling;
7593                                                                                 }
7594                                                                         }
7595                                                                 }
7596
7597                                                                 // Trim whitespace of the last node in a block
7598                                                                 textNode = node.lastChild;
7599                                                                 if (textNode && textNode.type === 3) {
7600                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');
7601
7602                                                                         // Any characters left after trim or should we remove it
7603                                                                         if (text.length > 0) {
7604                                                                                 textNode.value = text;
7605                                                                                 textNode = textNode.prev;
7606                                                                         } else {
7607                                                                                 sibling = textNode.prev;
7608                                                                                 textNode.remove();
7609                                                                                 textNode = sibling;
7610
7611                                                                                 // Remove any pure whitespace siblings
7612                                                                                 while (textNode && textNode.type === 3) {
7613                                                                                         text = textNode.value;
7614                                                                                         sibling = textNode.prev;
7615
7616                                                                                         if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
7617                                                                                                 textNode.remove();
7618                                                                                                 textNode = sibling;
7619                                                                                         }
7620
7621                                                                                         textNode = sibling;
7622                                                                                 }
7623                                                                         }
7624                                                                 }
7625                                                         }
7626
7627                                                         // Trim start white space
7628                                                         // Removed due to: #5424
7629                                                         /*textNode = node.prev;
7630                                                         if (textNode && textNode.type === 3) {
7631                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
7632
7633                                                                 if (text.length > 0)
7634                                                                         textNode.value = text;
7635                                                                 else
7636                                                                         textNode.remove();
7637                                                         }*/
7638                                                 }
7639
7640                                                 // Check if we exited a whitespace preserved element
7641                                                 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
7642                                                         isInWhiteSpacePreservedElement = false;
7643                                                 }
7644
7645                                                 // Handle empty nodes
7646                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {
7647                                                         if (node.isEmpty(nonEmptyElements)) {
7648                                                                 if (elementRule.paddEmpty) {
7649                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';
7650                                                                 } else {
7651                                                                         // Leave nodes that have a name like <a name="name">
7652                                                                         if (!node.attributes.map.name && !node.attributes.map.id) {
7653                                                                                 tempNode = node.parent;
7654                                                                                 node.empty().remove();
7655                                                                                 node = tempNode;
7656                                                                                 return;
7657                                                                         }
7658                                                                 }
7659                                                         }
7660                                                 }
7661
7662                                                 node = node.parent;
7663                                         }
7664                                 }
7665                         }, schema);
7666
7667                         rootNode = node = new Node(args.context || settings.root_name, 11);
7668
7669                         parser.parse(html);
7670
7671                         // Fix invalid children or report invalid children in a contextual parsing
7672                         if (validate && invalidChildren.length) {
7673                                 if (!args.context) {
7674                                         fixInvalidChildren(invalidChildren);
7675                                 } else {
7676                                         args.invalid = true;
7677                                 }
7678                         }
7679
7680                         // Wrap nodes in the root into block elements if the root is body
7681                         if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
7682                                 addRootBlocks();
7683                         }
7684
7685                         // Run filters only when the contents is valid
7686                         if (!args.invalid) {
7687                                 // Run node filters
7688                                 for (name in matchedNodes) {
7689                                         list = nodeFilters[name];
7690                                         nodes = matchedNodes[name];
7691
7692                                         // Remove already removed children
7693                                         fi = nodes.length;
7694                                         while (fi--) {
7695                                                 if (!nodes[fi].parent) {
7696                                                         nodes.splice(fi, 1);
7697                                                 }
7698                                         }
7699
7700                                         for (i = 0, l = list.length; i < l; i++) {
7701                                                 list[i](nodes, name, args);
7702                                         }
7703                                 }
7704
7705                                 // Run attribute filters
7706                                 for (i = 0, l = attributeFilters.length; i < l; i++) {
7707                                         list = attributeFilters[i];
7708
7709                                         if (list.name in matchedAttributes) {
7710                                                 nodes = matchedAttributes[list.name];
7711
7712                                                 // Remove already removed children
7713                                                 fi = nodes.length;
7714                                                 while (fi--) {
7715                                                         if (!nodes[fi].parent) {
7716                                                                 nodes.splice(fi, 1);
7717                                                         }
7718                                                 }
7719
7720                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
7721                                                         list.callbacks[fi](nodes, list.name, args);
7722                                                 }
7723                                         }
7724                                 }
7725                         }
7726
7727                         return rootNode;
7728                 };
7729
7730                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
7731                 // make it possible to place the caret inside empty blocks. This logic tries to remove
7732                 // these elements and keep br elements that where intended to be there intact
7733                 if (settings.remove_trailing_brs) {
7734                         self.addNodeFilter('br', function(nodes) {
7735                                 var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
7736                                 var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
7737                                 var elementRule, textNode;
7738
7739                                 // Remove brs from body element as well
7740                                 blockElements.body = 1;
7741
7742                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
7743                                 for (i = 0; i < l; i++) {
7744                                         node = nodes[i];
7745                                         parent = node.parent;
7746
7747                                         if (blockElements[node.parent.name] && node === parent.lastChild) {
7748                                                 // Loop all nodes to the left of the current node and check for other BR elements
7749                                                 // excluding bookmarks since they are invisible
7750                                                 prev = node.prev;
7751                                                 while (prev) {
7752                                                         prevName = prev.name;
7753
7754                                                         // Ignore bookmarks
7755                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
7756                                                                 // Found a non BR element
7757                                                                 if (prevName !== "br") {
7758                                                                         break;
7759                                                                 }
7760
7761                                                                 // Found another br it's a <br><br> structure then don't remove anything
7762                                                                 if (prevName === 'br') {
7763                                                                         node = null;
7764                                                                         break;
7765                                                                 }
7766                                                         }
7767
7768                                                         prev = prev.prev;
7769                                                 }
7770
7771                                                 if (node) {
7772                                                         node.remove();
7773
7774                                                         // Is the parent to be considered empty after we removed the BR
7775                                                         if (parent.isEmpty(nonEmptyElements)) {
7776                                                                 elementRule = schema.getElementRule(parent.name);
7777
7778                                                                 // Remove or padd the element depending on schema rule
7779                                                                 if (elementRule) {
7780                                                                         if (elementRule.removeEmpty) {
7781                                                                                 parent.remove();
7782                                                                         } else if (elementRule.paddEmpty) {
7783                                                                                 parent.empty().append(new Node('#text', 3)).value = '\u00a0';
7784                                                                         }
7785                                                                 }
7786                                                         }
7787                                                 }
7788                                         } else {
7789                                                 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
7790                                                 // so they become <p><b><i>&nbsp;</i></b></p>
7791                                                 lastParent = node;
7792                                                 while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
7793                                                         lastParent = parent;
7794
7795                                                         if (blockElements[parent.name]) {
7796                                                                 break;
7797                                                         }
7798
7799                                                         parent = parent.parent;
7800                                                 }
7801
7802                                                 if (lastParent === parent) {
7803                                                         textNode = new Node('#text', 3);
7804                                                         textNode.value = '\u00a0';
7805                                                         node.replace(textNode);
7806                                                 }
7807                                         }
7808                                 }
7809                         });
7810                 }
7811
7812                 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
7813                 if (!settings.allow_html_in_named_anchor) {
7814                         self.addAttributeFilter('id,name', function(nodes) {
7815                                 var i = nodes.length, sibling, prevSibling, parent, node;
7816
7817                                 while (i--) {
7818                                         node = nodes[i];
7819                                         if (node.name === 'a' && node.firstChild && !node.attr('href')) {
7820                                                 parent = node.parent;
7821
7822                                                 // Move children after current node
7823                                                 sibling = node.lastChild;
7824                                                 do {
7825                                                         prevSibling = sibling.prev;
7826                                                         parent.insert(sibling, node);
7827                                                         sibling = prevSibling;
7828                                                 } while (sibling);
7829                                         }
7830                                 }
7831                         });
7832                 }
7833         };
7834 });
7835
7836 // Included from: js/tinymce/classes/html/Writer.js
7837
7838 /**
7839  * Writer.js
7840  *
7841  * Copyright, Moxiecode Systems AB
7842  * Released under LGPL License.
7843  *
7844  * License: http://www.tinymce.com/license
7845  * Contributing: http://www.tinymce.com/contributing
7846  */
7847
7848 /**
7849  * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
7850  *
7851  * @class tinymce.html.Writer
7852  * @example
7853  * var writer = new tinymce.html.Writer({indent: true});
7854  * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
7855  * console.log(writer.getContent());
7856  *
7857  * @class tinymce.html.Writer
7858  * @version 3.4
7859  */
7860 define("tinymce/html/Writer", [
7861         "tinymce/html/Entities",
7862         "tinymce/util/Tools"
7863 ], function(Entities, Tools) {
7864         var makeMap = Tools.makeMap;
7865
7866         /**
7867          * Constructs a new Writer instance.
7868          *
7869          * @constructor
7870          * @method Writer
7871          * @param {Object} settings Name/value settings object.
7872          */
7873         return function(settings) {
7874                 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
7875
7876                 settings = settings || {};
7877                 indent = settings.indent;
7878                 indentBefore = makeMap(settings.indent_before || '');
7879                 indentAfter = makeMap(settings.indent_after || '');
7880                 encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
7881                 htmlOutput = settings.element_format == "html";
7882
7883                 return {
7884                         /**
7885                          * Writes the a start element such as <p id="a">.
7886                          *
7887                          * @method start
7888                          * @param {String} name Name of the element.
7889                          * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
7890                          * @param {Boolean} empty Optional empty state if the tag should end like <br />.
7891                          */
7892                         start: function(name, attrs, empty) {
7893                                 var i, l, attr, value;
7894
7895                                 if (indent && indentBefore[name] && html.length > 0) {
7896                                         value = html[html.length - 1];
7897
7898                                         if (value.length > 0 && value !== '\n') {
7899                                                 html.push('\n');
7900                                         }
7901                                 }
7902
7903                                 html.push('<', name);
7904
7905                                 if (attrs) {
7906                                         for (i = 0, l = attrs.length; i < l; i++) {
7907                                                 attr = attrs[i];
7908                                                 html.push(' ', attr.name, '="', encode(attr.value, true), '"');
7909                                         }
7910                                 }
7911
7912                                 if (!empty || htmlOutput) {
7913                                         html[html.length] = '>';
7914                                 } else {
7915                                         html[html.length] = ' />';
7916                                 }
7917
7918                                 if (empty && indent && indentAfter[name] && html.length > 0) {
7919                                         value = html[html.length - 1];
7920
7921                                         if (value.length > 0 && value !== '\n') {
7922                                                 html.push('\n');
7923                                         }
7924                                 }
7925                         },
7926
7927                         /**
7928                          * Writes the a end element such as </p>.
7929                          *
7930                          * @method end
7931                          * @param {String} name Name of the element.
7932                          */
7933                         end: function(name) {
7934                                 var value;
7935
7936                                 /*if (indent && indentBefore[name] && html.length > 0) {
7937                                         value = html[html.length - 1];
7938
7939                                         if (value.length > 0 && value !== '\n')
7940                                                 html.push('\n');
7941                                 }*/
7942
7943                                 html.push('</', name, '>');
7944
7945                                 if (indent && indentAfter[name] && html.length > 0) {
7946                                         value = html[html.length - 1];
7947
7948                                         if (value.length > 0 && value !== '\n') {
7949                                                 html.push('\n');
7950                                         }
7951                                 }
7952                         },
7953
7954                         /**
7955                          * Writes a text node.
7956                          *
7957                          * @method text
7958                          * @param {String} text String to write out.
7959                          * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
7960                          */
7961                         text: function(text, raw) {
7962                                 if (text.length > 0) {
7963                                         html[html.length] = raw ? text : encode(text);
7964                                 }
7965                         },
7966
7967                         /**
7968                          * Writes a cdata node such as <![CDATA[data]]>.
7969                          *
7970                          * @method cdata
7971                          * @param {String} text String to write out inside the cdata.
7972                          */
7973                         cdata: function(text) {
7974                                 html.push('<![CDATA[', text, ']]>');
7975                         },
7976
7977                         /**
7978                          * Writes a comment node such as <!-- Comment -->.
7979                          *
7980                          * @method cdata
7981                          * @param {String} text String to write out inside the comment.
7982                          */
7983                         comment: function(text) {
7984                                 html.push('<!--', text, '-->');
7985                         },
7986
7987                         /**
7988                          * Writes a PI node such as <?xml attr="value" ?>.
7989                          *
7990                          * @method pi
7991                          * @param {String} name Name of the pi.
7992                          * @param {String} text String to write out inside the pi.
7993                          */
7994                         pi: function(name, text) {
7995                                 if (text) {
7996                                         html.push('<?', name, ' ', text, '?>');
7997                                 } else {
7998                                         html.push('<?', name, '?>');
7999                                 }
8000
8001                                 if (indent) {
8002                                         html.push('\n');
8003                                 }
8004                         },
8005
8006                         /**
8007                          * Writes a doctype node such as <!DOCTYPE data>.
8008                          *
8009                          * @method doctype
8010                          * @param {String} text String to write out inside the doctype.
8011                          */
8012                         doctype: function(text) {
8013                                 html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
8014                         },
8015
8016                         /**
8017                          * Resets the internal buffer if one wants to reuse the writer.
8018                          *
8019                          * @method reset
8020                          */
8021                         reset: function() {
8022                                 html.length = 0;
8023                         },
8024
8025                         /**
8026                          * Returns the contents that got serialized.
8027                          *
8028                          * @method getContent
8029                          * @return {String} HTML contents that got written down.
8030                          */
8031                         getContent: function() {
8032                                 return html.join('').replace(/\n$/, '');
8033                         }
8034                 };
8035         };
8036 });
8037
8038 // Included from: js/tinymce/classes/html/Serializer.js
8039
8040 /**
8041  * Serializer.js
8042  *
8043  * Copyright, Moxiecode Systems AB
8044  * Released under LGPL License.
8045  *
8046  * License: http://www.tinymce.com/license
8047  * Contributing: http://www.tinymce.com/contributing
8048  */
8049
8050 /**
8051  * This class is used to serialize down the DOM tree into a string using a Writer instance.
8052  *
8053  *
8054  * @example
8055  * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
8056  * @class tinymce.html.Serializer
8057  * @version 3.4
8058  */
8059 define("tinymce/html/Serializer", [
8060         "tinymce/html/Writer",
8061         "tinymce/html/Schema"
8062 ], function(Writer, Schema) {
8063         /**
8064          * Constructs a new Serializer instance.
8065          *
8066          * @constructor
8067          * @method Serializer
8068          * @param {Object} settings Name/value settings object.
8069          * @param {tinymce.html.Schema} schema Schema instance to use.
8070          */
8071         return function(settings, schema) {
8072                 var self = this, writer = new Writer(settings);
8073
8074                 settings = settings || {};
8075                 settings.validate = "validate" in settings ? settings.validate : true;
8076
8077                 self.schema = schema = schema || new Schema();
8078                 self.writer = writer;
8079
8080                 /**
8081                  * Serializes the specified node into a string.
8082                  *
8083                  * @example
8084                  * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
8085                  * @method serialize
8086                  * @param {tinymce.html.Node} node Node instance to serialize.
8087                  * @return {String} String with HTML based on DOM tree.
8088                  */
8089                 self.serialize = function(node) {
8090                         var handlers, validate;
8091
8092                         validate = settings.validate;
8093
8094                         handlers = {
8095                                 // #text
8096                                 3: function(node) {
8097                                         writer.text(node.value, node.raw);
8098                                 },
8099
8100                                 // #comment
8101                                 8: function(node) {
8102                                         writer.comment(node.value);
8103                                 },
8104
8105                                 // Processing instruction
8106                                 7: function(node) {
8107                                         writer.pi(node.name, node.value);
8108                                 },
8109
8110                                 // Doctype
8111                                 10: function(node) {
8112                                         writer.doctype(node.value);
8113                                 },
8114
8115                                 // CDATA
8116                                 4: function(node) {
8117                                         writer.cdata(node.value);
8118                                 },
8119
8120                                 // Document fragment
8121                                 11: function(node) {
8122                                         if ((node = node.firstChild)) {
8123                                                 do {
8124                                                         walk(node);
8125                                                 } while ((node = node.next));
8126                                         }
8127                                 }
8128                         };
8129
8130                         writer.reset();
8131
8132                         function walk(node) {
8133                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
8134
8135                                 if (!handler) {
8136                                         name = node.name;
8137                                         isEmpty = node.shortEnded;
8138                                         attrs = node.attributes;
8139
8140                                         // Sort attributes
8141                                         if (validate && attrs && attrs.length > 1) {
8142                                                 sortedAttrs = [];
8143                                                 sortedAttrs.map = {};
8144
8145                                                 elementRule = schema.getElementRule(node.name);
8146                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
8147                                                         attrName = elementRule.attributesOrder[i];
8148
8149                                                         if (attrName in attrs.map) {
8150                                                                 attrValue = attrs.map[attrName];
8151                                                                 sortedAttrs.map[attrName] = attrValue;
8152                                                                 sortedAttrs.push({name: attrName, value: attrValue});
8153                                                         }
8154                                                 }
8155
8156                                                 for (i = 0, l = attrs.length; i < l; i++) {
8157                                                         attrName = attrs[i].name;
8158
8159                                                         if (!(attrName in sortedAttrs.map)) {
8160                                                                 attrValue = attrs.map[attrName];
8161                                                                 sortedAttrs.map[attrName] = attrValue;
8162                                                                 sortedAttrs.push({name: attrName, value: attrValue});
8163                                                         }
8164                                                 }
8165
8166                                                 attrs = sortedAttrs;
8167                                         }
8168
8169                                         writer.start(node.name, attrs, isEmpty);
8170
8171                                         if (!isEmpty) {
8172                                                 if ((node = node.firstChild)) {
8173                                                         do {
8174                                                                 walk(node);
8175                                                         } while ((node = node.next));
8176                                                 }
8177
8178                                                 writer.end(name);
8179                                         }
8180                                 } else {
8181                                         handler(node);
8182                                 }
8183                         }
8184
8185                         // Serialize element and treat all non elements as fragments
8186                         if (node.type == 1 && !settings.inner) {
8187                                 walk(node);
8188                         } else {
8189                                 handlers[11](node);
8190                         }
8191
8192                         return writer.getContent();
8193                 };
8194         };
8195 });
8196
8197 // Included from: js/tinymce/classes/dom/Serializer.js
8198
8199 /**
8200  * Serializer.js
8201  *
8202  * Copyright, Moxiecode Systems AB
8203  * Released under LGPL License.
8204  *
8205  * License: http://www.tinymce.com/license
8206  * Contributing: http://www.tinymce.com/contributing
8207  */
8208
8209 /**
8210  * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
8211  * more details and examples on how to use this class.
8212  *
8213  * @class tinymce.dom.Serializer
8214  */
8215 define("tinymce/dom/Serializer", [
8216         "tinymce/dom/DOMUtils",
8217         "tinymce/html/DomParser",
8218         "tinymce/html/Entities",
8219         "tinymce/html/Serializer",
8220         "tinymce/html/Node",
8221         "tinymce/html/Schema",
8222         "tinymce/Env",
8223         "tinymce/util/Tools"
8224 ], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) {
8225         var each = Tools.each, trim = Tools.trim;
8226         var DOM = DOMUtils.DOM;
8227
8228         /**
8229          * Constructs a new DOM serializer class.
8230          *
8231          * @constructor
8232          * @method Serializer
8233          * @param {Object} settings Serializer settings object.
8234          * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
8235          */
8236         return function(settings, editor) {
8237                 var dom, schema, htmlParser;
8238
8239                 if (editor) {
8240                         dom = editor.dom;
8241                         schema = editor.schema;
8242                 }
8243
8244                 // Default DOM and Schema if they are undefined
8245                 dom = dom || DOM;
8246                 schema = schema || new Schema(settings);
8247                 settings.entity_encoding = settings.entity_encoding || 'named';
8248                 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
8249
8250                 htmlParser = new DomParser(settings, schema);
8251
8252                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
8253                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
8254                         var i = nodes.length, node, value, internalName = 'data-mce-' + name;
8255                         var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
8256
8257                         while (i--) {
8258                                 node = nodes[i];
8259
8260                                 value = node.attributes.map[internalName];
8261                                 if (value !== undef) {
8262                                         // Set external name to internal value and remove internal
8263                                         node.attr(name, value.length > 0 ? value : null);
8264                                         node.attr(internalName, null);
8265                                 } else {
8266                                         // No internal attribute found then convert the value we have in the DOM
8267                                         value = node.attributes.map[name];
8268
8269                                         if (name === "style") {
8270                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
8271                                         } else if (urlConverter) {
8272                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
8273                                         }
8274
8275                                         node.attr(name, value.length > 0 ? value : null);
8276                                 }
8277                         }
8278                 });
8279
8280                 // Remove internal classes mceItem<..> or mceSelected
8281                 htmlParser.addAttributeFilter('class', function(nodes) {
8282                         var i = nodes.length, node, value;
8283
8284                         while (i--) {
8285                                 node = nodes[i];
8286                                 value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
8287                                 node.attr('class', value.length > 0 ? value : null);
8288                         }
8289                 });
8290
8291                 // Remove bookmark elements
8292                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
8293                         var i = nodes.length, node;
8294
8295                         while (i--) {
8296                                 node = nodes[i];
8297
8298                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
8299                                         node.remove();
8300                                 }
8301                         }
8302                 });
8303
8304                 // Remove expando attributes
8305                 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) {
8306                         var i = nodes.length;
8307
8308                         while (i--) {
8309                                 nodes[i].attr(name, null);
8310                         }
8311                 });
8312
8313                 htmlParser.addNodeFilter('noscript', function(nodes) {
8314                         var i = nodes.length, node;
8315
8316                         while (i--) {
8317                                 node = nodes[i].firstChild;
8318
8319                                 if (node) {
8320                                         node.value = Entities.decode(node.value);
8321                                 }
8322                         }
8323                 });
8324
8325                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
8326                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
8327                         var i = nodes.length, node, value;
8328
8329                         function trim(value) {
8330                                 /*jshint maxlen:255 */
8331                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
8332                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
8333                                                 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
8334                                                 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
8335                         }
8336
8337                         while (i--) {
8338                                 node = nodes[i];
8339                                 value = node.firstChild ? node.firstChild.value : '';
8340
8341                                 if (name === "script") {
8342                                         // Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5)
8343                                         var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, '');
8344                                         node.attr('type', type === 'text/javascript' ? null : type);
8345
8346                                         if (value.length > 0) {
8347                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
8348                                         }
8349                                 } else {
8350                                         if (value.length > 0) {
8351                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
8352                                         }
8353                                 }
8354                         }
8355                 });
8356
8357                 // Convert comments to cdata and handle protected comments
8358                 htmlParser.addNodeFilter('#comment', function(nodes) {
8359                         var i = nodes.length, node;
8360
8361                         while (i--) {
8362                                 node = nodes[i];
8363
8364                                 if (node.value.indexOf('[CDATA[') === 0) {
8365                                         node.name = '#cdata';
8366                                         node.type = 4;
8367                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
8368                                 } else if (node.value.indexOf('mce:protected ') === 0) {
8369                                         node.name = "#text";
8370                                         node.type = 3;
8371                                         node.raw = true;
8372                                         node.value = unescape(node.value).substr(14);
8373                                 }
8374                         }
8375                 });
8376
8377                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
8378                         var i = nodes.length, node;
8379
8380                         while (i--) {
8381                                 node = nodes[i];
8382                                 if (node.type === 7) {
8383                                         node.remove();
8384                                 } else if (node.type === 1) {
8385                                         if (name === "input" && !("type" in node.attributes.map)) {
8386                                                 node.attr('type', 'text');
8387                                         }
8388                                 }
8389                         }
8390                 });
8391
8392                 // Fix list elements, TODO: Replace this later
8393                 if (settings.fix_list_elements) {
8394                         htmlParser.addNodeFilter('ul,ol', function(nodes) {
8395                                 var i = nodes.length, node, parentNode;
8396
8397                                 while (i--) {
8398                                         node = nodes[i];
8399                                         parentNode = node.parent;
8400
8401                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
8402                                                 if (node.prev && node.prev.name === 'li') {
8403                                                         node.prev.append(node);
8404                                                 }
8405                                         }
8406                                 }
8407                         });
8408                 }
8409
8410                 // Remove internal data attributes
8411                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) {
8412                         var i = nodes.length;
8413
8414                         while (i--) {
8415                                 nodes[i].attr(name, null);
8416                         }
8417                 });
8418
8419                 // Return public methods
8420                 return {
8421                         /**
8422                          * Schema instance that was used to when the Serializer was constructed.
8423                          *
8424                          * @field {tinymce.html.Schema} schema
8425                          */
8426                         schema: schema,
8427
8428                         /**
8429                          * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
8430                          * and then execute the callback ones it has finished parsing the document.
8431                          *
8432                          * @example
8433                          * parser.addNodeFilter('p,h1', function(nodes, name) {
8434                          *              for (var i = 0; i < nodes.length; i++) {
8435                          *                      console.log(nodes[i].name);
8436                          *              }
8437                          * });
8438                          * @method addNodeFilter
8439                          * @method {String} name Comma separated list of nodes to collect.
8440                          * @param {function} callback Callback function to execute once it has collected nodes.
8441                          */
8442                         addNodeFilter: htmlParser.addNodeFilter,
8443
8444                         /**
8445                          * Adds a attribute filter function to the parser used by the serializer, the parser will
8446                          * collect nodes that has the specified attributes
8447                          * and then execute the callback ones it has finished parsing the document.
8448                          *
8449                          * @example
8450                          * parser.addAttributeFilter('src,href', function(nodes, name) {
8451                          *              for (var i = 0; i < nodes.length; i++) {
8452                          *                      console.log(nodes[i].name);
8453                          *              }
8454                          * });
8455                          * @method addAttributeFilter
8456                          * @method {String} name Comma separated list of nodes to collect.
8457                          * @param {function} callback Callback function to execute once it has collected nodes.
8458                          */
8459                         addAttributeFilter: htmlParser.addAttributeFilter,
8460
8461                         /**
8462                          * Serializes the specified browser DOM node into a HTML string.
8463                          *
8464                          * @method serialize
8465                          * @param {DOMNode} node DOM node to serialize.
8466                          * @param {Object} args Arguments option that gets passed to event handlers.
8467                          */
8468                         serialize: function(node, args) {
8469                                 var self = this, impl, doc, oldDoc, htmlSerializer, content;
8470
8471                                 // Explorer won't clone contents of script and style and the
8472                                 // selected index of select elements are cleared on a clone operation.
8473                                 if (Env.ie && dom.select('script,style,select,map').length > 0) {
8474                                         content = node.innerHTML;
8475                                         node = node.cloneNode(false);
8476                                         dom.setHTML(node, content);
8477                                 } else {
8478                                         node = node.cloneNode(true);
8479                                 }
8480
8481                                 // Nodes needs to be attached to something in WebKit/Opera
8482                                 // This fix will make DOM ranges and make Sizzle happy!
8483                                 impl = node.ownerDocument.implementation;
8484                                 if (impl.createHTMLDocument) {
8485                                         // Create an empty HTML document
8486                                         doc = impl.createHTMLDocument("");
8487
8488                                         // Add the element or it's children if it's a body element to the new document
8489                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
8490                                                 doc.body.appendChild(doc.importNode(node, true));
8491                                         });
8492
8493                                         // Grab first child or body element for serialization
8494                                         if (node.nodeName != 'BODY') {
8495                                                 node = doc.body.firstChild;
8496                                         } else {
8497                                                 node = doc.body;
8498                                         }
8499
8500                                         // set the new document in DOMUtils so createElement etc works
8501                                         oldDoc = dom.doc;
8502                                         dom.doc = doc;
8503                                 }
8504
8505                                 args = args || {};
8506                                 args.format = args.format || 'html';
8507
8508                                 // Don't wrap content if we want selected html
8509                                 if (args.selection) {
8510                                         args.forced_root_block = '';
8511                                 }
8512
8513                                 // Pre process
8514                                 if (!args.no_events) {
8515                                         args.node = node;
8516                                         self.onPreProcess(args);
8517                                 }
8518
8519                                 // Setup serializer
8520                                 htmlSerializer = new Serializer(settings, schema);
8521
8522                                 // Parse and serialize HTML
8523                                 args.content = htmlSerializer.serialize(
8524                                         htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
8525                                 );
8526
8527                                 // Replace all BOM characters for now until we can find a better solution
8528                                 if (!args.cleanup) {
8529                                         args.content = args.content.replace(/\uFEFF/g, '');
8530                                 }
8531
8532                                 // Post process
8533                                 if (!args.no_events) {
8534                                         self.onPostProcess(args);
8535                                 }
8536
8537                                 // Restore the old document if it was changed
8538                                 if (oldDoc) {
8539                                         dom.doc = oldDoc;
8540                                 }
8541
8542                                 args.node = null;
8543
8544                                 return args.content;
8545                         },
8546
8547                         /**
8548                          * Adds valid elements rules to the serializers schema instance this enables you to specify things
8549                          * like what elements should be outputted and what attributes specific elements might have.
8550                          * Consult the Wiki for more details on this format.
8551                          *
8552                          * @method addRules
8553                          * @param {String} rules Valid elements rules string to add to schema.
8554                          */
8555                         addRules: function(rules) {
8556                                 schema.addValidElements(rules);
8557                         },
8558
8559                         /**
8560                          * Sets the valid elements rules to the serializers schema instance this enables you to specify things
8561                          * like what elements should be outputted and what attributes specific elements might have.
8562                          * Consult the Wiki for more details on this format.
8563                          *
8564                          * @method setRules
8565                          * @param {String} rules Valid elements rules string.
8566                          */
8567                         setRules: function(rules) {
8568                                 schema.setValidElements(rules);
8569                         },
8570
8571                         onPreProcess: function(args) {
8572                                 if (editor) {
8573                                         editor.fire('PreProcess', args);
8574                                 }
8575                         },
8576
8577                         onPostProcess: function(args) {
8578                                 if (editor) {
8579                                         editor.fire('PostProcess', args);
8580                                 }
8581                         }
8582                 };
8583         };
8584 });
8585
8586 // Included from: js/tinymce/classes/dom/TridentSelection.js
8587
8588 /**
8589  * TridentSelection.js
8590  *
8591  * Copyright, Moxiecode Systems AB
8592  * Released under LGPL License.
8593  *
8594  * License: http://www.tinymce.com/license
8595  * Contributing: http://www.tinymce.com/contributing
8596  */
8597
8598 /**
8599  * Selection class for old explorer versions. This one fakes the
8600  * native selection object available on modern browsers.
8601  *
8602  * @class tinymce.dom.TridentSelection
8603  */
8604 define("tinymce/dom/TridentSelection", [], function() {
8605         function Selection(selection) {
8606                 var self = this, dom = selection.dom, FALSE = false;
8607
8608                 function getPosition(rng, start) {
8609                         var checkRng, startIndex = 0, endIndex, inside,
8610                                 children, child, offset, index, position = -1, parent;
8611
8612                         // Setup test range, collapse it and get the parent
8613                         checkRng = rng.duplicate();
8614                         checkRng.collapse(start);
8615                         parent = checkRng.parentElement();
8616
8617                         // Check if the selection is within the right document
8618                         if (parent.ownerDocument !== selection.dom.doc) {
8619                                 return;
8620                         }
8621
8622                         // IE will report non editable elements as it's parent so look for an editable one
8623                         while (parent.contentEditable === "false") {
8624                                 parent = parent.parentNode;
8625                         }
8626
8627                         // If parent doesn't have any children then return that we are inside the element
8628                         if (!parent.hasChildNodes()) {
8629                                 return {node: parent, inside: 1};
8630                         }
8631
8632                         // Setup node list and endIndex
8633                         children = parent.children;
8634                         endIndex = children.length - 1;
8635
8636                         // Perform a binary search for the position
8637                         while (startIndex <= endIndex) {
8638                                 index = Math.floor((startIndex + endIndex) / 2);
8639
8640                                 // Move selection to node and compare the ranges
8641                                 child = children[index];
8642                                 checkRng.moveToElementText(child);
8643                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
8644
8645                                 // Before/after or an exact match
8646                                 if (position > 0) {
8647                                         endIndex = index - 1;
8648                                 } else if (position < 0) {
8649                                         startIndex = index + 1;
8650                                 } else {
8651                                         return {node: child};
8652                                 }
8653                         }
8654
8655                         // Check if child position is before or we didn't find a position
8656                         if (position < 0) {
8657                                 // No element child was found use the parent element and the offset inside that
8658                                 if (!child) {
8659                                         checkRng.moveToElementText(parent);
8660                                         checkRng.collapse(true);
8661                                         child = parent;
8662                                         inside = true;
8663                                 } else {
8664                                         checkRng.collapse(false);
8665                                 }
8666
8667                                 // Walk character by character in text node until we hit the selected range endpoint,
8668                                 // hit the end of document or parent isn't the right one
8669                                 // We need to walk char by char since rng.text or rng.htmlText will trim line endings
8670                                 offset = 0;
8671                                 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
8672                                         if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
8673                                                 break;
8674                                         }
8675
8676                                         offset++;
8677                                 }
8678                         } else {
8679                                 // Child position is after the selection endpoint
8680                                 checkRng.collapse(true);
8681
8682                                 // Walk character by character in text node until we hit the selected range endpoint, hit
8683                                 // the end of document or parent isn't the right one
8684                                 offset = 0;
8685                                 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
8686                                         if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
8687                                                 break;
8688                                         }
8689
8690                                         offset++;
8691                                 }
8692                         }
8693
8694                         return {node: child, position: position, offset: offset, inside: inside};
8695                 }
8696
8697                 // Returns a W3C DOM compatible range object by using the IE Range API
8698                 function getRange() {
8699                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
8700
8701                         // If selection is outside the current document just return an empty range
8702                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
8703                         if (element.ownerDocument != dom.doc) {
8704                                 return domRange;
8705                         }
8706
8707                         collapsed = selection.isCollapsed();
8708
8709                         // Handle control selection
8710                         if (ieRange.item) {
8711                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));
8712                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
8713
8714                                 return domRange;
8715                         }
8716
8717                         function findEndPoint(start) {
8718                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
8719
8720                                 container = endPoint.node;
8721                                 offset = endPoint.offset;
8722
8723                                 if (endPoint.inside && !container.hasChildNodes()) {
8724                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);
8725                                         return;
8726                                 }
8727
8728                                 if (offset === undef) {
8729                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
8730                                         return;
8731                                 }
8732
8733                                 if (endPoint.position < 0) {
8734                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;
8735
8736                                         if (!sibling) {
8737                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
8738                                                 return;
8739                                         }
8740
8741                                         if (!offset) {
8742                                                 if (sibling.nodeType == 3) {
8743                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
8744                                                 } else {
8745                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
8746                                                 }
8747
8748                                                 return;
8749                                         }
8750
8751                                         // Find the text node and offset
8752                                         while (sibling) {
8753                                                 nodeValue = sibling.nodeValue;
8754                                                 textNodeOffset += nodeValue.length;
8755
8756                                                 // We are at or passed the position we where looking for
8757                                                 if (textNodeOffset >= offset) {
8758                                                         container = sibling;
8759                                                         textNodeOffset -= offset;
8760                                                         textNodeOffset = nodeValue.length - textNodeOffset;
8761                                                         break;
8762                                                 }
8763
8764                                                 sibling = sibling.nextSibling;
8765                                         }
8766                                 } else {
8767                                         // Find the text node and offset
8768                                         sibling = container.previousSibling;
8769
8770                                         if (!sibling) {
8771                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
8772                                         }
8773
8774                                         // If there isn't any text to loop then use the first position
8775                                         if (!offset) {
8776                                                 if (container.nodeType == 3) {
8777                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
8778                                                 } else {
8779                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
8780                                                 }
8781
8782                                                 return;
8783                                         }
8784
8785                                         while (sibling) {
8786                                                 textNodeOffset += sibling.nodeValue.length;
8787
8788                                                 // We are at or passed the position we where looking for
8789                                                 if (textNodeOffset >= offset) {
8790                                                         container = sibling;
8791                                                         textNodeOffset -= offset;
8792                                                         break;
8793                                                 }
8794
8795                                                 sibling = sibling.previousSibling;
8796                                         }
8797                                 }
8798
8799                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
8800                         }
8801
8802                         try {
8803                                 // Find start point
8804                                 findEndPoint(true);
8805
8806                                 // Find end point if needed
8807                                 if (!collapsed) {
8808                                         findEndPoint();
8809                                 }
8810                         } catch (ex) {
8811                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you
8812                                 // access the nodeValue or other properties of text nodes. This seems to happend when
8813                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
8814                                 if (ex.number == -2147024809) {
8815                                         // Get the current selection
8816                                         bookmark = self.getBookmark(2);
8817
8818                                         // Get start element
8819                                         tmpRange = ieRange.duplicate();
8820                                         tmpRange.collapse(true);
8821                                         element = tmpRange.parentElement();
8822
8823                                         // Get end element
8824                                         if (!collapsed) {
8825                                                 tmpRange = ieRange.duplicate();
8826                                                 tmpRange.collapse(false);
8827                                                 element2 = tmpRange.parentElement();
8828                                                 element2.innerHTML = element2.innerHTML;
8829                                         }
8830
8831                                         // Remove the broken elements
8832                                         element.innerHTML = element.innerHTML;
8833
8834                                         // Restore the selection
8835                                         self.moveToBookmark(bookmark);
8836
8837                                         // Since the range has moved we need to re-get it
8838                                         ieRange = selection.getRng();
8839
8840                                         // Find start point
8841                                         findEndPoint(true);
8842
8843                                         // Find end point if needed
8844                                         if (!collapsed) {
8845                                                 findEndPoint();
8846                                         }
8847                                 } else {
8848                                         throw ex; // Throw other errors
8849                                 }
8850                         }
8851
8852                         return domRange;
8853                 }
8854
8855                 this.getBookmark = function(type) {
8856                         var rng = selection.getRng(), bookmark = {};
8857
8858                         function getIndexes(node) {
8859                                 var parent, root, children, i, indexes = [];
8860
8861                                 parent = node.parentNode;
8862                                 root = dom.getRoot().parentNode;
8863
8864                                 while (parent != root && parent.nodeType !== 9) {
8865                                         children = parent.children;
8866
8867                                         i = children.length;
8868                                         while (i--) {
8869                                                 if (node === children[i]) {
8870                                                         indexes.push(i);
8871                                                         break;
8872                                                 }
8873                                         }
8874
8875                                         node = parent;
8876                                         parent = parent.parentNode;
8877                                 }
8878
8879                                 return indexes;
8880                         }
8881
8882                         function getBookmarkEndPoint(start) {
8883                                 var position;
8884
8885                                 position = getPosition(rng, start);
8886                                 if (position) {
8887                                         return {
8888                                                 position: position.position,
8889                                                 offset: position.offset,
8890                                                 indexes: getIndexes(position.node),
8891                                                 inside: position.inside
8892                                         };
8893                                 }
8894                         }
8895
8896                         // Non ubstructive bookmark
8897                         if (type === 2) {
8898                                 // Handle text selection
8899                                 if (!rng.item) {
8900                                         bookmark.start = getBookmarkEndPoint(true);
8901
8902                                         if (!selection.isCollapsed()) {
8903                                                 bookmark.end = getBookmarkEndPoint();
8904                                         }
8905                                 } else {
8906                                         bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
8907                                 }
8908                         }
8909
8910                         return bookmark;
8911                 };
8912
8913                 this.moveToBookmark = function(bookmark) {
8914                         var rng, body = dom.doc.body;
8915
8916                         function resolveIndexes(indexes) {
8917                                 var node, i, idx, children;
8918
8919                                 node = dom.getRoot();
8920                                 for (i = indexes.length - 1; i >= 0; i--) {
8921                                         children = node.children;
8922                                         idx = indexes[i];
8923
8924                                         if (idx <= children.length - 1) {
8925                                                 node = children[idx];
8926                                         }
8927                                 }
8928
8929                                 return node;
8930                         }
8931
8932                         function setBookmarkEndPoint(start) {
8933                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
8934
8935                                 if (endPoint) {
8936                                         moveLeft = endPoint.position > 0;
8937
8938                                         moveRng = body.createTextRange();
8939                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
8940
8941                                         offset = endPoint.offset;
8942                                         if (offset !== undef) {
8943                                                 moveRng.collapse(endPoint.inside || moveLeft);
8944                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);
8945                                         } else {
8946                                                 moveRng.collapse(start);
8947                                         }
8948
8949                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
8950
8951                                         if (start) {
8952                                                 rng.collapse(true);
8953                                         }
8954                                 }
8955                         }
8956
8957                         if (bookmark.start) {
8958                                 if (bookmark.start.ctrl) {
8959                                         rng = body.createControlRange();
8960                                         rng.addElement(resolveIndexes(bookmark.start.indexes));
8961                                         rng.select();
8962                                 } else {
8963                                         rng = body.createTextRange();
8964                                         setBookmarkEndPoint(true);
8965                                         setBookmarkEndPoint();
8966                                         rng.select();
8967                                 }
8968                         }
8969                 };
8970
8971                 this.addRange = function(rng) {
8972                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
8973                                 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
8974
8975                         function setEndPoint(start) {
8976                                 var container, offset, marker, tmpRng, nodes;
8977
8978                                 marker = dom.create('a');
8979                                 container = start ? startContainer : endContainer;
8980                                 offset = start ? startOffset : endOffset;
8981                                 tmpRng = ieRng.duplicate();
8982
8983                                 if (container == doc || container == doc.documentElement) {
8984                                         container = body;
8985                                         offset = 0;
8986                                 }
8987
8988                                 if (container.nodeType == 3) {
8989                                         container.parentNode.insertBefore(marker, container);
8990                                         tmpRng.moveToElementText(marker);
8991                                         tmpRng.moveStart('character', offset);
8992                                         dom.remove(marker);
8993                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
8994                                 } else {
8995                                         nodes = container.childNodes;
8996
8997                                         if (nodes.length) {
8998                                                 if (offset >= nodes.length) {
8999                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
9000                                                 } else {
9001                                                         container.insertBefore(marker, nodes[offset]);
9002                                                 }
9003
9004                                                 tmpRng.moveToElementText(marker);
9005                                         } else if (container.canHaveHTML) {
9006                                                 // Empty node selection for example <div>|</div>
9007                                                 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
9008                                                 container.innerHTML = '<span>&#xFEFF;</span>';
9009                                                 marker = container.firstChild;
9010                                                 tmpRng.moveToElementText(marker);
9011                                                 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
9012                                         }
9013
9014                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
9015                                         dom.remove(marker);
9016                                 }
9017                         }
9018
9019                         // Setup some shorter versions
9020                         startContainer = rng.startContainer;
9021                         startOffset = rng.startOffset;
9022                         endContainer = rng.endContainer;
9023                         endOffset = rng.endOffset;
9024                         ieRng = body.createTextRange();
9025
9026                         // If single element selection then try making a control selection out of it
9027                         if (startContainer == endContainer && startContainer.nodeType == 1) {
9028                                 // Trick to place the caret inside an empty block element like <p></p>
9029                                 if (startOffset == endOffset && !startContainer.hasChildNodes()) {
9030                                         if (startContainer.canHaveHTML) {
9031                                                 // Check if previous sibling is an empty block if it is then we need to render it
9032                                                 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
9033                                                 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
9034                                                 sibling = startContainer.previousSibling;
9035                                                 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
9036                                                         sibling.innerHTML = '&#xFEFF;';
9037                                                 } else {
9038                                                         sibling = null;
9039                                                 }
9040
9041                                                 startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
9042                                                 ieRng.moveToElementText(startContainer.lastChild);
9043                                                 ieRng.select();
9044                                                 dom.doc.selection.clear();
9045                                                 startContainer.innerHTML = '';
9046
9047                                                 if (sibling) {
9048                                                         sibling.innerHTML = '';
9049                                                 }
9050                                                 return;
9051                                         } else {
9052                                                 startOffset = dom.nodeIndex(startContainer);
9053                                                 startContainer = startContainer.parentNode;
9054                                         }
9055                                 }
9056
9057                                 if (startOffset == endOffset - 1) {
9058                                         try {
9059                                                 ctrlElm = startContainer.childNodes[startOffset];
9060                                                 ctrlRng = body.createControlRange();
9061                                                 ctrlRng.addElement(ctrlElm);
9062                                                 ctrlRng.select();
9063
9064                                                 // Check if the range produced is on the correct element and is a control range
9065                                                 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
9066                                                 nativeRng = selection.getRng();
9067                                                 if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
9068                                                         return;
9069                                                 }
9070                                         } catch (ex) {
9071                                                 // Ignore
9072                                         }
9073                                 }
9074                         }
9075
9076                         // Set start/end point of selection
9077                         setEndPoint(true);
9078                         setEndPoint();
9079
9080                         // Select the new range and scroll it into view
9081                         ieRng.select();
9082                 };
9083
9084                 // Expose range method
9085                 this.getRangeAt = getRange;
9086         }
9087
9088         return Selection;
9089 });
9090
9091 // Included from: js/tinymce/classes/util/VK.js
9092
9093 /**
9094  * VK.js
9095  *
9096  * Copyright, Moxiecode Systems AB
9097  * Released under LGPL License.
9098  *
9099  * License: http://www.tinymce.com/license
9100  * Contributing: http://www.tinymce.com/contributing
9101  */
9102
9103 /**
9104  * This file exposes a set of the common KeyCodes for use.  Please grow it as needed.
9105  */
9106 define("tinymce/util/VK", [
9107         "tinymce/Env"
9108 ], function(Env) {
9109         return {
9110                 BACKSPACE: 8,
9111                 DELETE: 46,
9112                 DOWN: 40,
9113                 ENTER: 13,
9114                 LEFT: 37,
9115                 RIGHT: 39,
9116                 SPACEBAR: 32,
9117                 TAB: 9,
9118                 UP: 38,
9119
9120                 modifierPressed: function(e) {
9121                         return e.shiftKey || e.ctrlKey || e.altKey;
9122                 },
9123
9124                 metaKeyPressed: function(e) {
9125                         // Check if ctrl or meta key is pressed also check if alt is false for Polish users
9126                         return (Env.mac ? e.ctrlKey || e.metaKey : e.ctrlKey) && !e.altKey;
9127                 }
9128         };
9129 });
9130
9131 // Included from: js/tinymce/classes/dom/ControlSelection.js
9132
9133 /**
9134  * ControlSelection.js
9135  *
9136  * Copyright, Moxiecode Systems AB
9137  * Released under LGPL License.
9138  *
9139  * License: http://www.tinymce.com/license
9140  * Contributing: http://www.tinymce.com/contributing
9141  */
9142
9143 /**
9144  * This class handles control selection of elements. Controls are elements
9145  * that can be resized and needs to be selected as a whole. It adds custom resize handles
9146  * to all browser engines that support properly disabling the built in resize logic.
9147  *
9148  * @class tinymce.dom.ControlSelection
9149  */
9150 define("tinymce/dom/ControlSelection", [
9151         "tinymce/util/VK",
9152         "tinymce/util/Tools",
9153         "tinymce/Env"
9154 ], function(VK, Tools, Env) {
9155         return function(selection, editor) {
9156                 var dom = editor.dom, each = Tools.each;
9157                 var selectedElm, selectedElmGhost, resizeHandles, selectedHandle, lastMouseDownEvent;
9158                 var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
9159                 var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
9160
9161                 // Details about each resize handle how to scale etc
9162                 resizeHandles = {
9163                         // Name: x multiplier, y multiplier, delta size x, delta size y
9164                         n:  [0.5,   0,     0,   -1],
9165                         e:  [1,    0.5,    1,    0],
9166                         s:  [0.5,   1,     0,    1],
9167                         w:  [0,    0.5,   -1,    0],
9168                         nw: [0,     0,    -1,   -1],
9169                         ne: [1,     0,     1,   -1],
9170                         se: [1,     1,     1,    1],
9171                         sw: [0,     1,    -1,    1]
9172                 };
9173
9174                 // Add CSS for resize handles, cloned element and selected
9175                 var rootClass = '.mce-content-body';
9176                 editor.contentStyles.push(
9177                         rootClass + ' div.mce-resizehandle {' +
9178                                 'position: absolute;' +
9179                                 'border: 1px solid black;' +
9180                                 'background: #FFF;' +
9181                                 'width: 5px;' +
9182                                 'height: 5px;' +
9183                                 'z-index: 10000' +
9184                         '}' +
9185                         rootClass + ' .mce-resizehandle:hover {' +
9186                                 'background: #000' +
9187                         '}' +
9188                         rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
9189                                 'outline: 1px solid black;' +
9190                                 'resize: none' + // Have been talks about implementing this in browsers
9191                         '}' +
9192                         rootClass + ' .mce-clonedresizable {' +
9193                                 'position: absolute;' +
9194                                 (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
9195                                 'opacity: .5;' +
9196                                 'filter: alpha(opacity=50);' +
9197                                 'z-index: 10000' +
9198                         '}'
9199                 );
9200
9201                 function isResizable(elm) {
9202                         if (editor.settings.object_resizing === false) {
9203                                 return false;
9204                         }
9205
9206                         if (!/TABLE|IMG|DIV/.test(elm.nodeName)) {
9207                                 return false;
9208                         }
9209
9210                         if (elm.getAttribute('data-mce-resize') === 'false') {
9211                                 return false;
9212                         }
9213
9214                         return true;
9215                 }
9216
9217                 function resizeGhostElement(e) {
9218                         var deltaX, deltaY;
9219
9220                         // Calc new width/height
9221                         deltaX = e.screenX - startX;
9222                         deltaY = e.screenY - startY;
9223
9224                         // Calc new size
9225                         width = deltaX * selectedHandle[2] + startW;
9226                         height = deltaY * selectedHandle[3] + startH;
9227
9228                         // Never scale down lower than 5 pixels
9229                         width = width < 5 ? 5 : width;
9230                         height = height < 5 ? 5 : height;
9231
9232                         // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
9233                         if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
9234                                 width = Math.round(height / ratio);
9235                                 height = Math.round(width * ratio);
9236                         }
9237
9238                         // Update ghost size
9239                         dom.setStyles(selectedElmGhost, {
9240                                 width: width,
9241                                 height: height
9242                         });
9243
9244                         // Update ghost X position if needed
9245                         if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
9246                                 dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
9247                         }
9248
9249                         // Update ghost Y position if needed
9250                         if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
9251                                 dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
9252                         }
9253
9254                         if (!resizeStarted) {
9255                                 editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
9256                                 resizeStarted = true;
9257                         }
9258                 }
9259
9260                 function endGhostResize() {
9261                         resizeStarted = false;
9262
9263                         function setSizeProp(name, value) {
9264                                 if (value) {
9265                                         // Resize by using style or attribute
9266                                         if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
9267                                                 dom.setStyle(selectedElm, name, value);
9268                                         } else {
9269                                                 dom.setAttrib(selectedElm, name, value);
9270                                         }
9271                                 }
9272                         }
9273
9274                         // Set width/height properties
9275                         setSizeProp('width', width);
9276                         setSizeProp('height', height);
9277
9278                         dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
9279                         dom.unbind(editableDoc, 'mouseup', endGhostResize);
9280
9281                         if (rootDocument != editableDoc) {
9282                                 dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
9283                                 dom.unbind(rootDocument, 'mouseup', endGhostResize);
9284                         }
9285
9286                         // Remove ghost and update resize handle positions
9287                         dom.remove(selectedElmGhost);
9288
9289                         if (!isIE || selectedElm.nodeName == "TABLE") {
9290                                 showResizeRect(selectedElm);
9291                         }
9292
9293                         editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
9294                         editor.nodeChanged();
9295                 }
9296
9297                 function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
9298                         var position, targetWidth, targetHeight, e, rect;
9299
9300                         // Fix when inline element is within a relaive container
9301                         var offsetParent = editor.getBody().offsetParent || editor.getBody();
9302
9303                         // Get position and size of target
9304                         position = dom.getPos(targetElm, offsetParent);
9305                         selectedElmX = position.x;
9306                         selectedElmY = position.y;
9307                         rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
9308                         targetWidth = rect.width || (rect.right - rect.left);
9309                         targetHeight = rect.height || (rect.bottom - rect.top);
9310
9311                         // Reset width/height if user selects a new image/table
9312                         if (selectedElm != targetElm) {
9313                                 detachResizeStartListener();
9314                                 selectedElm = targetElm;
9315                                 width = height = 0;
9316                         }
9317
9318                         // Makes it possible to disable resizing
9319                         e = editor.fire('ObjectSelected', {target: targetElm});
9320
9321                         if (isResizable(targetElm) && !e.isDefaultPrevented()) {
9322                                 each(resizeHandles, function(handle, name) {
9323                                         var handleElm, handlerContainerElm;
9324
9325                                         function startDrag(e) {
9326                                                 resizeStarted = true;
9327
9328                                                 startX = e.screenX;
9329                                                 startY = e.screenY;
9330                                                 startW = selectedElm.clientWidth;
9331                                                 startH = selectedElm.clientHeight;
9332                                                 ratio = startH / startW;
9333                                                 selectedHandle = handle;
9334
9335                                                 selectedElmGhost = selectedElm.cloneNode(true);
9336                                                 dom.addClass(selectedElmGhost, 'mce-clonedresizable');
9337                                                 selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
9338                                                 selectedElmGhost.unSelectabe = true;
9339                                                 dom.setStyles(selectedElmGhost, {
9340                                                         left: selectedElmX,
9341                                                         top: selectedElmY,
9342                                                         margin: 0
9343                                                 });
9344
9345                                                 selectedElmGhost.removeAttribute('data-mce-selected');
9346                                                 editor.getBody().appendChild(selectedElmGhost);
9347
9348                                                 dom.bind(editableDoc, 'mousemove', resizeGhostElement);
9349                                                 dom.bind(editableDoc, 'mouseup', endGhostResize);
9350
9351                                                 if (rootDocument != editableDoc) {
9352                                                         dom.bind(rootDocument, 'mousemove', resizeGhostElement);
9353                                                         dom.bind(rootDocument, 'mouseup', endGhostResize);
9354                                                 }
9355                                         }
9356
9357                                         if (mouseDownHandleName) {
9358                                                 // Drag started by IE native resizestart
9359                                                 if (name == mouseDownHandleName) {
9360                                                         startDrag(mouseDownEvent);
9361                                                 }
9362
9363                                                 return;
9364                                         }
9365
9366                                         // Get existing or render resize handle
9367                                         handleElm = dom.get('mceResizeHandle' + name);
9368                                         if (!handleElm) {
9369                                                 handlerContainerElm = editor.getBody();
9370
9371                                                 handleElm = dom.add(handlerContainerElm, 'div', {
9372                                                         id: 'mceResizeHandle' + name,
9373                                                         'data-mce-bogus': true,
9374                                                         'class': 'mce-resizehandle',
9375                                                         contentEditable: false, // Hides IE move layer cursor
9376                                                         unSelectabe: true,
9377                                                         style: 'cursor:' + name + '-resize; margin:0; padding:0'
9378                                                 });
9379
9380                                                 dom.bind(handleElm, 'mousedown', function(e) {
9381                                                         e.preventDefault();
9382                                                         startDrag(e);
9383                                                 });
9384                                         } else {
9385                                                 dom.show(handleElm);
9386                                         }
9387
9388                                         /*
9389                                         var halfHandleW = handleElm.offsetWidth / 2;
9390                                         var halfHandleH = handleElm.offsetHeight / 2;
9391
9392                                         // Position element
9393                                         dom.setStyles(handleElm, {
9394                                                 left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)),
9395                                                 top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH))
9396                                         });
9397                                         */
9398
9399                                         // Position element
9400                                         dom.setStyles(handleElm, {
9401                                                 left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
9402                                                 top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
9403                                         });
9404                                 });
9405                         } else {
9406                                 hideResizeRect();
9407                         }
9408
9409                         selectedElm.setAttribute('data-mce-selected', '1');
9410                 }
9411
9412                 function hideResizeRect() {
9413                         var name, handleElm;
9414
9415                         if (selectedElm) {
9416                                 selectedElm.removeAttribute('data-mce-selected');
9417                         }
9418
9419                         for (name in resizeHandles) {
9420                                 handleElm = dom.get('mceResizeHandle' + name);
9421                                 if (handleElm) {
9422                                         dom.unbind(handleElm);
9423                                         dom.remove(handleElm);
9424                                 }
9425                         }
9426                 }
9427
9428                 function updateResizeRect(e) {
9429                         var controlElm;
9430
9431                         function isChildOrEqual(node, parent) {
9432                                 do {
9433                                         if (node === parent) {
9434                                                 return true;
9435                                         }
9436                                 } while ((node = node.parentNode));
9437                         }
9438
9439                         // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
9440                         each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
9441                                 img.removeAttribute('data-mce-selected');
9442                         });
9443
9444                         controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
9445                         controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr');
9446
9447                         if (controlElm) {
9448                                 disableGeckoResize();
9449
9450                                 if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) {
9451                                         if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) {
9452                                                 showResizeRect(controlElm);
9453                                                 return;
9454                                         }
9455                                 }
9456                         }
9457
9458                         hideResizeRect();
9459                 }
9460
9461                 function attachEvent(elm, name, func) {
9462                         if (elm && elm.attachEvent) {
9463                                 elm.attachEvent('on' + name, func);
9464                         }
9465                 }
9466
9467                 function detachEvent(elm, name, func) {
9468                         if (elm && elm.detachEvent) {
9469                                 elm.detachEvent('on' + name, func);
9470                         }
9471                 }
9472
9473                 function resizeNativeStart(e) {
9474                         var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
9475
9476                         pos = target.getBoundingClientRect();
9477                         relativeX = lastMouseDownEvent.clientX - pos.left;
9478                         relativeY = lastMouseDownEvent.clientY - pos.top;
9479
9480                         // Figure out what corner we are draging on
9481                         for (name in resizeHandles) {
9482                                 corner = resizeHandles[name];
9483
9484                                 cornerX = target.offsetWidth * corner[0];
9485                                 cornerY = target.offsetHeight * corner[1];
9486
9487                                 if (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) {
9488                                         selectedHandle = corner;
9489                                         break;
9490                                 }
9491                         }
9492
9493                         // Remove native selection and let the magic begin
9494                         resizeStarted = true;
9495                         editor.getDoc().selection.empty();
9496                         showResizeRect(target, name, lastMouseDownEvent);
9497                 }
9498
9499                 function nativeControlSelect(e) {
9500                         var target = e.srcElement;
9501
9502                         if (target != selectedElm) {
9503                                 detachResizeStartListener();
9504
9505                                 if (target.id.indexOf('mceResizeHandle') === 0) {
9506                                         e.returnValue = false;
9507                                         return;
9508                                 }
9509
9510                                 if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
9511                                         hideResizeRect();
9512                                         selectedElm = target;
9513                                         attachEvent(target, 'resizestart', resizeNativeStart);
9514                                 }
9515                         }
9516                 }
9517
9518                 function detachResizeStartListener() {
9519                         detachEvent(selectedElm, 'resizestart', resizeNativeStart);
9520                 }
9521
9522                 function disableGeckoResize() {
9523                         try {
9524                                 // Disable object resizing on Gecko
9525                                 editor.getDoc().execCommand('enableObjectResizing', false, false);
9526                         } catch (ex) {
9527                                 // Ignore
9528                         }
9529                 }
9530
9531                 function controlSelect(elm) {
9532                         var ctrlRng;
9533
9534                         if (!isIE) {
9535                                 return;
9536                         }
9537
9538                         ctrlRng = editableDoc.body.createControlRange();
9539
9540                         try {
9541                                 ctrlRng.addElement(elm);
9542                                 ctrlRng.select();
9543                                 return true;
9544                         } catch (ex) {
9545                                 // Ignore since the element can't be control selected for example a P tag
9546                         }
9547                 }
9548
9549                 editor.on('init', function() {
9550                         if (isIE) {
9551                                 // Hide the resize rect on resize and reselect the image
9552                                 editor.on('ObjectResized', function(e) {
9553                                         if (e.target.nodeName != 'TABLE') {
9554                                                 hideResizeRect();
9555                                                 controlSelect(e.target);
9556                                         }
9557                                 });
9558
9559                                 attachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
9560
9561                                 editor.on('mousedown', function(e) {
9562                                         lastMouseDownEvent = e;
9563                                 });
9564                         } else {
9565                                 disableGeckoResize();
9566
9567                                 if (Env.ie >= 11) {
9568                                         // TODO: Drag/drop doesn't work
9569                                         editor.on('mouseup mousedown', function(e) {
9570                                                 if (e.target.nodeName == 'IMG' || editor.selection.getNode().nodeName == 'IMG') {
9571                                                         e.preventDefault();
9572                                                         editor.selection.select(e.target);
9573                                                 }
9574                                         });
9575                                 }
9576                         }
9577
9578                         editor.on('nodechange mousedown ResizeEditor', updateResizeRect);
9579
9580                         // Update resize rect while typing in a table
9581                         editor.on('keydown keyup', function(e) {
9582                                 if (selectedElm && selectedElm.nodeName == "TABLE") {
9583                                         updateResizeRect(e);
9584                                 }
9585                         });
9586
9587                         // Hide rect on focusout since it would float on top of windows otherwise
9588                         //editor.on('focusout', hideResizeRect);
9589                 });
9590
9591                 function destroy() {
9592                         selectedElm = selectedElmGhost = null;
9593
9594                         if (isIE) {
9595                                 detachResizeStartListener();
9596                                 detachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
9597                         }
9598                 }
9599
9600                 return {
9601                         controlSelect: controlSelect,
9602                         destroy: destroy
9603                 };
9604         };
9605 });
9606
9607 // Included from: js/tinymce/classes/dom/Selection.js
9608
9609 /**
9610  * Selection.js
9611  *
9612  * Copyright, Moxiecode Systems AB
9613  * Released under LGPL License.
9614  *
9615  * License: http://www.tinymce.com/license
9616  * Contributing: http://www.tinymce.com/contributing
9617  */
9618
9619 /**
9620  * This class handles text and control selection it's an crossbrowser utility class.
9621  * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
9622  *
9623  * @class tinymce.dom.Selection
9624  * @example
9625  * // Getting the currently selected node for the active editor
9626  * alert(tinymce.activeEditor.selection.getNode().nodeName);
9627  */
9628 define("tinymce/dom/Selection", [
9629         "tinymce/dom/TreeWalker",
9630         "tinymce/dom/TridentSelection",
9631         "tinymce/dom/ControlSelection",
9632         "tinymce/Env",
9633         "tinymce/util/Tools"
9634 ], function(TreeWalker, TridentSelection, ControlSelection, Env, Tools) {
9635         var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
9636         var isIE = Env.ie, isOpera = Env.opera;
9637
9638         /**
9639          * Constructs a new selection instance.
9640          *
9641          * @constructor
9642          * @method Selection
9643          * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
9644          * @param {Window} win Window to bind the selection object to.
9645          * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
9646          */
9647         function Selection(dom, win, serializer, editor) {
9648                 var self = this;
9649
9650                 self.dom = dom;
9651                 self.win = win;
9652                 self.serializer = serializer;
9653                 self.editor = editor;
9654
9655                 self.controlSelection = new ControlSelection(self, editor);
9656
9657                 // No W3C Range support
9658                 if (!self.win.getSelection) {
9659                         self.tridentSel = new TridentSelection(self);
9660                 }
9661         }
9662
9663         Selection.prototype = {
9664                 /**
9665                  * Move the selection cursor range to the specified node and offset.
9666                  *
9667                  * @method setCursorLocation
9668                  * @param {Node} node Node to put the cursor in.
9669                  * @param {Number} offset Offset from the start of the node to put the cursor at.
9670                  */
9671                 setCursorLocation: function(node, offset) {
9672                         var self = this, rng = self.dom.createRng();
9673                         rng.setStart(node, offset);
9674                         rng.setEnd(node, offset);
9675                         self.setRng(rng);
9676                         self.collapse(false);
9677                 },
9678
9679                 /**
9680                  * Returns the selected contents using the DOM serializer passed in to this class.
9681                  *
9682                  * @method getContent
9683                  * @param {Object} s Optional settings class with for example output format text or html.
9684                  * @return {String} Selected contents in for example HTML format.
9685                  * @example
9686                  * // Alerts the currently selected contents
9687                  * alert(tinymce.activeEditor.selection.getContent());
9688                  *
9689                  * // Alerts the currently selected contents as plain text
9690                  * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
9691                  */
9692                 getContent: function(args) {
9693                         var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
9694                         var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
9695
9696                         args = args || {};
9697                         whiteSpaceBefore = whiteSpaceAfter = '';
9698                         args.get = true;
9699                         args.format = args.format || 'html';
9700                         args.selection = true;
9701                         self.editor.fire('BeforeGetContent', args);
9702
9703                         if (args.format == 'text') {
9704                                 return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
9705                         }
9706
9707                         if (rng.cloneContents) {
9708                                 fragment = rng.cloneContents();
9709
9710                                 if (fragment) {
9711                                         tmpElm.appendChild(fragment);
9712                                 }
9713                         } else if (rng.item !== undefined || rng.htmlText !== undefined) {
9714                                 // IE will produce invalid markup if elements are present that
9715                                 // it doesn't understand like custom elements or HTML5 elements.
9716                                 // Adding a BR in front of the contents and then remoiving it seems to fix it though.
9717                                 tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
9718                                 tmpElm.removeChild(tmpElm.firstChild);
9719                         } else {
9720                                 tmpElm.innerHTML = rng.toString();
9721                         }
9722
9723                         // Keep whitespace before and after
9724                         if (/^\s/.test(tmpElm.innerHTML)) {
9725                                 whiteSpaceBefore = ' ';
9726                         }
9727
9728                         if (/\s+$/.test(tmpElm.innerHTML)) {
9729                                 whiteSpaceAfter = ' ';
9730                         }
9731
9732                         args.getInner = true;
9733
9734                         args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
9735                         self.editor.fire('GetContent', args);
9736
9737                         return args.content;
9738                 },
9739
9740                 /**
9741                  * Sets the current selection to the specified content. If any contents is selected it will be replaced
9742                  * with the contents passed in to this function. If there is no selection the contents will be inserted
9743                  * where the caret is placed in the editor/page.
9744                  *
9745                  * @method setContent
9746                  * @param {String} content HTML contents to set could also be other formats depending on settings.
9747                  * @param {Object} args Optional settings object with for example data format.
9748                  * @example
9749                  * // Inserts some HTML contents at the current selection
9750                  * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
9751                  */
9752                 setContent: function(content, args) {
9753                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9754
9755                         args = args || {format: 'html'};
9756                         args.set = true;
9757                         args.selection = true;
9758                         content = args.content = content;
9759
9760                         // Dispatch before set content event
9761                         if (!args.no_events) {
9762                                 self.editor.fire('BeforeSetContent', args);
9763                         }
9764
9765                         content = args.content;
9766
9767                         if (rng.insertNode) {
9768                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
9769                                 content += '<span id="__caret">_</span>';
9770
9771                                 // Delete and insert new node
9772                                 if (rng.startContainer == doc && rng.endContainer == doc) {
9773                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9774                                         doc.body.innerHTML = content;
9775                                 } else {
9776                                         rng.deleteContents();
9777
9778                                         if (doc.body.childNodes.length === 0) {
9779                                                 doc.body.innerHTML = content;
9780                                         } else {
9781                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
9782                                                 if (rng.createContextualFragment) {
9783                                                         rng.insertNode(rng.createContextualFragment(content));
9784                                                 } else {
9785                                                         // Fake createContextualFragment call in IE 9
9786                                                         frag = doc.createDocumentFragment();
9787                                                         temp = doc.createElement('div');
9788
9789                                                         frag.appendChild(temp);
9790                                                         temp.outerHTML = content;
9791
9792                                                         rng.insertNode(frag);
9793                                                 }
9794                                         }
9795                                 }
9796
9797                                 // Move to caret marker
9798                                 caretNode = self.dom.get('__caret');
9799
9800                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
9801                                 rng = doc.createRange();
9802                                 rng.setStartBefore(caretNode);
9803                                 rng.setEndBefore(caretNode);
9804                                 self.setRng(rng);
9805
9806                                 // Remove the caret position
9807                                 self.dom.remove('__caret');
9808
9809                                 try {
9810                                         self.setRng(rng);
9811                                 } catch (ex) {
9812                                         // Might fail on Opera for some odd reason
9813                                 }
9814                         } else {
9815                                 if (rng.item) {
9816                                         // Delete content and get caret text selection
9817                                         doc.execCommand('Delete', false, null);
9818                                         rng = self.getRng();
9819                                 }
9820
9821                                 // Explorer removes spaces from the beginning of pasted contents
9822                                 if (/^\s+/.test(content)) {
9823                                         rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9824                                         self.dom.remove('__mce_tmp');
9825                                 } else {
9826                                         rng.pasteHTML(content);
9827                                 }
9828                         }
9829
9830                         // Dispatch set content event
9831                         if (!args.no_events) {
9832                                 self.editor.fire('SetContent', args);
9833                         }
9834                 },
9835
9836                 /**
9837                  * Returns the start element of a selection range. If the start is in a text
9838                  * node the parent element will be returned.
9839                  *
9840                  * @method getStart
9841                  * @return {Element} Start element of selection range.
9842                  */
9843                 getStart: function() {
9844                         var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9845
9846                         if (rng.duplicate || rng.item) {
9847                                 // Control selection, return first item
9848                                 if (rng.item) {
9849                                         return rng.item(0);
9850                                 }
9851
9852                                 // Get start element
9853                                 checkRng = rng.duplicate();
9854                                 checkRng.collapse(1);
9855                                 startElement = checkRng.parentElement();
9856                                 if (startElement.ownerDocument !== self.dom.doc) {
9857                                         startElement = self.dom.getRoot();
9858                                 }
9859
9860                                 // Check if range parent is inside the start element, then return the inner parent element
9861                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9862                                 parentElement = node = rng.parentElement();
9863                                 while ((node = node.parentNode)) {
9864                                         if (node == startElement) {
9865                                                 startElement = parentElement;
9866                                                 break;
9867                                         }
9868                                 }
9869
9870                                 return startElement;
9871                         } else {
9872                                 startElement = rng.startContainer;
9873
9874                                 if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
9875                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9876                                 }
9877
9878                                 if (startElement && startElement.nodeType == 3) {
9879                                         return startElement.parentNode;
9880                                 }
9881
9882                                 return startElement;
9883                         }
9884                 },
9885
9886                 /**
9887                  * Returns the end element of a selection range. If the end is in a text
9888                  * node the parent element will be returned.
9889                  *
9890                  * @method getEnd
9891                  * @return {Element} End element of selection range.
9892                  */
9893                 getEnd: function() {
9894                         var self = this, rng = self.getRng(), endElement, endOffset;
9895
9896                         if (rng.duplicate || rng.item) {
9897                                 if (rng.item) {
9898                                         return rng.item(0);
9899                                 }
9900
9901                                 rng = rng.duplicate();
9902                                 rng.collapse(0);
9903                                 endElement = rng.parentElement();
9904                                 if (endElement.ownerDocument !== self.dom.doc) {
9905                                         endElement = self.dom.getRoot();
9906                                 }
9907
9908                                 if (endElement && endElement.nodeName == 'BODY') {
9909                                         return endElement.lastChild || endElement;
9910                                 }
9911
9912                                 return endElement;
9913                         } else {
9914                                 endElement = rng.endContainer;
9915                                 endOffset = rng.endOffset;
9916
9917                                 if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
9918                                         endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9919                                 }
9920
9921                                 if (endElement && endElement.nodeType == 3) {
9922                                         return endElement.parentNode;
9923                                 }
9924
9925                                 return endElement;
9926                         }
9927                 },
9928
9929                 /**
9930                  * Returns a bookmark location for the current selection. This bookmark object
9931                  * can then be used to restore the selection after some content modification to the document.
9932                  *
9933                  * @method getBookmark
9934                  * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
9935                  * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
9936                  * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
9937                  * @example
9938                  * // Stores a bookmark of the current selection
9939                  * var bm = tinymce.activeEditor.selection.getBookmark();
9940                  *
9941                  * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
9942                  *
9943                  * // Restore the selection bookmark
9944                  * tinymce.activeEditor.selection.moveToBookmark(bm);
9945                  */
9946                 getBookmark: function(type, normalized) {
9947                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
9948
9949                         function findIndex(name, element) {
9950                                 var index = 0;
9951
9952                                 each(dom.select(name), function(node, i) {
9953                                         if (node == element) {
9954                                                 index = i;
9955                                         }
9956                                 });
9957
9958                                 return index;
9959                         }
9960
9961                         function normalizeTableCellSelection(rng) {
9962                                 function moveEndPoint(start) {
9963                                         var container, offset, childNodes, prefix = start ? 'start' : 'end';
9964
9965                                         container = rng[prefix + 'Container'];
9966                                         offset = rng[prefix + 'Offset'];
9967
9968                                         if (container.nodeType == 1 && container.nodeName == "TR") {
9969                                                 childNodes = container.childNodes;
9970                                                 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9971                                                 if (container) {
9972                                                         offset = start ? 0 : container.childNodes.length;
9973                                                         rng['set' + (start ? 'Start' : 'End')](container, offset);
9974                                                 }
9975                                         }
9976                                 }
9977
9978                                 moveEndPoint(true);
9979                                 moveEndPoint();
9980
9981                                 return rng;
9982                         }
9983
9984                         function getLocation() {
9985                                 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9986
9987                                 function getPoint(rng, start) {
9988                                         var container = rng[start ? 'startContainer' : 'endContainer'],
9989                                                 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9990
9991                                         if (container.nodeType == 3) {
9992                                                 if (normalized) {
9993                                                         for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
9994                                                                 offset += node.nodeValue.length;
9995                                                         }
9996                                                 }
9997
9998                                                 point.push(offset);
9999                                         } else {
10000                                                 childNodes = container.childNodes;
10001
10002                                                 if (offset >= childNodes.length && childNodes.length) {
10003                                                         after = 1;
10004                                                         offset = Math.max(0, childNodes.length - 1);
10005                                                 }
10006
10007                                                 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
10008                                         }
10009
10010                                         for (; container && container != root; container = container.parentNode) {
10011                                                 point.push(t.dom.nodeIndex(container, normalized));
10012                                         }
10013
10014                                         return point;
10015                                 }
10016
10017                                 bookmark.start = getPoint(rng, true);
10018
10019                                 if (!t.isCollapsed()) {
10020                                         bookmark.end = getPoint(rng);
10021                                 }
10022
10023                                 return bookmark;
10024                         }
10025
10026                         if (type == 2) {
10027                                 element = t.getNode();
10028                                 name = element.nodeName;
10029
10030                                 if (name == 'IMG') {
10031                                         return {name: name, index: findIndex(name, element)};
10032                                 }
10033
10034                                 if (t.tridentSel) {
10035                                         return t.tridentSel.getBookmark(type);
10036                                 }
10037
10038                                 return getLocation();
10039                         }
10040
10041                         // Handle simple range
10042                         if (type) {
10043                                 return {rng: t.getRng()};
10044                         }
10045
10046                         rng = t.getRng();
10047                         id = dom.uniqueId();
10048                         collapsed = t.isCollapsed();
10049                         styles = 'overflow:hidden;line-height:0px';
10050
10051                         // Explorer method
10052                         if (rng.duplicate || rng.item) {
10053                                 // Text selection
10054                                 if (!rng.item) {
10055                                         rng2 = rng.duplicate();
10056
10057                                         try {
10058                                                 // Insert start marker
10059                                                 rng.collapse();
10060                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
10061
10062                                                 // Insert end marker
10063                                                 if (!collapsed) {
10064                                                         rng2.collapse(false);
10065
10066                                                         // Detect the empty space after block elements in IE and move the
10067                                                         // end back one character <p></p>] becomes <p>]</p>
10068                                                         rng.moveToElementText(rng2.parentElement());
10069                                                         if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
10070                                                                 rng2.move('character', -1);
10071                                                         }
10072
10073                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
10074                                                 }
10075                                         } catch (ex) {
10076                                                 // IE might throw unspecified error so lets ignore it
10077                                                 return null;
10078                                         }
10079                                 } else {
10080                                         // Control selection
10081                                         element = rng.item(0);
10082                                         name = element.nodeName;
10083
10084                                         return {name: name, index: findIndex(name, element)};
10085                                 }
10086                         } else {
10087                                 element = t.getNode();
10088                                 name = element.nodeName;
10089                                 if (name == 'IMG') {
10090                                         return {name: name, index: findIndex(name, element)};
10091                                 }
10092
10093                                 // W3C method
10094                                 rng2 = normalizeTableCellSelection(rng.cloneRange());
10095
10096                                 // Insert end marker
10097                                 if (!collapsed) {
10098                                         rng2.collapse(false);
10099                                         rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
10100                                 }
10101
10102                                 rng = normalizeTableCellSelection(rng);
10103                                 rng.collapse(true);
10104                                 rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
10105                         }
10106
10107                         t.moveToBookmark({id: id, keep: 1});
10108
10109                         return {id: id};
10110                 },
10111
10112                 /**
10113                  * Restores the selection to the specified bookmark.
10114                  *
10115                  * @method moveToBookmark
10116                  * @param {Object} bookmark Bookmark to restore selection from.
10117                  * @return {Boolean} true/false if it was successful or not.
10118                  * @example
10119                  * // Stores a bookmark of the current selection
10120                  * var bm = tinymce.activeEditor.selection.getBookmark();
10121                  *
10122                  * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
10123                  *
10124                  * // Restore the selection bookmark
10125                  * tinymce.activeEditor.selection.moveToBookmark(bm);
10126                  */
10127                 moveToBookmark: function(bookmark) {
10128                         var t = this, dom = t.dom, rng, root, startContainer, endContainer, startOffset, endOffset;
10129
10130                         function setEndPoint(start) {
10131                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
10132
10133                                 if (point) {
10134                                         offset = point[0];
10135
10136                                         // Find container node
10137                                         for (node = root, i = point.length - 1; i >= 1; i--) {
10138                                                 children = node.childNodes;
10139
10140                                                 if (point[i] > children.length - 1) {
10141                                                         return;
10142                                                 }
10143
10144                                                 node = children[point[i]];
10145                                         }
10146
10147                                         // Move text offset to best suitable location
10148                                         if (node.nodeType === 3) {
10149                                                 offset = Math.min(point[0], node.nodeValue.length);
10150                                         }
10151
10152                                         // Move element offset to best suitable location
10153                                         if (node.nodeType === 1) {
10154                                                 offset = Math.min(point[0], node.childNodes.length);
10155                                         }
10156
10157                                         // Set offset within container node
10158                                         if (start) {
10159                                                 rng.setStart(node, offset);
10160                                         } else {
10161                                                 rng.setEnd(node, offset);
10162                                         }
10163                                 }
10164
10165                                 return true;
10166                         }
10167
10168                         function restoreEndPoint(suffix) {
10169                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
10170
10171                                 if (marker) {
10172                                         node = marker.parentNode;
10173
10174                                         if (suffix == 'start') {
10175                                                 if (!keep) {
10176                                                         idx = dom.nodeIndex(marker);
10177                                                 } else {
10178                                                         node = marker.firstChild;
10179                                                         idx = 1;
10180                                                 }
10181
10182                                                 startContainer = endContainer = node;
10183                                                 startOffset = endOffset = idx;
10184                                         } else {
10185                                                 if (!keep) {
10186                                                         idx = dom.nodeIndex(marker);
10187                                                 } else {
10188                                                         node = marker.firstChild;
10189                                                         idx = 1;
10190                                                 }
10191
10192                                                 endContainer = node;
10193                                                 endOffset = idx;
10194                                         }
10195
10196                                         if (!keep) {
10197                                                 prev = marker.previousSibling;
10198                                                 next = marker.nextSibling;
10199
10200                                                 // Remove all marker text nodes
10201                                                 each(grep(marker.childNodes), function(node) {
10202                                                         if (node.nodeType == 3) {
10203                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
10204                                                         }
10205                                                 });
10206
10207                                                 // Remove marker but keep children if for example contents where inserted into the marker
10208                                                 // Also remove duplicated instances of the marker for example by a
10209                                                 // split operation or by WebKit auto split on paste feature
10210                                                 while ((marker = dom.get(bookmark.id + '_' + suffix))) {
10211                                                         dom.remove(marker, 1);
10212                                                 }
10213
10214                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
10215                                                 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
10216                                                 // isn't worth the effort. Sorry, Opera but it's just a fact
10217                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) {
10218                                                         idx = prev.nodeValue.length;
10219                                                         prev.appendData(next.nodeValue);
10220                                                         dom.remove(next);
10221
10222                                                         if (suffix == 'start') {
10223                                                                 startContainer = endContainer = prev;
10224                                                                 startOffset = endOffset = idx;
10225                                                         } else {
10226                                                                 endContainer = prev;
10227                                                                 endOffset = idx;
10228                                                         }
10229                                                 }
10230                                         }
10231                                 }
10232                         }
10233
10234                         function addBogus(node) {
10235                                 // Adds a bogus BR element for empty block elements
10236                                 if (dom.isBlock(node) && !node.innerHTML && !isIE) {
10237                                         node.innerHTML = '<br data-mce-bogus="1" />';
10238                                 }
10239
10240                                 return node;
10241                         }
10242
10243                         if (bookmark) {
10244                                 if (bookmark.start) {
10245                                         rng = dom.createRng();
10246                                         root = dom.getRoot();
10247
10248                                         if (t.tridentSel) {
10249                                                 return t.tridentSel.moveToBookmark(bookmark);
10250                                         }
10251
10252                                         if (setEndPoint(true) && setEndPoint()) {
10253                                                 t.setRng(rng);
10254                                         }
10255                                 } else if (bookmark.id) {
10256                                         // Restore start/end points
10257                                         restoreEndPoint('start');
10258                                         restoreEndPoint('end');
10259
10260                                         if (startContainer) {
10261                                                 rng = dom.createRng();
10262                                                 rng.setStart(addBogus(startContainer), startOffset);
10263                                                 rng.setEnd(addBogus(endContainer), endOffset);
10264                                                 t.setRng(rng);
10265                                         }
10266                                 } else if (bookmark.name) {
10267                                         t.select(dom.select(bookmark.name)[bookmark.index]);
10268                                 } else if (bookmark.rng) {
10269                                         t.setRng(bookmark.rng);
10270                                 }
10271                         }
10272                 },
10273
10274                 /**
10275                  * Selects the specified element. This will place the start and end of the selection range around the element.
10276                  *
10277                  * @method select
10278                  * @param {Element} node HMTL DOM element to select.
10279                  * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
10280                  * @return {Element} Selected element the same element as the one that got passed in.
10281                  * @example
10282                  * // Select the first paragraph in the active editor
10283                  * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
10284                  */
10285                 select: function(node, content) {
10286                         var self = this, dom = self.dom, rng = dom.createRng(), idx;
10287
10288                         // Clear stored range set by FocusManager
10289                         self.lastFocusBookmark = null;
10290
10291                         function setPoint(node, start) {
10292                                 var walker = new TreeWalker(node, node);
10293
10294                                 do {
10295                                         // Text node
10296                                         if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
10297                                                 if (start) {
10298                                                         rng.setStart(node, 0);
10299                                                 } else {
10300                                                         rng.setEnd(node, node.nodeValue.length);
10301                                                 }
10302
10303                                                 return;
10304                                         }
10305
10306                                         // BR element
10307                                         if (node.nodeName == 'BR') {
10308                                                 if (start) {
10309                                                         rng.setStartBefore(node);
10310                                                 } else {
10311                                                         rng.setEndBefore(node);
10312                                                 }
10313
10314                                                 return;
10315                                         }
10316                                 } while ((node = (start ? walker.next() : walker.prev())));
10317                         }
10318
10319                         if (node) {
10320                                 if (!content && self.controlSelection.controlSelect(node)) {
10321                                         return;
10322                                 }
10323
10324                                 idx = dom.nodeIndex(node);
10325                                 rng.setStart(node.parentNode, idx);
10326                                 rng.setEnd(node.parentNode, idx + 1);
10327
10328                                 // Find first/last text node or BR element
10329                                 if (content) {
10330                                         setPoint(node, 1);
10331                                         setPoint(node);
10332                                 }
10333
10334                                 self.setRng(rng);
10335                         }
10336
10337                         return node;
10338                 },
10339
10340                 /**
10341                  * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
10342                  *
10343                  * @method isCollapsed
10344                  * @return {Boolean} true/false state if the selection range is collapsed or not.
10345                  * Collapsed means if it's a caret or a larger selection.
10346                  */
10347                 isCollapsed: function() {
10348                         var self = this, rng = self.getRng(), sel = self.getSel();
10349
10350                         if (!rng || rng.item) {
10351                                 return false;
10352                         }
10353
10354                         if (rng.compareEndPoints) {
10355                                 return rng.compareEndPoints('StartToEnd', rng) === 0;
10356                         }
10357
10358                         return !sel || rng.collapsed;
10359                 },
10360
10361                 /**
10362                  * Collapse the selection to start or end of range.
10363                  *
10364                  * @method collapse
10365                  * @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start.
10366                  */
10367                 collapse: function(to_start) {
10368                         var self = this, rng = self.getRng(), node;
10369
10370                         // Control range on IE
10371                         if (rng.item) {
10372                                 node = rng.item(0);
10373                                 rng = self.win.document.body.createTextRange();
10374                                 rng.moveToElementText(node);
10375                         }
10376
10377                         rng.collapse(!!to_start);
10378                         self.setRng(rng);
10379                 },
10380
10381                 /**
10382                  * Returns the browsers internal selection object.
10383                  *
10384                  * @method getSel
10385                  * @return {Selection} Internal browser selection object.
10386                  */
10387                 getSel: function() {
10388                         var win = this.win;
10389
10390                         return win.getSelection ? win.getSelection() : win.document.selection;
10391                 },
10392
10393                 /**
10394                  * Returns the browsers internal range object.
10395                  *
10396                  * @method getRng
10397                  * @param {Boolean} w3c Forces a compatible W3C range on IE.
10398                  * @return {Range} Internal browser range object.
10399                  * @see http://www.quirksmode.org/dom/range_intro.html
10400                  * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
10401                  */
10402                 getRng: function(w3c) {
10403                         var self = this, selection, rng, elm, doc = self.win.document, ieRng;
10404
10405                         // Use last rng passed from FocusManager if it's available this enables
10406                         // calls to editor.selection.getStart() to work when caret focus is lost on IE
10407                         if (!w3c && self.lastFocusBookmark) {
10408                                 var bookmark = self.lastFocusBookmark;
10409
10410                                 // Convert bookmark to range IE 11 fix
10411                                 if (bookmark.startContainer) {
10412                                         rng = doc.createRange();
10413                                         rng.setStart(bookmark.startContainer, bookmark.startOffset);
10414                                         rng.setEnd(bookmark.endContainer, bookmark.endOffset);
10415                                 } else {
10416                                         rng = bookmark;
10417                                 }
10418
10419                                 return rng;
10420                         }
10421
10422                         // Found tridentSel object then we need to use that one
10423                         if (w3c && self.tridentSel) {
10424                                 return self.tridentSel.getRangeAt(0);
10425                         }
10426
10427                         try {
10428                                 if ((selection = self.getSel())) {
10429                                         if (selection.rangeCount > 0) {
10430                                                 rng = selection.getRangeAt(0);
10431                                         } else {
10432                                                 rng = selection.createRange ? selection.createRange() : doc.createRange();
10433                                         }
10434                                 }
10435                         } catch (ex) {
10436                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
10437                         }
10438
10439                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
10440                         if (isIE && rng && rng.setStart) {
10441                                 try {
10442                                         // IE will sometimes throw an exception here
10443                                         ieRng = doc.selection.createRange();
10444                                 } catch (ex) {
10445
10446                                 }
10447
10448                                 if (ieRng && ieRng.item) {
10449                                         elm = ieRng.item(0);
10450                                         rng = doc.createRange();
10451                                         rng.setStartBefore(elm);
10452                                         rng.setEndAfter(elm);
10453                                 }
10454                         }
10455
10456                         // No range found then create an empty one
10457                         // This can occur when the editor is placed in a hidden container element on Gecko
10458                         // Or on IE when there was an exception
10459                         if (!rng) {
10460                                 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
10461                         }
10462
10463                         // If range is at start of document then move it to start of body
10464                         if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
10465                                 elm = self.dom.getRoot();
10466                                 rng.setStart(elm, 0);
10467                                 rng.setEnd(elm, 0);
10468                         }
10469
10470                         if (self.selectedRange && self.explicitRange) {
10471                                 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 &&
10472                                         rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
10473                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
10474                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
10475                                         rng = self.explicitRange;
10476                                 } else {
10477                                         self.selectedRange = null;
10478                                         self.explicitRange = null;
10479                                 }
10480                         }
10481
10482                         return rng;
10483                 },
10484
10485                 /**
10486                  * Changes the selection to the specified DOM range.
10487                  *
10488                  * @method setRng
10489                  * @param {Range} rng Range to select.
10490                  */
10491                 setRng: function(rng, forward) {
10492                         var self = this, sel;
10493
10494                         // Is IE specific range
10495                         if (rng.select) {
10496                                 try {
10497                                         rng.select();
10498                                 } catch (ex) {
10499                                         // Needed for some odd IE bug #1843306
10500                                 }
10501
10502                                 return;
10503                         }
10504
10505                         if (!self.tridentSel) {
10506                                 sel = self.getSel();
10507
10508                                 if (sel) {
10509                                         self.explicitRange = rng;
10510
10511                                         try {
10512                                                 sel.removeAllRanges();
10513                                                 sel.addRange(rng);
10514                                         } catch (ex) {
10515                                                 // IE might throw errors here if the editor is within a hidden container and selection is changed
10516                                         }
10517
10518                                         // Forward is set to false and we have an extend function
10519                                         if (forward === false && sel.extend) {
10520                                                 sel.collapse(rng.endContainer, rng.endOffset);
10521                                                 sel.extend(rng.startContainer, rng.startOffset);
10522                                         }
10523
10524                                         // adding range isn't always successful so we need to check range count otherwise an exception can occur
10525                                         self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
10526                                 }
10527                         } else {
10528                                 // Is W3C Range fake range on IE
10529                                 if (rng.cloneRange) {
10530                                         try {
10531                                                 self.tridentSel.addRange(rng);
10532                                                 return;
10533                                         } catch (ex) {
10534                                                 //IE9 throws an error here if called before selection is placed in the editor
10535                                         }
10536                                 }
10537                         }
10538                 },
10539
10540                 /**
10541                  * Sets the current selection to the specified DOM element.
10542                  *
10543                  * @method setNode
10544                  * @param {Element} elm Element to set as the contents of the selection.
10545                  * @return {Element} Returns the element that got passed in.
10546                  * @example
10547                  * // Inserts a DOM node at current selection/caret location
10548                  * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
10549                  */
10550                 setNode: function(elm) {
10551                         var self = this;
10552
10553                         self.setContent(self.dom.getOuterHTML(elm));
10554
10555                         return elm;
10556                 },
10557
10558                 /**
10559                  * Returns the currently selected element or the common ancestor element for both start and end of the selection.
10560                  *
10561                  * @method getNode
10562                  * @return {Element} Currently selected element or common ancestor element.
10563                  * @example
10564                  * // Alerts the currently selected elements node name
10565                  * alert(tinymce.activeEditor.selection.getNode().nodeName);
10566                  */
10567                 getNode: function() {
10568                         var self = this, rng = self.getRng(), elm;
10569                         var startContainer = rng.startContainer, endContainer = rng.endContainer;
10570                         var startOffset = rng.startOffset, endOffset = rng.endOffset;
10571
10572                         function skipEmptyTextNodes(node, forwards) {
10573                                 var orig = node;
10574
10575                                 while (node && node.nodeType === 3 && node.length === 0) {
10576                                         node = forwards ? node.nextSibling : node.previousSibling;
10577                                 }
10578
10579                                 return node || orig;
10580                         }
10581
10582                         // Range maybe lost after the editor is made visible again
10583                         if (!rng) {
10584                                 return self.dom.getRoot();
10585                         }
10586
10587                         if (rng.setStart) {
10588                                 elm = rng.commonAncestorContainer;
10589
10590                                 // Handle selection a image or other control like element such as anchors
10591                                 if (!rng.collapsed) {
10592                                         if (startContainer == endContainer) {
10593                                                 if (endOffset - startOffset < 2) {
10594                                                         if (startContainer.hasChildNodes()) {
10595                                                                 elm = startContainer.childNodes[startOffset];
10596                                                         }
10597                                                 }
10598                                         }
10599
10600                                         // If the anchor node is a element instead of a text node then return this element
10601                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
10602                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
10603
10604                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
10605                                         // This happens when you double click an underlined word in FireFox.
10606                                         if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
10607                                                 if (startContainer.length === startOffset) {
10608                                                         startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
10609                                                 } else {
10610                                                         startContainer = startContainer.parentNode;
10611                                                 }
10612
10613                                                 if (endOffset === 0) {
10614                                                         endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
10615                                                 } else {
10616                                                         endContainer = endContainer.parentNode;
10617                                                 }
10618
10619                                                 if (startContainer && startContainer === endContainer) {
10620                                                         return startContainer;
10621                                                 }
10622                                         }
10623                                 }
10624
10625                                 if (elm && elm.nodeType == 3) {
10626                                         return elm.parentNode;
10627                                 }
10628
10629                                 return elm;
10630                         }
10631
10632                         return rng.item ? rng.item(0) : rng.parentElement();
10633                 },
10634
10635                 getSelectedBlocks: function(startElm, endElm) {
10636                         var self = this, dom = self.dom, node, root, selectedBlocks = [];
10637
10638                         root = dom.getRoot();
10639                         startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
10640                         endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
10641
10642                         if (startElm && startElm != root) {
10643                                 selectedBlocks.push(startElm);
10644                         }
10645
10646                         if (startElm && endElm && startElm != endElm) {
10647                                 node = startElm;
10648
10649                                 var walker = new TreeWalker(startElm, root);
10650                                 while ((node = walker.next()) && node != endElm) {
10651                                         if (dom.isBlock(node)) {
10652                                                 selectedBlocks.push(node);
10653                                         }
10654                                 }
10655                         }
10656
10657                         if (endElm && startElm != endElm && endElm != root) {
10658                                 selectedBlocks.push(endElm);
10659                         }
10660
10661                         return selectedBlocks;
10662                 },
10663
10664                 isForward: function(){
10665                         var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
10666
10667                         // No support for selection direction then always return true
10668                         if (!sel || !sel.anchorNode || !sel.focusNode) {
10669                                 return true;
10670                         }
10671
10672                         anchorRange = dom.createRng();
10673                         anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
10674                         anchorRange.collapse(true);
10675
10676                         focusRange = dom.createRng();
10677                         focusRange.setStart(sel.focusNode, sel.focusOffset);
10678                         focusRange.collapse(true);
10679
10680                         return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
10681                 },
10682
10683                 normalize: function() {
10684                         var self = this, rng, normalized, collapsed;
10685
10686                         function normalizeEndPoint(start) {
10687                                 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
10688
10689                                 function hasBrBeforeAfter(node, left) {
10690                                         var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
10691
10692                                         while ((node = walker[left ? 'prev' : 'next']())) {
10693                                                 if (node.nodeName === "BR") {
10694                                                         return true;
10695                                                 }
10696                                         }
10697                                 }
10698
10699                                 function isPrevNode(node, name) {
10700                                         return node.previousSibling && node.previousSibling.nodeName == name;
10701                                 }
10702
10703                                 // Walks the dom left/right to find a suitable text node to move the endpoint into
10704                                 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
10705                                 function findTextNodeRelative(left, startNode) {
10706                                         var walker, lastInlineElement;
10707
10708                                         startNode = startNode || container;
10709                                         walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
10710
10711                                         // Walk left until we hit a text node we can move to or a block/br/img
10712                                         while ((node = walker[left ? 'prev' : 'next']())) {
10713                                                 // Found text node that has a length
10714                                                 if (node.nodeType === 3 && node.nodeValue.length > 0) {
10715                                                         container = node;
10716                                                         offset = left ? node.nodeValue.length : 0;
10717                                                         normalized = true;
10718                                                         return;
10719                                                 }
10720
10721                                                 // Break if we find a block or a BR/IMG/INPUT etc
10722                                                 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10723                                                         return;
10724                                                 }
10725
10726                                                 lastInlineElement = node;
10727                                         }
10728
10729                                         // Only fetch the last inline element when in caret mode for now
10730                                         if (collapsed && lastInlineElement) {
10731                                                 container = lastInlineElement;
10732                                                 normalized = true;
10733                                                 offset = 0;
10734                                         }
10735                                 }
10736
10737                                 container = rng[(start ? 'start' : 'end') + 'Container'];
10738                                 offset = rng[(start ? 'start' : 'end') + 'Offset'];
10739                                 nonEmptyElementsMap = dom.schema.getNonEmptyElements();
10740
10741                                 // If the container is a document move it to the body element
10742                                 if (container.nodeType === 9) {
10743                                         container = dom.getRoot();
10744                                         offset = 0;
10745                                 }
10746
10747                                 // If the container is body try move it into the closest text node or position
10748                                 if (container === body) {
10749                                         // If start is before/after a image, table etc
10750                                         if (start) {
10751                                                 node = container.childNodes[offset > 0 ? offset - 1 : 0];
10752                                                 if (node) {
10753                                                         nodeName = node.nodeName.toLowerCase();
10754                                                         if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
10755                                                                 return;
10756                                                         }
10757                                                 }
10758                                         }
10759
10760                                         // Resolve the index
10761                                         if (container.hasChildNodes()) {
10762                                                 offset = Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
10763                                                 container = container.childNodes[offset];
10764                                                 offset = 0;
10765
10766                                                 // Don't walk into elements that doesn't have any child nodes like a IMG
10767                                                 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
10768                                                         // Walk the DOM to find a text node to place the caret at or a BR
10769                                                         node = container;
10770                                                         walker = new TreeWalker(container, body);
10771
10772                                                         do {
10773                                                                 // Found a text node use that position
10774                                                                 if (node.nodeType === 3 && node.nodeValue.length > 0) {
10775                                                                         offset = start ? 0 : node.nodeValue.length;
10776                                                                         container = node;
10777                                                                         normalized = true;
10778                                                                         break;
10779                                                                 }
10780
10781                                                                 // Found a BR/IMG element that we can place the caret before
10782                                                                 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10783                                                                         offset = dom.nodeIndex(node);
10784                                                                         container = node.parentNode;
10785
10786                                                                         // Put caret after image when moving the end point
10787                                                                         if (node.nodeName ==  "IMG" && !start) {
10788                                                                                 offset++;
10789                                                                         }
10790
10791                                                                         normalized = true;
10792                                                                         break;
10793                                                                 }
10794                                                         } while ((node = (start ? walker.next() : walker.prev())));
10795                                                 }
10796                                         }
10797                                 }
10798
10799                                 // Lean the caret to the left if possible
10800                                 if (collapsed) {
10801                                         // So this: <b>x</b><i>|x</i>
10802                                         // Becomes: <b>x|</b><i>x</i>
10803                                         // Seems that only gecko has issues with this
10804                                         if (container.nodeType === 3 && offset === 0) {
10805                                                 findTextNodeRelative(true);
10806                                         }
10807
10808                                         // Lean left into empty inline elements when the caret is before a BR
10809                                         // So this: <i><b></b><i>|<br></i>
10810                                         // Becomes: <i><b>|</b><i><br></i>
10811                                         // Seems that only gecko has issues with this.
10812                                         // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
10813                                         if (container.nodeType === 1) {
10814                                                 node = container.childNodes[offset];
10815                                                 if(node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
10816                                                         !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
10817                                                         findTextNodeRelative(true, container.childNodes[offset]);
10818                                                 }
10819                                         }
10820                                 }
10821
10822                                 // Lean the start of the selection right if possible
10823                                 // So this: x[<b>x]</b>
10824                                 // Becomes: x<b>[x]</b>
10825                                 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
10826                                         findTextNodeRelative(false);
10827                                 }
10828
10829                                 // Set endpoint if it was normalized
10830                                 if (normalized) {
10831                                         rng['set' + (start ? 'Start' : 'End')](container, offset);
10832                                 }
10833                         }
10834
10835                         // Normalize only on non IE browsers for now
10836                         if (isIE) {
10837                                 return;
10838                         }
10839
10840                         rng = self.getRng();
10841                         collapsed = rng.collapsed;
10842
10843                         // Normalize the end points
10844                         normalizeEndPoint(true);
10845
10846                         if (!collapsed) {
10847                                 normalizeEndPoint();
10848                         }
10849
10850                         // Set the selection if it was normalized
10851                         if (normalized) {
10852                                 // If it was collapsed then make sure it still is
10853                                 if (collapsed) {
10854                                         rng.collapse(true);
10855                                 }
10856
10857                                 //console.log(self.dom.dumpRng(rng));
10858                                 self.setRng(rng, self.isForward());
10859                         }
10860                 },
10861
10862                 /**
10863                  * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback.
10864                  *
10865                  * @method selectorChanged
10866                  * @param {String} selector CSS selector to check for.
10867                  * @param {function} callback Callback with state and args when the selector is matches or not.
10868                  */
10869                 selectorChanged: function(selector, callback) {
10870                         var self = this, currentSelectors;
10871
10872                         if (!self.selectorChangedData) {
10873                                 self.selectorChangedData = {};
10874                                 currentSelectors = {};
10875
10876                                 self.editor.on('NodeChange', function(e) {
10877                                         var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10878
10879                                         // Check for new matching selectors
10880                                         each(self.selectorChangedData, function(callbacks, selector) {
10881                                                 each(parents, function(node) {
10882                                                         if (dom.is(node, selector)) {
10883                                                                 if (!currentSelectors[selector]) {
10884                                                                         // Execute callbacks
10885                                                                         each(callbacks, function(callback) {
10886                                                                                 callback(true, {node: node, selector: selector, parents: parents});
10887                                                                         });
10888
10889                                                                         currentSelectors[selector] = callbacks;
10890                                                                 }
10891
10892                                                                 matchedSelectors[selector] = callbacks;
10893                                                                 return false;
10894                                                         }
10895                                                 });
10896                                         });
10897
10898                                         // Check if current selectors still match
10899                                         each(currentSelectors, function(callbacks, selector) {
10900                                                 if (!matchedSelectors[selector]) {
10901                                                         delete currentSelectors[selector];
10902
10903                                                         each(callbacks, function(callback) {
10904                                                                 callback(false, {node: node, selector: selector, parents: parents});
10905                                                         });
10906                                                 }
10907                                         });
10908                                 });
10909                         }
10910
10911                         // Add selector listeners
10912                         if (!self.selectorChangedData[selector]) {
10913                                 self.selectorChangedData[selector] = [];
10914                         }
10915
10916                         self.selectorChangedData[selector].push(callback);
10917
10918                         return self;
10919                 },
10920
10921                 scrollIntoView: function(elm) {
10922                         var y, viewPort, self = this, dom = self.dom;
10923
10924                         viewPort = dom.getViewPort(self.editor.getWin());
10925                         y = dom.getPos(elm).y;
10926                         if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
10927                                 self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25);
10928                         }
10929                 },
10930
10931                 destroy: function() {
10932                         this.win = null;
10933                         this.controlSelection.destroy();
10934                 }
10935         };
10936
10937         return Selection;
10938 });
10939
10940 // Included from: js/tinymce/classes/dom/RangeUtils.js
10941
10942 /**
10943  * Range.js
10944  *
10945  * Copyright, Moxiecode Systems AB
10946  * Released under LGPL License.
10947  *
10948  * License: http://www.tinymce.com/license
10949  * Contributing: http://www.tinymce.com/contributing
10950  */
10951
10952 /**
10953  * RangeUtils
10954  *
10955  * @class tinymce.dom.RangeUtils
10956  * @private
10957  */
10958 define("tinymce/dom/RangeUtils", [
10959         "tinymce/util/Tools"
10960 ], function(Tools) {
10961         var each = Tools.each;
10962
10963         function RangeUtils(dom) {
10964                 /**
10965                  * Walks the specified range like object and executes the callback for each sibling collection it finds.
10966                  *
10967                  * @method walk
10968                  * @param {Object} rng Range like object.
10969                  * @param {function} callback Callback function to execute for each sibling collection.
10970                  */
10971                 this.walk = function(rng, callback) {
10972                         var startContainer = rng.startContainer,
10973                                 startOffset = rng.startOffset,
10974                                 endContainer = rng.endContainer,
10975                                 endOffset = rng.endOffset,
10976                                 ancestor, startPoint,
10977                                 endPoint, node, parent, siblings, nodes;
10978
10979                         // Handle table cell selection the table plugin enables
10980                         // you to fake select table cells and perform formatting actions on them
10981                         nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
10982                         if (nodes.length > 0) {
10983                                 each(nodes, function(node) {
10984                                         callback([node]);
10985                                 });
10986
10987                                 return;
10988                         }
10989
10990                         /**
10991                          * Excludes start/end text node if they are out side the range
10992                          *
10993                          * @private
10994                          * @param {Array} nodes Nodes to exclude items from.
10995                          * @return {Array} Array with nodes excluding the start/end container if needed.
10996                          */
10997                         function exclude(nodes) {
10998                                 var node;
10999
11000                                 // First node is excluded
11001                                 node = nodes[0];
11002                                 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
11003                                         nodes.splice(0, 1);
11004                                 }
11005
11006                                 // Last node is excluded
11007                                 node = nodes[nodes.length - 1];
11008                                 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
11009                                         nodes.splice(nodes.length - 1, 1);
11010                                 }
11011
11012                                 return nodes;
11013                         }
11014
11015                         /**
11016                          * Collects siblings
11017                          *
11018                          * @private
11019                          * @param {Node} node Node to collect siblings from.
11020                          * @param {String} name Name of the sibling to check for.
11021                          * @return {Array} Array of collected siblings.
11022                          */
11023                         function collectSiblings(node, name, end_node) {
11024                                 var siblings = [];
11025
11026                                 for (; node && node != end_node; node = node[name]) {
11027                                         siblings.push(node);
11028                                 }
11029
11030                                 return siblings;
11031                         }
11032
11033                         /**
11034                          * Find an end point this is the node just before the common ancestor root.
11035                          *
11036                          * @private
11037                          * @param {Node} node Node to start at.
11038                          * @param {Node} root Root/ancestor element to stop just before.
11039                          * @return {Node} Node just before the root element.
11040                          */
11041                         function findEndPoint(node, root) {
11042                                 do {
11043                                         if (node.parentNode == root) {
11044                                                 return node;
11045                                         }
11046
11047                                         node = node.parentNode;
11048                                 } while(node);
11049                         }
11050
11051                         function walkBoundary(start_node, end_node, next) {
11052                                 var siblingName = next ? 'nextSibling' : 'previousSibling';
11053
11054                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
11055                                         parent = node.parentNode;
11056                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
11057
11058                                         if (siblings.length) {
11059                                                 if (!next) {
11060                                                         siblings.reverse();
11061                                                 }
11062
11063                                                 callback(exclude(siblings));
11064                                         }
11065                                 }
11066                         }
11067
11068                         // If index based start position then resolve it
11069                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
11070                                 startContainer = startContainer.childNodes[startOffset];
11071                         }
11072
11073                         // If index based end position then resolve it
11074                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
11075                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
11076                         }
11077
11078                         // Same container
11079                         if (startContainer == endContainer) {
11080                                 return callback(exclude([startContainer]));
11081                         }
11082
11083                         // Find common ancestor and end points
11084                         ancestor = dom.findCommonAncestor(startContainer, endContainer);
11085
11086                         // Process left side
11087                         for (node = startContainer; node; node = node.parentNode) {
11088                                 if (node === endContainer) {
11089                                         return walkBoundary(startContainer, ancestor, true);
11090                                 }
11091
11092                                 if (node === ancestor) {
11093                                         break;
11094                                 }
11095                         }
11096
11097                         // Process right side
11098                         for (node = endContainer; node; node = node.parentNode) {
11099                                 if (node === startContainer) {
11100                                         return walkBoundary(endContainer, ancestor);
11101                                 }
11102
11103                                 if (node === ancestor) {
11104                                         break;
11105                                 }
11106                         }
11107
11108                         // Find start/end point
11109                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;
11110                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;
11111
11112                         // Walk left leaf
11113                         walkBoundary(startContainer, startPoint, true);
11114
11115                         // Walk the middle from start to end point
11116                         siblings = collectSiblings(
11117                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
11118                                 'nextSibling',
11119                                 endPoint == endContainer ? endPoint.nextSibling : endPoint
11120                         );
11121
11122                         if (siblings.length) {
11123                                 callback(exclude(siblings));
11124                         }
11125
11126                         // Walk right leaf
11127                         walkBoundary(endContainer, endPoint);
11128                 };
11129
11130                 /**
11131                  * Splits the specified range at it's start/end points.
11132                  *
11133                  * @private
11134                  * @param {Range/RangeObject} rng Range to split.
11135                  * @return {Object} Range position object.
11136                  */
11137                 this.split = function(rng) {
11138                         var startContainer = rng.startContainer,
11139                                 startOffset = rng.startOffset,
11140                                 endContainer = rng.endContainer,
11141                                 endOffset = rng.endOffset;
11142
11143                         function splitText(node, offset) {
11144                                 return node.splitText(offset);
11145                         }
11146
11147                         // Handle single text node
11148                         if (startContainer == endContainer && startContainer.nodeType == 3) {
11149                                 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11150                                         endContainer = splitText(startContainer, startOffset);
11151                                         startContainer = endContainer.previousSibling;
11152
11153                                         if (endOffset > startOffset) {
11154                                                 endOffset = endOffset - startOffset;
11155                                                 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
11156                                                 endOffset = endContainer.nodeValue.length;
11157                                                 startOffset = 0;
11158                                         } else {
11159                                                 endOffset = 0;
11160                                         }
11161                                 }
11162                         } else {
11163                                 // Split startContainer text node if needed
11164                                 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11165                                         startContainer = splitText(startContainer, startOffset);
11166                                         startOffset = 0;
11167                                 }
11168
11169                                 // Split endContainer text node if needed
11170                                 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
11171                                         endContainer = splitText(endContainer, endOffset).previousSibling;
11172                                         endOffset = endContainer.nodeValue.length;
11173                                 }
11174                         }
11175
11176                         return {
11177                                 startContainer: startContainer,
11178                                 startOffset: startOffset,
11179                                 endContainer: endContainer,
11180                                 endOffset: endOffset
11181                         };
11182                 };
11183         }
11184
11185         /**
11186          * Compares two ranges and checks if they are equal.
11187          *
11188          * @static
11189          * @method compareRanges
11190          * @param {DOMRange} rng1 First range to compare.
11191          * @param {DOMRange} rng2 First range to compare.
11192          * @return {Boolean} true/false if the ranges are equal.
11193          */
11194         RangeUtils.compareRanges = function(rng1, rng2) {
11195                 if (rng1 && rng2) {
11196                         // Compare native IE ranges
11197                         if (rng1.item || rng1.duplicate) {
11198                                 // Both are control ranges and the selected element matches
11199                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
11200                                         return true;
11201                                 }
11202
11203                                 // Both are text ranges and the range matches
11204                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
11205                                         return true;
11206                                 }
11207                         } else {
11208                                 // Compare w3c ranges
11209                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
11210                         }
11211                 }
11212
11213                 return false;
11214         };
11215
11216         return RangeUtils;
11217 });
11218
11219 // Included from: js/tinymce/classes/Formatter.js
11220
11221 /**
11222  * Formatter.js
11223  *
11224  * Copyright, Moxiecode Systems AB
11225  * Released under LGPL License.
11226  *
11227  * License: http://www.tinymce.com/license
11228  * Contributing: http://www.tinymce.com/contributing
11229  */
11230
11231 /**
11232  * Text formatter engine class. This class is used to apply formats like bold, italic, font size
11233  * etc to the current selection or specific nodes. This engine was build to replace the browsers
11234  * default formatting logic for execCommand due to it's inconsistent and buggy behavior.
11235  *
11236  * @class tinymce.Formatter
11237  * @example
11238  *  tinymce.activeEditor.formatter.register('mycustomformat', {
11239  *    inline: 'span',
11240  *    styles: {color: '#ff0000'}
11241  *  });
11242  *
11243  *  tinymce.activeEditor.formatter.apply('mycustomformat');
11244  */
11245 define("tinymce/Formatter", [
11246         "tinymce/dom/TreeWalker",
11247         "tinymce/dom/RangeUtils",
11248         "tinymce/util/Tools"
11249 ], function(TreeWalker, RangeUtils, Tools) {
11250         /**
11251          * Constructs a new formatter instance.
11252          *
11253          * @constructor Formatter
11254          * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
11255          */
11256         return function(ed) {
11257                 var formats = {},
11258                         dom = ed.dom,
11259                         selection = ed.selection,
11260                         rangeUtils = new RangeUtils(dom),
11261                         isValid = ed.schema.isValidChild,
11262                         isBlock = dom.isBlock,
11263                         forcedRootBlock = ed.settings.forced_root_block,
11264                         nodeIndex = dom.nodeIndex,
11265                         INVISIBLE_CHAR = '\uFEFF',
11266                         MCE_ATTR_RE = /^(src|href|style)$/,
11267                         FALSE = false,
11268                         TRUE = true,
11269                         formatChangeData,
11270                         undef,
11271                         getContentEditable = dom.getContentEditable,
11272                         disableCaretContainer,
11273                         markCaretContainersBogus;
11274
11275                 var each = Tools.each,
11276                         grep = Tools.grep,
11277                         walk = Tools.walk,
11278                         extend = Tools.extend;
11279
11280                 function isTextBlock(name) {
11281                         if (name.nodeType) {
11282                                 name = name.nodeName;
11283                         }
11284
11285                         return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
11286                 }
11287
11288                 function getParents(node, selector) {
11289                         return dom.getParents(node, selector, dom.getRoot());
11290                 }
11291
11292                 function isCaretNode(node) {
11293                         return node.nodeType === 1 && node.id === '_mce_caret';
11294                 }
11295
11296                 function defaultFormats() {
11297                         register({
11298                                 alignleft: [
11299                                         {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
11300                                         {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
11301                                 ],
11302
11303                                 aligncenter: [
11304                                         {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
11305                                         {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
11306                                         {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
11307                                 ],
11308
11309                                 alignright: [
11310                                         {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
11311                                         {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
11312                                 ],
11313
11314                                 alignjustify: [
11315                                         {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
11316                                 ],
11317
11318                                 bold: [
11319                                         {inline: 'strong', remove: 'all'},
11320                                         {inline: 'span', styles: {fontWeight: 'bold'}},
11321                                         {inline: 'b', remove: 'all'}
11322                                 ],
11323
11324                                 italic: [
11325                                         {inline: 'em', remove: 'all'},
11326                                         {inline: 'span', styles: {fontStyle: 'italic'}},
11327                                         {inline: 'i', remove: 'all'}
11328                                 ],
11329
11330                                 underline: [
11331                                         {inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
11332                                         {inline: 'u', remove: 'all'}
11333                                 ],
11334
11335                                 strikethrough: [
11336                                         {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
11337                                         {inline: 'strike', remove: 'all'}
11338                                 ],
11339
11340                                 forecolor: {inline: 'span', styles: {color: '%value'}, wrap_links: false},
11341                                 hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, wrap_links: false},
11342                                 fontname: {inline: 'span', styles: {fontFamily: '%value'}},
11343                                 fontsize: {inline: 'span', styles: {fontSize: '%value'}},
11344                                 fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
11345                                 blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
11346                                 subscript: {inline: 'sub'},
11347                                 superscript: {inline: 'sup'},
11348                                 code: {inline: 'code'},
11349
11350                                 link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
11351                                         onmatch: function() {
11352                                                 return true;
11353                                         },
11354
11355                                         onformat: function(elm, fmt, vars) {
11356                                                 each(vars, function(value, key) {
11357                                                         dom.setAttrib(elm, key, value);
11358                                                 });
11359                                         }
11360                                 },
11361
11362                                 removeformat: [
11363                                         {
11364                                                 selector: 'b,strong,em,i,font,u,strike,sub,sup',
11365                                                 remove: 'all',
11366                                                 split: true,
11367                                                 expand: false,
11368                                                 block_expand: true,
11369                                                 deep: true
11370                                         },
11371                                         {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
11372                                         {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
11373                                 ]
11374                         });
11375
11376                         // Register default block formats
11377                         each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
11378                                 register(name, {block: name, remove: 'all'});
11379                         });
11380
11381                         // Register user defined formats
11382                         register(ed.settings.formats);
11383                 }
11384
11385                 function addKeyboardShortcuts() {
11386                         // Add some inline shortcuts
11387                         ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
11388                         ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
11389                         ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
11390
11391                         // BlockFormat shortcuts keys
11392                         for (var i = 1; i <= 6; i++) {
11393                                 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
11394                         }
11395
11396                         ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
11397                         ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
11398                         ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
11399                 }
11400
11401                 // Public functions
11402
11403                 /**
11404                  * Returns the format by name or all formats if no name is specified.
11405                  *
11406                  * @method get
11407                  * @param {String} name Optional name to retrive by.
11408                  * @return {Array/Object} Array/Object with all registred formats or a specific format.
11409                  */
11410                 function get(name) {
11411                         return name ? formats[name] : formats;
11412                 }
11413
11414                 /**
11415                  * Registers a specific format by name.
11416                  *
11417                  * @method register
11418                  * @param {Object/String} name Name of the format for example "bold".
11419                  * @param {Object/Array} format Optional format object or array of format variants
11420                  * can only be omitted if the first arg is an object.
11421                  */
11422                 function register(name, format) {
11423                         if (name) {
11424                                 if (typeof(name) !== 'string') {
11425                                         each(name, function(format, name) {
11426                                                 register(name, format);
11427                                         });
11428                                 } else {
11429                                         // Force format into array and add it to internal collection
11430                                         format = format.length ? format : [format];
11431
11432                                         each(format, function(format) {
11433                                                 // Set deep to false by default on selector formats this to avoid removing
11434                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs
11435                                                 if (format.deep === undef) {
11436                                                         format.deep = !format.selector;
11437                                                 }
11438
11439                                                 // Default to true
11440                                                 if (format.split === undef) {
11441                                                         format.split = !format.selector || format.inline;
11442                                                 }
11443
11444                                                 // Default to true
11445                                                 if (format.remove === undef && format.selector && !format.inline) {
11446                                                         format.remove = 'none';
11447                                                 }
11448
11449                                                 // Mark format as a mixed format inline + block level
11450                                                 if (format.selector && format.inline) {
11451                                                         format.mixed = true;
11452                                                         format.block_expand = true;
11453                                                 }
11454
11455                                                 // Split classes if needed
11456                                                 if (typeof(format.classes) === 'string') {
11457                                                         format.classes = format.classes.split(/\s+/);
11458                                                 }
11459                                         });
11460
11461                                         formats[name] = format;
11462                                 }
11463                         }
11464                 }
11465
11466                 function getTextDecoration(node) {
11467                         var decoration;
11468
11469                         ed.dom.getParent(node, function(n) {
11470                                 decoration = ed.dom.getStyle(n, 'text-decoration');
11471                                 return decoration && decoration !== 'none';
11472                         });
11473
11474                         return decoration;
11475                 }
11476
11477                 function processUnderlineAndColor(node) {
11478                         var textDecoration;
11479                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
11480                                 textDecoration = getTextDecoration(node.parentNode);
11481                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
11482                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);
11483                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
11484                                         ed.dom.setStyle(node, 'text-decoration', null);
11485                                 }
11486                         }
11487                 }
11488
11489                 /**
11490                  * Applies the specified format to the current selection or specified node.
11491                  *
11492                  * @method apply
11493                  * @param {String} name Name of format to apply.
11494                  * @param {Object} vars Optional list of variables to replace within format before applying it.
11495                  * @param {Node} node Optional node to apply the format to defaults to current selection.
11496                  */
11497                 function apply(name, vars, node) {
11498                         var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
11499
11500                         function setElementFormat(elm, fmt) {
11501                                 fmt = fmt || format;
11502
11503                                 if (elm) {
11504                                         if (fmt.onformat) {
11505                                                 fmt.onformat(elm, fmt, vars, node);
11506                                         }
11507
11508                                         each(fmt.styles, function(value, name) {
11509                                                 dom.setStyle(elm, name, replaceVars(value, vars));
11510                                         });
11511
11512                                         each(fmt.attributes, function(value, name) {
11513                                                 dom.setAttrib(elm, name, replaceVars(value, vars));
11514                                         });
11515
11516                                         each(fmt.classes, function(value) {
11517                                                 value = replaceVars(value, vars);
11518
11519                                                 if (!dom.hasClass(elm, value)) {
11520                                                         dom.addClass(elm, value);
11521                                                 }
11522                                         });
11523                                 }
11524                         }
11525
11526                         function adjustSelectionToVisibleSelection() {
11527                                 function findSelectionEnd(start, end) {
11528                                         var walker = new TreeWalker(end);
11529                                         for (node = walker.current(); node; node = walker.prev()) {
11530                                                 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
11531                                                         return node;
11532                                                 }
11533                                         }
11534                                 }
11535
11536                                 // Adjust selection so that a end container with a end offset of zero is not included in the selection
11537                                 // as this isn't visible to the user.
11538                                 var rng = ed.selection.getRng();
11539                                 var start = rng.startContainer;
11540                                 var end = rng.endContainer;
11541
11542                                 if (start != end && rng.endOffset === 0) {
11543                                         var newEnd = findSelectionEnd(start, end);
11544                                         var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
11545
11546                                         rng.setEnd(newEnd, endOffset);
11547                                 }
11548
11549                                 return rng;
11550                         }
11551
11552                         function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
11553                                 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
11554
11555                                 // find the index of the first child list.
11556                                 each(node.childNodes, function(n, index) {
11557                                         if (n.nodeName === "UL" || n.nodeName === "OL") {
11558                                                 listIndex = index;
11559                                                 list = n;
11560                                                 return false;
11561                                         }
11562                                 });
11563
11564                                 // get the index of the bookmarks
11565                                 each(node.childNodes, function(n, index) {
11566                                         if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
11567                                                 if (n.id == bookmark.id + "_start") {
11568                                                         startIndex = index;
11569                                                 } else if (n.id == bookmark.id + "_end") {
11570                                                         endIndex = index;
11571                                                 }
11572                                         }
11573                                 });
11574
11575                                 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
11576                                 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
11577                                         each(grep(node.childNodes), process);
11578                                         return 0;
11579                                 } else {
11580                                         currentWrapElm = dom.clone(wrapElm, FALSE);
11581
11582                                         // create a list of the nodes on the same side of the list as the selection
11583                                         each(grep(node.childNodes), function(n, index) {
11584                                                 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
11585                                                         nodes.push(n);
11586                                                         n.parentNode.removeChild(n);
11587                                                 }
11588                                         });
11589
11590                                         // insert the wrapping element either before or after the list.
11591                                         if (startIndex < listIndex) {
11592                                                 node.insertBefore(currentWrapElm, list);
11593                                         } else if (startIndex > listIndex) {
11594                                                 node.insertBefore(currentWrapElm, list.nextSibling);
11595                                         }
11596
11597                                         // add the new nodes to the list.
11598                                         newWrappers.push(currentWrapElm);
11599
11600                                         each(nodes, function(node) {
11601                                                 currentWrapElm.appendChild(node);
11602                                         });
11603
11604                                         return currentWrapElm;
11605                                 }
11606                         }
11607
11608                         function applyRngStyle(rng, bookmark, node_specific) {
11609                                 var newWrappers = [], wrapName, wrapElm, contentEditable = true;
11610
11611                                 // Setup wrapper element
11612                                 wrapName = format.inline || format.block;
11613                                 wrapElm = dom.create(wrapName);
11614                                 setElementFormat(wrapElm);
11615
11616                                 rangeUtils.walk(rng, function(nodes) {
11617                                         var currentWrapElm;
11618
11619                                         /**
11620                                          * Process a list of nodes wrap them.
11621                                          */
11622                                         function process(node) {
11623                                                 var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
11624
11625                                                 lastContentEditable = contentEditable;
11626                                                 nodeName = node.nodeName.toLowerCase();
11627                                                 parentName = node.parentNode.nodeName.toLowerCase();
11628
11629                                                 // Node has a contentEditable value
11630                                                 if (node.nodeType === 1 && getContentEditable(node)) {
11631                                                         lastContentEditable = contentEditable;
11632                                                         contentEditable = getContentEditable(node) === "true";
11633                                                         hasContentEditableState = true; // We don't want to wrap the container only it's children
11634                                                 }
11635
11636                                                 // Stop wrapping on br elements
11637                                                 if (isEq(nodeName, 'br')) {
11638                                                         currentWrapElm = 0;
11639
11640                                                         // Remove any br elements when we wrap things
11641                                                         if (format.block) {
11642                                                                 dom.remove(node);
11643                                                         }
11644
11645                                                         return;
11646                                                 }
11647
11648                                                 // If node is wrapper type
11649                                                 if (format.wrapper && matchNode(node, name, vars)) {
11650                                                         currentWrapElm = 0;
11651                                                         return;
11652                                                 }
11653
11654                                                 // Can we rename the block
11655                                                 // TODO: Break this if up, too complex
11656                                                 if (contentEditable && !hasContentEditableState && format.block &&
11657                                                         !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
11658                                                         node = dom.rename(node, wrapName);
11659                                                         setElementFormat(node);
11660                                                         newWrappers.push(node);
11661                                                         currentWrapElm = 0;
11662                                                         return;
11663                                                 }
11664
11665                                                 // Handle selector patterns
11666                                                 if (format.selector) {
11667                                                         // Look for matching formats
11668                                                         each(formatList, function(format) {
11669                                                                 // Check collapsed state if it exists
11670                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
11671                                                                         return;
11672                                                                 }
11673
11674                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
11675                                                                         setElementFormat(node, format);
11676                                                                         found = true;
11677                                                                 }
11678                                                         });
11679
11680                                                         // Continue processing if a selector match wasn't found and a inline element is defined
11681                                                         if (!format.inline || found) {
11682                                                                 currentWrapElm = 0;
11683                                                                 return;
11684                                                         }
11685                                                 }
11686
11687                                                 // Is it valid to wrap this item
11688                                                 // TODO: Break this if up, too complex
11689                                                 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
11690                                                                 !(!node_specific && node.nodeType === 3 &&
11691                                                                 node.nodeValue.length === 1 &&
11692                                                                 node.nodeValue.charCodeAt(0) === 65279) &&
11693                                                                 !isCaretNode(node) &&
11694                                                                 (!format.inline || !isBlock(node))) {
11695                                                         // Start wrapping
11696                                                         if (!currentWrapElm) {
11697                                                                 // Wrap the node
11698                                                                 currentWrapElm = dom.clone(wrapElm, FALSE);
11699                                                                 node.parentNode.insertBefore(currentWrapElm, node);
11700                                                                 newWrappers.push(currentWrapElm);
11701                                                         }
11702
11703                                                         currentWrapElm.appendChild(node);
11704                                                 } else if (nodeName == 'li' && bookmark) {
11705                                                         // Start wrapping - if we are in a list node and have a bookmark, then
11706                                                         // we will always begin by wrapping in a new element.
11707                                                         currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
11708                                                 } else {
11709                                                         // Start a new wrapper for possible children
11710                                                         currentWrapElm = 0;
11711
11712                                                         each(grep(node.childNodes), process);
11713
11714                                                         if (hasContentEditableState) {
11715                                                                 contentEditable = lastContentEditable; // Restore last contentEditable state from stack
11716                                                         }
11717
11718                                                         // End the last wrapper
11719                                                         currentWrapElm = 0;
11720                                                 }
11721                                         }
11722
11723                                         // Process siblings from range
11724                                         each(nodes, process);
11725                                 });
11726
11727                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
11728                                 if (format.wrap_links === false) {
11729                                         each(newWrappers, function(node) {
11730                                                 function process(node) {
11731                                                         var i, currentWrapElm, children;
11732
11733                                                         if (node.nodeName === 'A') {
11734                                                                 currentWrapElm = dom.clone(wrapElm, FALSE);
11735                                                                 newWrappers.push(currentWrapElm);
11736
11737                                                                 children = grep(node.childNodes);
11738                                                                 for (i = 0; i < children.length; i++) {
11739                                                                         currentWrapElm.appendChild(children[i]);
11740                                                                 }
11741
11742                                                                 node.appendChild(currentWrapElm);
11743                                                         }
11744
11745                                                         each(grep(node.childNodes), process);
11746                                                 }
11747
11748                                                 process(node);
11749                                         });
11750                                 }
11751
11752                                 // Cleanup
11753                                 each(newWrappers, function(node) {
11754                                         var childCount;
11755
11756                                         function getChildCount(node) {
11757                                                 var count = 0;
11758
11759                                                 each(node.childNodes, function(node) {
11760                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
11761                                                                 count++;
11762                                                         }
11763                                                 });
11764
11765                                                 return count;
11766                                         }
11767
11768                                         function mergeStyles(node) {
11769                                                 var child, clone;
11770
11771                                                 each(node.childNodes, function(node) {
11772                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
11773                                                                 child = node;
11774                                                                 return FALSE; // break loop
11775                                                         }
11776                                                 });
11777
11778                                                 // If child was found and of the same type as the current node
11779                                                 if (child && matchName(child, format)) {
11780                                                         clone = dom.clone(child, FALSE);
11781                                                         setElementFormat(clone);
11782
11783                                                         dom.replace(clone, node, TRUE);
11784                                                         dom.remove(child, 1);
11785                                                 }
11786
11787                                                 return clone || node;
11788                                         }
11789
11790                                         childCount = getChildCount(node);
11791
11792                                         // Remove empty nodes but only if there is multiple wrappers and they are not block
11793                                         // elements so never remove single <h1></h1> since that would remove the
11794                                         // currrent empty block element where the caret is at
11795                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
11796                                                 dom.remove(node, 1);
11797                                                 return;
11798                                         }
11799
11800                                         if (format.inline || format.wrapper) {
11801                                                 // Merges the current node with it's children of similar type to reduce the number of elements
11802                                                 if (!format.exact && childCount === 1) {
11803                                                         node = mergeStyles(node);
11804                                                 }
11805
11806                                                 // Remove/merge children
11807                                                 each(formatList, function(format) {
11808                                                         // Merge all children of similar type will move styles from child to parent
11809                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
11810                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
11811                                                         each(dom.select(format.inline, node), function(child) {
11812                                                                 var parent;
11813
11814                                                                 // When wrap_links is set to false we don't want
11815                                                                 // to remove the format on children within links
11816                                                                 if (format.wrap_links === false) {
11817                                                                         parent = child.parentNode;
11818
11819                                                                         do {
11820                                                                                 if (parent.nodeName === 'A') {
11821                                                                                         return;
11822                                                                                 }
11823                                                                         } while ((parent = parent.parentNode));
11824                                                                 }
11825
11826                                                                 removeFormat(format, vars, child, format.exact ? child : null);
11827                                                         });
11828                                                 });
11829
11830                                                 // Remove child if direct parent is of same type
11831                                                 if (matchNode(node.parentNode, name, vars)) {
11832                                                         dom.remove(node, 1);
11833                                                         node = 0;
11834                                                         return TRUE;
11835                                                 }
11836
11837                                                 // Look for parent with similar style format
11838                                                 if (format.merge_with_parents) {
11839                                                         dom.getParent(node.parentNode, function(parent) {
11840                                                                 if (matchNode(parent, name, vars)) {
11841                                                                         dom.remove(node, 1);
11842                                                                         node = 0;
11843                                                                         return TRUE;
11844                                                                 }
11845                                                         });
11846                                                 }
11847
11848                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
11849                                                 if (node && format.merge_siblings !== false) {
11850                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
11851                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
11852                                                 }
11853                                         }
11854                                 });
11855                         }
11856
11857                         if (format) {
11858                                 if (node) {
11859                                         if (node.nodeType) {
11860                                                 rng = dom.createRng();
11861                                                 rng.setStartBefore(node);
11862                                                 rng.setEndAfter(node);
11863                                                 applyRngStyle(expandRng(rng, formatList), null, true);
11864                                         } else {
11865                                                 applyRngStyle(node, null, true);
11866                                         }
11867                                 } else {
11868                                         if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
11869                                                 // Obtain selection node before selection is unselected by applyRngStyle()
11870                                                 var curSelNode = ed.selection.getNode();
11871
11872                                                 // If the formats have a default block and we can't find a parent block then
11873                                                 // start wrapping it with a DIV this is for forced_root_blocks: false
11874                                                 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
11875                                                 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
11876                                                         apply(formatList[0].defaultBlock);
11877                                                 }
11878
11879                                                 // Apply formatting to selection
11880                                                 ed.selection.setRng(adjustSelectionToVisibleSelection());
11881                                                 bookmark = selection.getBookmark();
11882                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
11883
11884                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.
11885                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
11886                                                         walk(curSelNode, processUnderlineAndColor, 'childNodes');
11887                                                         processUnderlineAndColor(curSelNode);
11888                                                 }
11889
11890                                                 selection.moveToBookmark(bookmark);
11891                                                 moveStart(selection.getRng(TRUE));
11892                                                 ed.nodeChanged();
11893                                         } else {
11894                                                 performCaretAction('apply', name, vars);
11895                                         }
11896                                 }
11897                         }
11898                 }
11899
11900                 /**
11901                  * Removes the specified format from the current selection or specified node.
11902                  *
11903                  * @method remove
11904                  * @param {String} name Name of format to remove.
11905                  * @param {Object} vars Optional list of variables to replace within format before removing it.
11906                  * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
11907                  */
11908                 function remove(name, vars, node) {
11909                         var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
11910
11911                         // Merges the styles for each node
11912                         function process(node) {
11913                                 var children, i, l, lastContentEditable, hasContentEditableState;
11914
11915                                 // Node has a contentEditable value
11916                                 if (node.nodeType === 1 && getContentEditable(node)) {
11917                                         lastContentEditable = contentEditable;
11918                                         contentEditable = getContentEditable(node) === "true";
11919                                         hasContentEditableState = true; // We don't want to wrap the container only it's children
11920                                 }
11921
11922                                 // Grab the children first since the nodelist might be changed
11923                                 children = grep(node.childNodes);
11924
11925                                 // Process current node
11926                                 if (contentEditable && !hasContentEditableState) {
11927                                         for (i = 0, l = formatList.length; i < l; i++) {
11928                                                 if (removeFormat(formatList[i], vars, node, node)) {
11929                                                         break;
11930                                                 }
11931                                         }
11932                                 }
11933
11934                                 // Process the children
11935                                 if (format.deep) {
11936                                         if (children.length) {
11937                                                 for (i = 0, l = children.length; i < l; i++) {
11938                                                         process(children[i]);
11939                                                 }
11940
11941                                                 if (hasContentEditableState) {
11942                                                         contentEditable = lastContentEditable; // Restore last contentEditable state from stack
11943                                                 }
11944                                         }
11945                                 }
11946                         }
11947
11948                         function findFormatRoot(container) {
11949                                 var formatRoot;
11950
11951                                 // Find format root
11952                                 each(getParents(container.parentNode).reverse(), function(parent) {
11953                                         var format;
11954
11955                                         // Find format root element
11956                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
11957                                                 // Is the node matching the format we are looking for
11958                                                 format = matchNode(parent, name, vars);
11959                                                 if (format && format.split !== false) {
11960                                                         formatRoot = parent;
11961                                                 }
11962                                         }
11963                                 });
11964
11965                                 return formatRoot;
11966                         }
11967
11968                         function wrapAndSplit(format_root, container, target, split) {
11969                                 var parent, clone, lastClone, firstClone, i, formatRootParent;
11970
11971                                 // Format root found then clone formats and split it
11972                                 if (format_root) {
11973                                         formatRootParent = format_root.parentNode;
11974
11975                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
11976                                                 clone = dom.clone(parent, FALSE);
11977
11978                                                 for (i = 0; i < formatList.length; i++) {
11979                                                         if (removeFormat(formatList[i], vars, clone, clone)) {
11980                                                                 clone = 0;
11981                                                                 break;
11982                                                         }
11983                                                 }
11984
11985                                                 // Build wrapper node
11986                                                 if (clone) {
11987                                                         if (lastClone) {
11988                                                                 clone.appendChild(lastClone);
11989                                                         }
11990
11991                                                         if (!firstClone) {
11992                                                                 firstClone = clone;
11993                                                         }
11994
11995                                                         lastClone = clone;
11996                                                 }
11997                                         }
11998
11999                                         // Never split block elements if the format is mixed
12000                                         if (split && (!format.mixed || !isBlock(format_root))) {
12001                                                 container = dom.split(format_root, container);
12002                                         }
12003
12004                                         // Wrap container in cloned formats
12005                                         if (lastClone) {
12006                                                 target.parentNode.insertBefore(lastClone, target);
12007                                                 firstClone.appendChild(target);
12008                                         }
12009                                 }
12010
12011                                 return container;
12012                         }
12013
12014                         function splitToFormatRoot(container) {
12015                                 return wrapAndSplit(findFormatRoot(container), container, container, true);
12016                         }
12017
12018                         function unwrap(start) {
12019                                 var node = dom.get(start ? '_start' : '_end'),
12020                                         out = node[start ? 'firstChild' : 'lastChild'];
12021
12022                                 // If the end is placed within the start the result will be removed
12023                                 // So this checks if the out node is a bookmark node if it is it
12024                                 // checks for another more suitable node
12025                                 if (isBookmarkNode(out)) {
12026                                         out = out[start ? 'firstChild' : 'lastChild'];
12027                                 }
12028
12029                                 dom.remove(node, true);
12030
12031                                 return out;
12032                         }
12033
12034                         function removeRngStyle(rng) {
12035                                 var startContainer, endContainer;
12036
12037                                 rng = expandRng(rng, formatList, TRUE);
12038
12039                                 if (format.split) {
12040                                         startContainer = getContainer(rng, TRUE);
12041                                         endContainer = getContainer(rng);
12042
12043                                         if (startContainer != endContainer) {
12044                                                 // WebKit will render the table incorrectly if we wrap a TD in a SPAN
12045                                                 // so lets see if the can use the first child instead
12046                                                 // This will happen if you tripple click a table cell and use remove formatting
12047                                                 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
12048                                                         if (startContainer.nodeName == "TD") {
12049                                                                 startContainer = startContainer.firstChild || startContainer;
12050                                                         } else {
12051                                                                 startContainer = startContainer.firstChild.firstChild || startContainer;
12052                                                         }
12053                                                 }
12054
12055                                                 // Wrap start/end nodes in span element since these might be cloned/moved
12056                                                 startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
12057                                                 endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
12058
12059                                                 // Split start/end
12060                                                 splitToFormatRoot(startContainer);
12061                                                 splitToFormatRoot(endContainer);
12062
12063                                                 // Unwrap start/end to get real elements again
12064                                                 startContainer = unwrap(TRUE);
12065                                                 endContainer = unwrap();
12066                                         } else {
12067                                                 startContainer = endContainer = splitToFormatRoot(startContainer);
12068                                         }
12069
12070                                         // Update range positions since they might have changed after the split operations
12071                                         rng.startContainer = startContainer.parentNode;
12072                                         rng.startOffset = nodeIndex(startContainer);
12073                                         rng.endContainer = endContainer.parentNode;
12074                                         rng.endOffset = nodeIndex(endContainer) + 1;
12075                                 }
12076
12077                                 // Remove items between start/end
12078                                 rangeUtils.walk(rng, function(nodes) {
12079                                         each(nodes, function(node) {
12080                                                 process(node);
12081
12082                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
12083                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
12084                                                         node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
12085                                                         removeFormat({
12086                                                                 'deep': false,
12087                                                                 'exact': true,
12088                                                                 'inline': 'span',
12089                                                                 'styles': {
12090                                                                         'textDecoration': 'underline'
12091                                                                 }
12092                                                         }, null, node);
12093                                                 }
12094                                         });
12095                                 });
12096                         }
12097
12098                         // Handle node
12099                         if (node) {
12100                                 if (node.nodeType) {
12101                                         rng = dom.createRng();
12102                                         rng.setStartBefore(node);
12103                                         rng.setEndAfter(node);
12104                                         removeRngStyle(rng);
12105                                 } else {
12106                                         removeRngStyle(node);
12107                                 }
12108
12109                                 return;
12110                         }
12111
12112                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
12113                                 bookmark = selection.getBookmark();
12114                                 removeRngStyle(selection.getRng(TRUE));
12115                                 selection.moveToBookmark(bookmark);
12116
12117                                 // Check if start element still has formatting then we are at: "<b>text|</b>text"
12118                                 // and need to move the start into the next text node
12119                                 if (format.inline && match(name, vars, selection.getStart())) {
12120                                         moveStart(selection.getRng(true));
12121                                 }
12122
12123                                 ed.nodeChanged();
12124                         } else {
12125                                 performCaretAction('remove', name, vars);
12126                         }
12127                 }
12128
12129                 /**
12130                  * Toggles the specified format on/off.
12131                  *
12132                  * @method toggle
12133                  * @param {String} name Name of format to apply/remove.
12134                  * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
12135                  * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
12136                  */
12137                 function toggle(name, vars, node) {
12138                         var fmt = get(name);
12139
12140                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
12141                                 remove(name, vars, node);
12142                         } else {
12143                                 apply(name, vars, node);
12144                         }
12145                 }
12146
12147                 /**
12148                  * Return true/false if the specified node has the specified format.
12149                  *
12150                  * @method matchNode
12151                  * @param {Node} node Node to check the format on.
12152                  * @param {String} name Format name to check.
12153                  * @param {Object} vars Optional list of variables to replace before checking it.
12154                  * @param {Boolean} similar Match format that has similar properties.
12155                  * @return {Object} Returns the format object it matches or undefined if it doesn't match.
12156                  */
12157                 function matchNode(node, name, vars, similar) {
12158                         var formatList = get(name), format, i, classes;
12159
12160                         function matchItems(node, format, item_name) {
12161                                 var key, value, items = format[item_name], i;
12162
12163                                 // Custom match
12164                                 if (format.onmatch) {
12165                                         return format.onmatch(node, format, item_name);
12166                                 }
12167
12168                                 // Check all items
12169                                 if (items) {
12170                                         // Non indexed object
12171                                         if (items.length === undef) {
12172                                                 for (key in items) {
12173                                                         if (items.hasOwnProperty(key)) {
12174                                                                 if (item_name === 'attributes') {
12175                                                                         value = dom.getAttrib(node, key);
12176                                                                 } else {
12177                                                                         value = getStyle(node, key);
12178                                                                 }
12179
12180                                                                 if (similar && !value && !format.exact) {
12181                                                                         return;
12182                                                                 }
12183
12184                                                                 if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
12185                                                                         return;
12186                                                                 }
12187                                                         }
12188                                                 }
12189                                         } else {
12190                                                 // Only one match needed for indexed arrays
12191                                                 for (i = 0; i < items.length; i++) {
12192                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
12193                                                                 return format;
12194                                                         }
12195                                                 }
12196                                         }
12197                                 }
12198
12199                                 return format;
12200                         }
12201
12202                         if (formatList && node) {
12203                                 // Check each format in list
12204                                 for (i = 0; i < formatList.length; i++) {
12205                                         format = formatList[i];
12206
12207                                         // Name name, attributes, styles and classes
12208                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
12209                                                 // Match classes
12210                                                 if ((classes = format.classes)) {
12211                                                         for (i = 0; i < classes.length; i++) {
12212                                                                 if (!dom.hasClass(node, classes[i])) {
12213                                                                         return;
12214                                                                 }
12215                                                         }
12216                                                 }
12217
12218                                                 return format;
12219                                         }
12220                                 }
12221                         }
12222                 }
12223
12224                 /**
12225                  * Matches the current selection or specified node against the specified format name.
12226                  *
12227                  * @method match
12228                  * @param {String} name Name of format to match.
12229                  * @param {Object} vars Optional list of variables to replace before checking it.
12230                  * @param {Node} node Optional node to check.
12231                  * @return {boolean} true/false if the specified selection/node matches the format.
12232                  */
12233                 function match(name, vars, node) {
12234                         var startNode;
12235
12236                         function matchParents(node) {
12237                                 var root = dom.getRoot();
12238
12239                                 // Find first node with similar format settings
12240                                 node = dom.getParent(node, function(node) {
12241                                         return node.parentNode === root || !!matchNode(node, name, vars, true);
12242                                 });
12243
12244                                 // Do an exact check on the similar format element
12245                                 return matchNode(node, name, vars);
12246                         }
12247
12248                         // Check specified node
12249                         if (node) {
12250                                 return matchParents(node);
12251                         }
12252
12253                         // Check selected node
12254                         node = selection.getNode();
12255                         if (matchParents(node)) {
12256                                 return TRUE;
12257                         }
12258
12259                         // Check start node if it's different
12260                         startNode = selection.getStart();
12261                         if (startNode != node) {
12262                                 if (matchParents(startNode)) {
12263                                         return TRUE;
12264                                 }
12265                         }
12266
12267                         return FALSE;
12268                 }
12269
12270                 /**
12271                  * Matches the current selection against the array of formats and returns a new array with matching formats.
12272                  *
12273                  * @method matchAll
12274                  * @param {Array} names Name of format to match.
12275                  * @param {Object} vars Optional list of variables to replace before checking it.
12276                  * @return {Array} Array with matched formats.
12277                  */
12278                 function matchAll(names, vars) {
12279                         var startElement, matchedFormatNames = [], checkedMap = {};
12280
12281                         // Check start of selection for formats
12282                         startElement = selection.getStart();
12283                         dom.getParent(startElement, function(node) {
12284                                 var i, name;
12285
12286                                 for (i = 0; i < names.length; i++) {
12287                                         name = names[i];
12288
12289                                         if (!checkedMap[name] && matchNode(node, name, vars)) {
12290                                                 checkedMap[name] = true;
12291                                                 matchedFormatNames.push(name);
12292                                         }
12293                                 }
12294                         }, dom.getRoot());
12295
12296                         return matchedFormatNames;
12297                 }
12298
12299                 /**
12300                  * Returns true/false if the specified format can be applied to the current selection or not. It
12301                  * will currently only check the state for selector formats, it returns true on all other format types.
12302                  *
12303                  * @method canApply
12304                  * @param {String} name Name of format to check.
12305                  * @return {boolean} true/false if the specified format can be applied to the current selection/node.
12306                  */
12307                 function canApply(name) {
12308                         var formatList = get(name), startNode, parents, i, x, selector;
12309
12310                         if (formatList) {
12311                                 startNode = selection.getStart();
12312                                 parents = getParents(startNode);
12313
12314                                 for (x = formatList.length - 1; x >= 0; x--) {
12315                                         selector = formatList[x].selector;
12316
12317                                         // Format is not selector based then always return TRUE
12318                                         // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
12319                                         if (!selector || formatList[x].defaultBlock) {
12320                                                 return TRUE;
12321                                         }
12322
12323                                         for (i = parents.length - 1; i >= 0; i--) {
12324                                                 if (dom.is(parents[i], selector)) {
12325                                                         return TRUE;
12326                                                 }
12327                                         }
12328                                 }
12329                         }
12330
12331                         return FALSE;
12332                 }
12333
12334                 /**
12335                  * Executes the specified callback when the current selection matches the formats or not.
12336                  *
12337                  * @method formatChanged
12338                  * @param {String} formats Comma separated list of formats to check for.
12339                  * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
12340                  * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
12341                  */
12342                 function formatChanged(formats, callback, similar) {
12343                         var currentFormats;
12344
12345                         // Setup format node change logic
12346                         if (!formatChangeData) {
12347                                 formatChangeData = {};
12348                                 currentFormats = {};
12349
12350                                 ed.on('NodeChange', function(e) {
12351                                         var parents = getParents(e.element), matchedFormats = {};
12352
12353                                         // Check for new formats
12354                                         each(formatChangeData, function(callbacks, format) {
12355                                                 each(parents, function(node) {
12356                                                         if (matchNode(node, format, {}, callbacks.similar)) {
12357                                                                 if (!currentFormats[format]) {
12358                                                                         // Execute callbacks
12359                                                                         each(callbacks, function(callback) {
12360                                                                                 callback(true, {node: node, format: format, parents: parents});
12361                                                                         });
12362
12363                                                                         currentFormats[format] = callbacks;
12364                                                                 }
12365
12366                                                                 matchedFormats[format] = callbacks;
12367                                                                 return false;
12368                                                         }
12369                                                 });
12370                                         });
12371
12372                                         // Check if current formats still match
12373                                         each(currentFormats, function(callbacks, format) {
12374                                                 if (!matchedFormats[format]) {
12375                                                         delete currentFormats[format];
12376
12377                                                         each(callbacks, function(callback) {
12378                                                                 callback(false, {node: e.element, format: format, parents: parents});
12379                                                         });
12380                                                 }
12381                                         });
12382                                 });
12383                         }
12384
12385                         // Add format listeners
12386                         each(formats.split(','), function(format) {
12387                                 if (!formatChangeData[format]) {
12388                                         formatChangeData[format] = [];
12389                                         formatChangeData[format].similar = similar;
12390                                 }
12391
12392                                 formatChangeData[format].push(callback);
12393                         });
12394
12395                         return this;
12396                 }
12397
12398                 // Expose to public
12399                 extend(this, {
12400                         get: get,
12401                         register: register,
12402                         apply: apply,
12403                         remove: remove,
12404                         toggle: toggle,
12405                         match: match,
12406                         matchAll: matchAll,
12407                         matchNode: matchNode,
12408                         canApply: canApply,
12409                         formatChanged: formatChanged
12410                 });
12411
12412                 // Initialize
12413                 defaultFormats();
12414                 addKeyboardShortcuts();
12415                 ed.on('BeforeGetContent', function() {
12416                         if (markCaretContainersBogus) {
12417                                 markCaretContainersBogus();
12418                         }
12419                 });
12420                 ed.on('mouseup keydown', function(e) {
12421                         if (disableCaretContainer) {
12422                                 disableCaretContainer(e);
12423                         }
12424                 });
12425
12426                 // Private functions
12427
12428                 /**
12429                  * Checks if the specified nodes name matches the format inline/block or selector.
12430                  *
12431                  * @private
12432                  * @param {Node} node Node to match against the specified format.
12433                  * @param {Object} format Format object o match with.
12434                  * @return {boolean} true/false if the format matches.
12435                  */
12436                 function matchName(node, format) {
12437                         // Check for inline match
12438                         if (isEq(node, format.inline)) {
12439                                 return TRUE;
12440                         }
12441
12442                         // Check for block match
12443                         if (isEq(node, format.block)) {
12444                                 return TRUE;
12445                         }
12446
12447                         // Check for selector match
12448                         if (format.selector) {
12449                                 return node.nodeType == 1 && dom.is(node, format.selector);
12450                         }
12451                 }
12452
12453                 /**
12454                  * Compares two string/nodes regardless of their case.
12455                  *
12456                  * @private
12457                  * @param {String/Node} Node or string to compare.
12458                  * @param {String/Node} Node or string to compare.
12459                  * @return {boolean} True/false if they match.
12460                  */
12461                 function isEq(str1, str2) {
12462                         str1 = str1 || '';
12463                         str2 = str2 || '';
12464
12465                         str1 = '' + (str1.nodeName || str1);
12466                         str2 = '' + (str2.nodeName || str2);
12467
12468                         return str1.toLowerCase() == str2.toLowerCase();
12469                 }
12470
12471                 /**
12472                  * Returns the style by name on the specified node. This method modifies the style
12473                  * contents to make it more easy to match. This will resolve a few browser issues.
12474                  *
12475                  * @private
12476                  * @param {Node} node to get style from.
12477                  * @param {String} name Style name to get.
12478                  * @return {String} Style item value.
12479                  */
12480                 function getStyle(node, name) {
12481                         return normalizeStyleValue(dom.getStyle(node, name), name);
12482                 }
12483
12484                 /**
12485                  * Normalize style value by name. This method modifies the style contents
12486                  * to make it more easy to match. This will resolve a few browser issues.
12487                  *
12488                  * @private
12489                  * @param {Node} node to get style from.
12490                  * @param {String} name Style name to get.
12491                  * @return {String} Style item value.
12492                  */
12493                 function normalizeStyleValue(value, name) {
12494                         // Force the format to hex
12495                         if (name == 'color' || name == 'backgroundColor') {
12496                                 value = dom.toHex(value);
12497                         }
12498
12499                         // Opera will return bold as 700
12500                         if (name == 'fontWeight' && value == 700) {
12501                                 value = 'bold';
12502                         }
12503
12504                         // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
12505                         if (name == 'fontFamily') {
12506                                 value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
12507                         }
12508
12509                         return '' + value;
12510                 }
12511
12512                 /**
12513                  * Replaces variables in the value. The variable format is %var.
12514                  *
12515                  * @private
12516                  * @param {String} value Value to replace variables in.
12517                  * @param {Object} vars Name/value array with variables to replace.
12518                  * @return {String} New value with replaced variables.
12519                  */
12520                 function replaceVars(value, vars) {
12521                         if (typeof(value) != "string") {
12522                                 value = value(vars);
12523                         } else if (vars) {
12524                                 value = value.replace(/%(\w+)/g, function(str, name) {
12525                                         return vars[name] || str;
12526                                 });
12527                         }
12528
12529                         return value;
12530                 }
12531
12532                 function isWhiteSpaceNode(node) {
12533                         return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
12534                 }
12535
12536                 function wrap(node, name, attrs) {
12537                         var wrapper = dom.create(name, attrs);
12538
12539                         node.parentNode.insertBefore(wrapper, node);
12540                         wrapper.appendChild(node);
12541
12542                         return wrapper;
12543                 }
12544
12545                 /**
12546                  * Expands the specified range like object to depending on format.
12547                  *
12548                  * For example on block formats it will move the start/end position
12549                  * to the beginning of the current block.
12550                  *
12551                  * @private
12552                  * @param {Object} rng Range like object.
12553                  * @param {Array} formats Array with formats to expand by.
12554                  * @return {Object} Expanded range like object.
12555                  */
12556                 function expandRng(rng, format, remove) {
12557                         var lastIdx, leaf, endPoint,
12558                                 startContainer = rng.startContainer,
12559                                 startOffset = rng.startOffset,
12560                                 endContainer = rng.endContainer,
12561                                 endOffset = rng.endOffset;
12562
12563                         // This function walks up the tree if there is no siblings before/after the node
12564                         function findParentContainer(start) {
12565                                 var container, parent, sibling, siblingName, root;
12566
12567                                 container = parent = start ? startContainer : endContainer;
12568                                 siblingName = start ? 'previousSibling' : 'nextSibling';
12569                                 root = dom.getRoot();
12570
12571                                 function isBogusBr(node) {
12572                                         return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
12573                                 }
12574
12575                                 // If it's a text node and the offset is inside the text
12576                                 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
12577                                         if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
12578                                                 return container;
12579                                         }
12580                                 }
12581
12582                                 for (;;) {
12583                                         // Stop expanding on block elements
12584                                         if (!format[0].block_expand && isBlock(parent)) {
12585                                                 return parent;
12586                                         }
12587
12588                                         // Walk left/right
12589                                         for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
12590                                                 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
12591                                                         return parent;
12592                                                 }
12593                                         }
12594
12595                                         // Check if we can move up are we at root level or body level
12596                                         if (parent.parentNode == root) {
12597                                                 container = parent;
12598                                                 break;
12599                                         }
12600
12601                                         parent = parent.parentNode;
12602                                 }
12603
12604                                 return container;
12605                         }
12606
12607                         // This function walks down the tree to find the leaf at the selection.
12608                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
12609                         function findLeaf(node, offset) {
12610                                 if (offset === undef) {
12611                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
12612                                 }
12613
12614                                 while (node && node.hasChildNodes()) {
12615                                         node = node.childNodes[offset];
12616                                         if (node) {
12617                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
12618                                         }
12619                                 }
12620                                 return { node: node, offset: offset };
12621                         }
12622
12623                         // If index based start position then resolve it
12624                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
12625                                 lastIdx = startContainer.childNodes.length - 1;
12626                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
12627
12628                                 if (startContainer.nodeType == 3) {
12629                                         startOffset = 0;
12630                                 }
12631                         }
12632
12633                         // If index based end position then resolve it
12634                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
12635                                 lastIdx = endContainer.childNodes.length - 1;
12636                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
12637
12638                                 if (endContainer.nodeType == 3) {
12639                                         endOffset = endContainer.nodeValue.length;
12640                                 }
12641                         }
12642
12643                         // Expands the node to the closes contentEditable false element if it exists
12644                         function findParentContentEditable(node) {
12645                                 var parent = node;
12646
12647                                 while (parent) {
12648                                         if (parent.nodeType === 1 && getContentEditable(parent)) {
12649                                                 return getContentEditable(parent) === "false" ? parent : node;
12650                                         }
12651
12652                                         parent = parent.parentNode;
12653                                 }
12654
12655                                 return node;
12656                         }
12657
12658                         function findWordEndPoint(container, offset, start) {
12659                                 var walker, node, pos, lastTextNode;
12660
12661                                 function findSpace(node, offset) {
12662                                         var pos, pos2, str = node.nodeValue;
12663
12664                                         if (typeof(offset) == "undefined") {
12665                                                 offset = start ? str.length : 0;
12666                                         }
12667
12668                                         if (start) {
12669                                                 pos = str.lastIndexOf(' ', offset);
12670                                                 pos2 = str.lastIndexOf('\u00a0', offset);
12671                                                 pos = pos > pos2 ? pos : pos2;
12672
12673                                                 // Include the space on remove to avoid tag soup
12674                                                 if (pos !== -1 && !remove) {
12675                                                         pos++;
12676                                                 }
12677                                         } else {
12678                                                 pos = str.indexOf(' ', offset);
12679                                                 pos2 = str.indexOf('\u00a0', offset);
12680                                                 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
12681                                         }
12682
12683                                         return pos;
12684                                 }
12685
12686                                 if (container.nodeType === 3) {
12687                                         pos = findSpace(container, offset);
12688
12689                                         if (pos !== -1) {
12690                                                 return {container: container, offset: pos};
12691                                         }
12692
12693                                         lastTextNode = container;
12694                                 }
12695
12696                                 // Walk the nodes inside the block
12697                                 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
12698                                 while ((node = walker[start ? 'prev' : 'next']())) {
12699                                         if (node.nodeType === 3) {
12700                                                 lastTextNode = node;
12701                                                 pos = findSpace(node);
12702
12703                                                 if (pos !== -1) {
12704                                                         return {container: node, offset: pos};
12705                                                 }
12706                                         } else if (isBlock(node)) {
12707                                                 break;
12708                                         }
12709                                 }
12710
12711                                 if (lastTextNode) {
12712                                         if (start) {
12713                                                 offset = 0;
12714                                         } else {
12715                                                 offset = lastTextNode.length;
12716                                         }
12717
12718                                         return {container: lastTextNode, offset: offset};
12719                                 }
12720                         }
12721
12722                         function findSelectorEndPoint(container, sibling_name) {
12723                                 var parents, i, y, curFormat;
12724
12725                                 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
12726                                         container = container[sibling_name];
12727                                 }
12728
12729                                 parents = getParents(container);
12730                                 for (i = 0; i < parents.length; i++) {
12731                                         for (y = 0; y < format.length; y++) {
12732                                                 curFormat = format[y];
12733
12734                                                 // If collapsed state is set then skip formats that doesn't match that
12735                                                 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
12736                                                         continue;
12737                                                 }
12738
12739                                                 if (dom.is(parents[i], curFormat.selector)) {
12740                                                         return parents[i];
12741                                                 }
12742                                         }
12743                                 }
12744
12745                                 return container;
12746                         }
12747
12748                         function findBlockEndPoint(container, sibling_name) {
12749                                 var node, root = dom.getRoot();
12750
12751                                 // Expand to block of similar type
12752                                 if (!format[0].wrapper) {
12753                                         node = dom.getParent(container, format[0].block);
12754                                 }
12755
12756                                 // Expand to first wrappable block element or any block element
12757                                 if (!node) {
12758                                         node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
12759                                                 // Fixes #6183 where it would expand to editable parent element in inline mode
12760                                                 return node != root && isTextBlock(node);
12761                                         });
12762                                 }
12763
12764                                 // Exclude inner lists from wrapping
12765                                 if (node && format[0].wrapper) {
12766                                         node = getParents(node, 'ul,ol').reverse()[0] || node;
12767                                 }
12768
12769                                 // Didn't find a block element look for first/last wrappable element
12770                                 if (!node) {
12771                                         node = container;
12772
12773                                         while (node[sibling_name] && !isBlock(node[sibling_name])) {
12774                                                 node = node[sibling_name];
12775
12776                                                 // Break on BR but include it will be removed later on
12777                                                 // we can't remove it now since we need to check if it can be wrapped
12778                                                 if (isEq(node, 'br')) {
12779                                                         break;
12780                                                 }
12781                                         }
12782                                 }
12783
12784                                 return node || container;
12785                         }
12786
12787                         // Expand to closest contentEditable element
12788                         startContainer = findParentContentEditable(startContainer);
12789                         endContainer = findParentContentEditable(endContainer);
12790
12791                         // Exclude bookmark nodes if possible
12792                         if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
12793                                 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
12794                                 startContainer = startContainer.nextSibling || startContainer;
12795
12796                                 if (startContainer.nodeType == 3) {
12797                                         startOffset = 0;
12798                                 }
12799                         }
12800
12801                         if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
12802                                 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
12803                                 endContainer = endContainer.previousSibling || endContainer;
12804
12805                                 if (endContainer.nodeType == 3) {
12806                                         endOffset = endContainer.length;
12807                                 }
12808                         }
12809
12810                         if (format[0].inline) {
12811                                 if (rng.collapsed) {
12812                                         // Expand left to closest word boundary
12813                                         endPoint = findWordEndPoint(startContainer, startOffset, true);
12814                                         if (endPoint) {
12815                                                 startContainer = endPoint.container;
12816                                                 startOffset = endPoint.offset;
12817                                         }
12818
12819                                         // Expand right to closest word boundary
12820                                         endPoint = findWordEndPoint(endContainer, endOffset);
12821                                         if (endPoint) {
12822                                                 endContainer = endPoint.container;
12823                                                 endOffset = endPoint.offset;
12824                                         }
12825                                 }
12826
12827                                 // Avoid applying formatting to a trailing space.
12828                                 leaf = findLeaf(endContainer, endOffset);
12829                                 if (leaf.node) {
12830                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
12831                                                 leaf = findLeaf(leaf.node.previousSibling);
12832                                         }
12833
12834                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
12835                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
12836
12837                                                 if (leaf.offset > 1) {
12838                                                         endContainer = leaf.node;
12839                                                         endContainer.splitText(leaf.offset - 1);
12840                                                 }
12841                                         }
12842                                 }
12843                         }
12844
12845                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers
12846                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
12847                         // This will reduce the number of wrapper elements that needs to be created
12848                         // Move start point up the tree
12849                         if (format[0].inline || format[0].block_expand) {
12850                                 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
12851                                         startContainer = findParentContainer(true);
12852                                 }
12853
12854                                 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
12855                                         endContainer = findParentContainer();
12856                                 }
12857                         }
12858
12859                         // Expand start/end container to matching selector
12860                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
12861                                 // Find new startContainer/endContainer if there is better one
12862                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
12863                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
12864                         }
12865
12866                         // Expand start/end container to matching block element or text node
12867                         if (format[0].block || format[0].selector) {
12868                                 // Find new startContainer/endContainer if there is better one
12869                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
12870                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
12871
12872                                 // Non block element then try to expand up the leaf
12873                                 if (format[0].block) {
12874                                         if (!isBlock(startContainer)) {
12875                                                 startContainer = findParentContainer(true);
12876                                         }
12877
12878                                         if (!isBlock(endContainer)) {
12879                                                 endContainer = findParentContainer();
12880                                         }
12881                                 }
12882                         }
12883
12884                         // Setup index for startContainer
12885                         if (startContainer.nodeType == 1) {
12886                                 startOffset = nodeIndex(startContainer);
12887                                 startContainer = startContainer.parentNode;
12888                         }
12889
12890                         // Setup index for endContainer
12891                         if (endContainer.nodeType == 1) {
12892                                 endOffset = nodeIndex(endContainer) + 1;
12893                                 endContainer = endContainer.parentNode;
12894                         }
12895
12896                         // Return new range like object
12897                         return {
12898                                 startContainer: startContainer,
12899                                 startOffset: startOffset,
12900                                 endContainer: endContainer,
12901                                 endOffset: endOffset
12902                         };
12903                 }
12904
12905                 /**
12906                  * Removes the specified format for the specified node. It will also remove the node if it doesn't have
12907                  * any attributes if the format specifies it to do so.
12908                  *
12909                  * @private
12910                  * @param {Object} format Format object with items to remove from node.
12911                  * @param {Object} vars Name/value object with variables to apply to format.
12912                  * @param {Node} node Node to remove the format styles on.
12913                  * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
12914                  * @return {Boolean} True/false if the node was removed or not.
12915                  */
12916                 function removeFormat(format, vars, node, compare_node) {
12917                         var i, attrs, stylesModified;
12918
12919                         // Check if node matches format
12920                         if (!matchName(node, format)) {
12921                                 return FALSE;
12922                         }
12923
12924                         // Should we compare with format attribs and styles
12925                         if (format.remove != 'all') {
12926                                 // Remove styles
12927                                 each(format.styles, function(value, name) {
12928                                         value = normalizeStyleValue(replaceVars(value, vars), name);
12929
12930                                         // Indexed array
12931                                         if (typeof(name) === 'number') {
12932                                                 name = value;
12933                                                 compare_node = 0;
12934                                         }
12935
12936                                         if (!compare_node || isEq(getStyle(compare_node, name), value)) {
12937                                                 dom.setStyle(node, name, '');
12938                                         }
12939
12940                                         stylesModified = 1;
12941                                 });
12942
12943                                 // Remove style attribute if it's empty
12944                                 if (stylesModified && dom.getAttrib(node, 'style') === '') {
12945                                         node.removeAttribute('style');
12946                                         node.removeAttribute('data-mce-style');
12947                                 }
12948
12949                                 // Remove attributes
12950                                 each(format.attributes, function(value, name) {
12951                                         var valueOut;
12952
12953                                         value = replaceVars(value, vars);
12954
12955                                         // Indexed array
12956                                         if (typeof(name) === 'number') {
12957                                                 name = value;
12958                                                 compare_node = 0;
12959                                         }
12960
12961                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
12962                                                 // Keep internal classes
12963                                                 if (name == 'class') {
12964                                                         value = dom.getAttrib(node, name);
12965                                                         if (value) {
12966                                                                 // Build new class value where everything is removed except the internal prefixed classes
12967                                                                 valueOut = '';
12968                                                                 each(value.split(/\s+/), function(cls) {
12969                                                                         if (/mce\w+/.test(cls)) {
12970                                                                                 valueOut += (valueOut ? ' ' : '') + cls;
12971                                                                         }
12972                                                                 });
12973
12974                                                                 // We got some internal classes left
12975                                                                 if (valueOut) {
12976                                                                         dom.setAttrib(node, name, valueOut);
12977                                                                         return;
12978                                                                 }
12979                                                         }
12980                                                 }
12981
12982                                                 // IE6 has a bug where the attribute doesn't get removed correctly
12983                                                 if (name == "class") {
12984                                                         node.removeAttribute('className');
12985                                                 }
12986
12987                                                 // Remove mce prefixed attributes
12988                                                 if (MCE_ATTR_RE.test(name)) {
12989                                                         node.removeAttribute('data-mce-' + name);
12990                                                 }
12991
12992                                                 node.removeAttribute(name);
12993                                         }
12994                                 });
12995
12996                                 // Remove classes
12997                                 each(format.classes, function(value) {
12998                                         value = replaceVars(value, vars);
12999
13000                                         if (!compare_node || dom.hasClass(compare_node, value)) {
13001                                                 dom.removeClass(node, value);
13002                                         }
13003                                 });
13004
13005                                 // Check for non internal attributes
13006                                 attrs = dom.getAttribs(node);
13007                                 for (i = 0; i < attrs.length; i++) {
13008                                         if (attrs[i].nodeName.indexOf('_') !== 0) {
13009                                                 return FALSE;
13010                                         }
13011                                 }
13012                         }
13013
13014                         // Remove the inline child if it's empty for example <b> or <span>
13015                         if (format.remove != 'none') {
13016                                 removeNode(node, format);
13017                                 return TRUE;
13018                         }
13019                 }
13020
13021                 /**
13022                  * Removes the node and wrap it's children in paragraphs before doing so or
13023                  * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
13024                  *
13025                  * If the div in the node below gets removed:
13026                  *  text<div>text</div>text
13027                  *
13028                  * Output becomes:
13029                  *  text<div><br />text<br /></div>text
13030                  *
13031                  * So when the div is removed the result is:
13032                  *  text<br />text<br />text
13033                  *
13034                  * @private
13035                  * @param {Node} node Node to remove + apply BR/P elements to.
13036                  * @param {Object} format Format rule.
13037                  * @return {Node} Input node.
13038                  */
13039                 function removeNode(node, format) {
13040                         var parentNode = node.parentNode, rootBlockElm;
13041
13042                         function find(node, next, inc) {
13043                                 node = getNonWhiteSpaceSibling(node, next, inc);
13044
13045                                 return !node || (node.nodeName == 'BR' || isBlock(node));
13046                         }
13047
13048                         if (format.block) {
13049                                 if (!forcedRootBlock) {
13050                                         // Append BR elements if needed before we remove the block
13051                                         if (isBlock(node) && !isBlock(parentNode)) {
13052                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
13053                                                         node.insertBefore(dom.create('br'), node.firstChild);
13054                                                 }
13055
13056                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
13057                                                         node.appendChild(dom.create('br'));
13058                                                 }
13059                                         }
13060                                 } else {
13061                                         // Wrap the block in a forcedRootBlock if we are at the root of document
13062                                         if (parentNode == dom.getRoot()) {
13063                                                 if (!format.list_block || !isEq(node, format.list_block)) {
13064                                                         each(grep(node.childNodes), function(node) {
13065                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
13066                                                                         if (!rootBlockElm) {
13067                                                                                 rootBlockElm = wrap(node, forcedRootBlock);
13068                                                                         } else {
13069                                                                                 rootBlockElm.appendChild(node);
13070                                                                         }
13071                                                                 } else {
13072                                                                         rootBlockElm = 0;
13073                                                                 }
13074                                                         });
13075                                                 }
13076                                         }
13077                                 }
13078                         }
13079
13080                         // Never remove nodes that isn't the specified inline element if a selector is specified too
13081                         if (format.selector && format.inline && !isEq(format.inline, node)) {
13082                                 return;
13083                         }
13084
13085                         dom.remove(node, 1);
13086                 }
13087
13088                 /**
13089                  * Returns the next/previous non whitespace node.
13090                  *
13091                  * @private
13092                  * @param {Node} node Node to start at.
13093                  * @param {boolean} next (Optional) Include next or previous node defaults to previous.
13094                  * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
13095                  * @return {Node} Next or previous node or undefined if it wasn't found.
13096                  */
13097                 function getNonWhiteSpaceSibling(node, next, inc) {
13098                         if (node) {
13099                                 next = next ? 'nextSibling' : 'previousSibling';
13100
13101                                 for (node = inc ? node : node[next]; node; node = node[next]) {
13102                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
13103                                                 return node;
13104                                         }
13105                                 }
13106                         }
13107                 }
13108
13109                 /**
13110                  * Checks if the specified node is a bookmark node or not.
13111                  *
13112                  * @private
13113                  * @param {Node} node Node to check if it's a bookmark node or not.
13114                  * @return {Boolean} true/false if the node is a bookmark node.
13115                  */
13116                 function isBookmarkNode(node) {
13117                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
13118                 }
13119
13120                 /**
13121                  * Merges the next/previous sibling element if they match.
13122                  *
13123                  * @private
13124                  * @param {Node} prev Previous node to compare/merge.
13125                  * @param {Node} next Next node to compare/merge.
13126                  * @return {Node} Next node if we didn't merge and prev node if we did.
13127                  */
13128                 function mergeSiblings(prev, next) {
13129                         var sibling, tmpSibling;
13130
13131                         /**
13132                          * Compares two nodes and checks if it's attributes and styles matches.
13133                          * This doesn't compare classes as items since their order is significant.
13134                          *
13135                          * @private
13136                          * @param {Node} node1 First node to compare with.
13137                          * @param {Node} node2 Second node to compare with.
13138                          * @return {boolean} True/false if the nodes are the same or not.
13139                          */
13140                         function compareElements(node1, node2) {
13141                                 // Not the same name
13142                                 if (node1.nodeName != node2.nodeName) {
13143                                         return FALSE;
13144                                 }
13145
13146                                 /**
13147                                  * Returns all the nodes attributes excluding internal ones, styles and classes.
13148                                  *
13149                                  * @private
13150                                  * @param {Node} node Node to get attributes from.
13151                                  * @return {Object} Name/value object with attributes and attribute values.
13152                                  */
13153                                 function getAttribs(node) {
13154                                         var attribs = {};
13155
13156                                         each(dom.getAttribs(node), function(attr) {
13157                                                 var name = attr.nodeName.toLowerCase();
13158
13159                                                 // Don't compare internal attributes or style
13160                                                 if (name.indexOf('_') !== 0 && name !== 'style') {
13161                                                         attribs[name] = dom.getAttrib(node, name);
13162                                                 }
13163                                         });
13164
13165                                         return attribs;
13166                                 }
13167
13168                                 /**
13169                                  * Compares two objects checks if it's key + value exists in the other one.
13170                                  *
13171                                  * @private
13172                                  * @param {Object} obj1 First object to compare.
13173                                  * @param {Object} obj2 Second object to compare.
13174                                  * @return {boolean} True/false if the objects matches or not.
13175                                  */
13176                                 function compareObjects(obj1, obj2) {
13177                                         var value, name;
13178
13179                                         for (name in obj1) {
13180                                                 // Obj1 has item obj2 doesn't have
13181                                                 if (obj1.hasOwnProperty(name)) {
13182                                                         value = obj2[name];
13183
13184                                                         // Obj2 doesn't have obj1 item
13185                                                         if (value === undef) {
13186                                                                 return FALSE;
13187                                                         }
13188
13189                                                         // Obj2 item has a different value
13190                                                         if (obj1[name] != value) {
13191                                                                 return FALSE;
13192                                                         }
13193
13194                                                         // Delete similar value
13195                                                         delete obj2[name];
13196                                                 }
13197                                         }
13198
13199                                         // Check if obj 2 has something obj 1 doesn't have
13200                                         for (name in obj2) {
13201                                                 // Obj2 has item obj1 doesn't have
13202                                                 if (obj2.hasOwnProperty(name)) {
13203                                                         return FALSE;
13204                                                 }
13205                                         }
13206
13207                                         return TRUE;
13208                                 }
13209
13210                                 // Attribs are not the same
13211                                 if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
13212                                         return FALSE;
13213                                 }
13214
13215                                 // Styles are not the same
13216                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
13217                                         return FALSE;
13218                                 }
13219
13220                                 return TRUE;
13221                         }
13222
13223                         function findElementSibling(node, sibling_name) {
13224                                 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
13225                                         if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
13226                                                 return node;
13227                                         }
13228
13229                                         if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
13230                                                 return sibling;
13231                                         }
13232                                 }
13233
13234                                 return node;
13235                         }
13236
13237                         // Check if next/prev exists and that they are elements
13238                         if (prev && next) {
13239                                 // If previous sibling is empty then jump over it
13240                                 prev = findElementSibling(prev, 'previousSibling');
13241                                 next = findElementSibling(next, 'nextSibling');
13242
13243                                 // Compare next and previous nodes
13244                                 if (compareElements(prev, next)) {
13245                                         // Append nodes between
13246                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {
13247                                                 tmpSibling = sibling;
13248                                                 sibling = sibling.nextSibling;
13249                                                 prev.appendChild(tmpSibling);
13250                                         }
13251
13252                                         // Remove next node
13253                                         dom.remove(next);
13254
13255                                         // Move children into prev node
13256                                         each(grep(next.childNodes), function(node) {
13257                                                 prev.appendChild(node);
13258                                         });
13259
13260                                         return prev;
13261                                 }
13262                         }
13263
13264                         return next;
13265                 }
13266
13267                 function getContainer(rng, start) {
13268                         var container, offset, lastIdx;
13269
13270                         container = rng[start ? 'startContainer' : 'endContainer'];
13271                         offset = rng[start ? 'startOffset' : 'endOffset'];
13272
13273                         if (container.nodeType == 1) {
13274                                 lastIdx = container.childNodes.length - 1;
13275
13276                                 if (!start && offset) {
13277                                         offset--;
13278                                 }
13279
13280                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
13281                         }
13282
13283                         // If start text node is excluded then walk to the next node
13284                         if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
13285                                 container = new TreeWalker(container, ed.getBody()).next() || container;
13286                         }
13287
13288                         // If end text node is excluded then walk to the previous node
13289                         if (container.nodeType === 3 && !start && offset === 0) {
13290                                 container = new TreeWalker(container, ed.getBody()).prev() || container;
13291                         }
13292
13293                         return container;
13294                 }
13295
13296                 function performCaretAction(type, name, vars) {
13297                         var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
13298
13299                         // Creates a caret container bogus element
13300                         function createCaretContainer(fill) {
13301                                 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
13302
13303                                 if (fill) {
13304                                         caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
13305                                 }
13306
13307                                 return caretContainer;
13308                         }
13309
13310                         function isCaretContainerEmpty(node, nodes) {
13311                                 while (node) {
13312                                         if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
13313                                                 return false;
13314                                         }
13315
13316                                         // Collect nodes
13317                                         if (nodes && node.nodeType === 1) {
13318                                                 nodes.push(node);
13319                                         }
13320
13321                                         node = node.firstChild;
13322                                 }
13323
13324                                 return true;
13325                         }
13326
13327                         // Returns any parent caret container element
13328                         function getParentCaretContainer(node) {
13329                                 while (node) {
13330                                         if (node.id === caretContainerId) {
13331                                                 return node;
13332                                         }
13333
13334                                         node = node.parentNode;
13335                                 }
13336                         }
13337
13338                         // Finds the first text node in the specified node
13339                         function findFirstTextNode(node) {
13340                                 var walker;
13341
13342                                 if (node) {
13343                                         walker = new TreeWalker(node, node);
13344
13345                                         for (node = walker.current(); node; node = walker.next()) {
13346                                                 if (node.nodeType === 3) {
13347                                                         return node;
13348                                                 }
13349                                         }
13350                                 }
13351                         }
13352
13353                         // Removes the caret container for the specified node or all on the current document
13354                         function removeCaretContainer(node, move_caret) {
13355                                 var child, rng;
13356
13357                                 if (!node) {
13358                                         node = getParentCaretContainer(selection.getStart());
13359
13360                                         if (!node) {
13361                                                 while ((node = dom.get(caretContainerId))) {
13362                                                         removeCaretContainer(node, false);
13363                                                 }
13364                                         }
13365                                 } else {
13366                                         rng = selection.getRng(true);
13367
13368                                         if (isCaretContainerEmpty(node)) {
13369                                                 if (move_caret !== false) {
13370                                                         rng.setStartBefore(node);
13371                                                         rng.setEndBefore(node);
13372                                                 }
13373
13374                                                 dom.remove(node);
13375                                         } else {
13376                                                 child = findFirstTextNode(node);
13377
13378                                                 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
13379                                                         child = child.deleteData(0, 1);
13380                                                 }
13381
13382                                                 dom.remove(node, 1);
13383                                         }
13384
13385                                         selection.setRng(rng);
13386                                 }
13387                         }
13388
13389                         // Applies formatting to the caret postion
13390                         function applyCaretFormat() {
13391                                 var rng, caretContainer, textNode, offset, bookmark, container, text;
13392
13393                                 rng = selection.getRng(true);
13394                                 offset = rng.startOffset;
13395                                 container = rng.startContainer;
13396                                 text = container.nodeValue;
13397
13398                                 caretContainer = getParentCaretContainer(selection.getStart());
13399                                 if (caretContainer) {
13400                                         textNode = findFirstTextNode(caretContainer);
13401                                 }
13402
13403                                 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
13404                                 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
13405                                         // Get bookmark of caret position
13406                                         bookmark = selection.getBookmark();
13407
13408                                         // Collapse bookmark range (WebKit)
13409                                         rng.collapse(true);
13410
13411                                         // Expand the range to the closest word and split it at those points
13412                                         rng = expandRng(rng, get(name));
13413                                         rng = rangeUtils.split(rng);
13414
13415                                         // Apply the format to the range
13416                                         apply(name, vars, rng);
13417
13418                                         // Move selection back to caret position
13419                                         selection.moveToBookmark(bookmark);
13420                                 } else {
13421                                         if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
13422                                                 caretContainer = createCaretContainer(true);
13423                                                 textNode = caretContainer.firstChild;
13424
13425                                                 rng.insertNode(caretContainer);
13426                                                 offset = 1;
13427
13428                                                 apply(name, vars, caretContainer);
13429                                         } else {
13430                                                 apply(name, vars, caretContainer);
13431                                         }
13432
13433                                         // Move selection to text node
13434                                         selection.setCursorLocation(textNode, offset);
13435                                 }
13436                         }
13437
13438                         function removeCaretFormat() {
13439                                 var rng = selection.getRng(true), container, offset, bookmark,
13440                                         hasContentAfter, node, formatNode, parents = [], i, caretContainer;
13441
13442                                 container = rng.startContainer;
13443                                 offset = rng.startOffset;
13444                                 node = container;
13445
13446                                 if (container.nodeType == 3) {
13447                                         if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
13448                                                 hasContentAfter = true;
13449                                         }
13450
13451                                         node = node.parentNode;
13452                                 }
13453
13454                                 while (node) {
13455                                         if (matchNode(node, name, vars)) {
13456                                                 formatNode = node;
13457                                                 break;
13458                                         }
13459
13460                                         if (node.nextSibling) {
13461                                                 hasContentAfter = true;
13462                                         }
13463
13464                                         parents.push(node);
13465                                         node = node.parentNode;
13466                                 }
13467
13468                                 // Node doesn't have the specified format
13469                                 if (!formatNode) {
13470                                         return;
13471                                 }
13472
13473                                 // Is there contents after the caret then remove the format on the element
13474                                 if (hasContentAfter) {
13475                                         // Get bookmark of caret position
13476                                         bookmark = selection.getBookmark();
13477
13478                                         // Collapse bookmark range (WebKit)
13479                                         rng.collapse(true);
13480
13481                                         // Expand the range to the closest word and split it at those points
13482                                         rng = expandRng(rng, get(name), true);
13483                                         rng = rangeUtils.split(rng);
13484
13485                                         // Remove the format from the range
13486                                         remove(name, vars, rng);
13487
13488                                         // Move selection back to caret position
13489                                         selection.moveToBookmark(bookmark);
13490                                 } else {
13491                                         caretContainer = createCaretContainer();
13492
13493                                         node = caretContainer;
13494                                         for (i = parents.length - 1; i >= 0; i--) {
13495                                                 node.appendChild(dom.clone(parents[i], false));
13496                                                 node = node.firstChild;
13497                                         }
13498
13499                                         // Insert invisible character into inner most format element
13500                                         node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
13501                                         node = node.firstChild;
13502
13503                                         var block = dom.getParent(formatNode, isTextBlock);
13504
13505                                         if (block && dom.isEmpty(block)) {
13506                                                 // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
13507                                                 formatNode.parentNode.replaceChild(caretContainer, formatNode);
13508                                         } else {
13509                                                 // Insert caret container after the formated node
13510                                                 dom.insertAfter(caretContainer, formatNode);
13511                                         }
13512
13513                                         // Move selection to text node
13514                                         selection.setCursorLocation(node, 1);
13515                                         // If the formatNode is empty, we can remove it safely. 
13516                                         if(dom.isEmpty(formatNode)) {
13517                                                 dom.remove(formatNode);
13518                                         }
13519                                 }
13520                         }
13521
13522                         // Checks if the parent caret container node isn't empty if that is the case it
13523                         // will remove the bogus state on all children that isn't empty
13524                         function unmarkBogusCaretParents() {
13525                                 var caretContainer;
13526
13527                                 caretContainer = getParentCaretContainer(selection.getStart());
13528                                 if (caretContainer && !dom.isEmpty(caretContainer)) {
13529                                         walk(caretContainer, function(node) {
13530                                                 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
13531                                                         dom.setAttrib(node, 'data-mce-bogus', null);
13532                                                 }
13533                                         }, 'childNodes');
13534                                 }
13535                         }
13536
13537                         // Only bind the caret events once
13538                         if (!ed._hasCaretEvents) {
13539                                 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
13540                                 markCaretContainersBogus = function() {
13541                                         var nodes = [], i;
13542
13543                                         if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
13544                                                 // Mark children
13545                                                 i = nodes.length;
13546                                                 while (i--) {
13547                                                         dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
13548                                                 }
13549                                         }
13550                                 };
13551
13552                                 disableCaretContainer = function(e) {
13553                                         var keyCode = e.keyCode;
13554
13555                                         removeCaretContainer();
13556
13557                                         // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
13558                                         if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
13559                                                 removeCaretContainer(getParentCaretContainer(selection.getStart()));
13560                                         }
13561
13562                                         unmarkBogusCaretParents();
13563                                 };
13564
13565                                 // Remove bogus state if they got filled by contents using editor.selection.setContent
13566                                 ed.on('SetContent', function(e) {
13567                                         if (e.selection) {
13568                                                 unmarkBogusCaretParents();
13569                                         }
13570                                 });
13571                                 ed._hasCaretEvents = true;
13572                         }
13573
13574                         // Do apply or remove caret format
13575                         if (type == "apply") {
13576                                 applyCaretFormat();
13577                         } else {
13578                                 removeCaretFormat();
13579                         }
13580                 }
13581
13582                 /**
13583                  * Moves the start to the first suitable text node.
13584                  */
13585                 function moveStart(rng) {
13586                         var container = rng.startContainer,
13587                                         offset = rng.startOffset, isAtEndOfText,
13588                                         walker, node, nodes, tmpNode;
13589
13590                         // Convert text node into index if possible
13591                         if (container.nodeType == 3 && offset >= container.nodeValue.length) {
13592                                 // Get the parent container location and walk from there
13593                                 offset = nodeIndex(container);
13594                                 container = container.parentNode;
13595                                 isAtEndOfText = true;
13596                         }
13597
13598                         // Move startContainer/startOffset in to a suitable node
13599                         if (container.nodeType == 1) {
13600                                 nodes = container.childNodes;
13601                                 container = nodes[Math.min(offset, nodes.length - 1)];
13602                                 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
13603
13604                                 // If offset is at end of the parent node walk to the next one
13605                                 if (offset > nodes.length - 1 || isAtEndOfText) {
13606                                         walker.next();
13607                                 }
13608
13609                                 for (node = walker.current(); node; node = walker.next()) {
13610                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
13611                                                 // IE has a "neat" feature where it moves the start node into the closest element
13612                                                 // we can avoid this by inserting an element before it and then remove it after we set the selection
13613                                                 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
13614                                                 node.parentNode.insertBefore(tmpNode, node);
13615
13616                                                 // Set selection and remove tmpNode
13617                                                 rng.setStart(node, 0);
13618                                                 selection.setRng(rng);
13619                                                 dom.remove(tmpNode);
13620
13621                                                 return;
13622                                         }
13623                                 }
13624                         }
13625                 }
13626         };
13627 });
13628
13629 // Included from: js/tinymce/classes/UndoManager.js
13630
13631 /**
13632  * UndoManager.js
13633  *
13634  * Copyright, Moxiecode Systems AB
13635  * Released under LGPL License.
13636  *
13637  * License: http://www.tinymce.com/license
13638  * Contributing: http://www.tinymce.com/contributing
13639  */
13640
13641 /**
13642  * 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.
13643  *
13644  * @class tinymce.UndoManager
13645  */
13646 define("tinymce/UndoManager", [
13647         "tinymce/Env",
13648         "tinymce/util/Tools"
13649 ], function(Env, Tools) {
13650         var trim = Tools.trim, trimContentRegExp;
13651
13652         trimContentRegExp = new RegExp([
13653                 '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
13654                 '<div[^>]+data-mce-bogus[^>]+><\\/div>', // Trim bogus divs like resize handles
13655                 '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
13656         ].join('|'), 'gi');
13657
13658         return function(editor) {
13659                 var self, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, lock;
13660
13661                 // Returns a trimmed version of the current editor contents
13662                 function getContent() {
13663                         return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, ''));
13664                 }
13665
13666                 function addNonTypingUndoLevel() {
13667                         self.typing = false;
13668                         self.add();
13669                 }
13670
13671                 // Add initial undo level when the editor is initialized
13672                 editor.on('init', function() {
13673                         self.add();
13674                 });
13675
13676                 // Get position before an execCommand is processed
13677                 editor.on('BeforeExecCommand', function(e) {
13678                         var cmd = e.command;
13679
13680                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
13681                                 self.beforeChange();
13682                         }
13683                 });
13684
13685                 // Add undo level after an execCommand call was made
13686                 editor.on('ExecCommand', function(e) {
13687                         var cmd = e.command;
13688
13689                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
13690                                 self.add();
13691                         }
13692                 });
13693
13694                 editor.on('ObjectResizeStart', function() {
13695                         self.beforeChange();
13696                 });
13697
13698                 editor.on('SaveContent ObjectResized', addNonTypingUndoLevel);
13699                 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
13700                 editor.dom.bind(editor.getBody(), 'focusout', function() {
13701                         if (!editor.removed && self.typing) {
13702                                 addNonTypingUndoLevel();
13703                         }
13704                 });
13705
13706                 editor.on('KeyUp', function(e) {
13707                         var keyCode = e.keyCode;
13708
13709                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
13710                                 addNonTypingUndoLevel();
13711                                 editor.nodeChanged();
13712                         }
13713
13714                         if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
13715                                 editor.nodeChanged();
13716                         }
13717
13718                         // Fire a TypingUndo event on the first character entered
13719                         if (isFirstTypedCharacter && self.typing) {
13720                                 // Make the it dirty if the content was changed after typing the first character
13721                                 if (!editor.isDirty()) {
13722                                         editor.isNotDirty = !data[0] || getContent() == data[0].content;
13723
13724                                         // Fire initial change event
13725                                         if (!editor.isNotDirty) {
13726                                                 editor.fire('change', {level: data[0], lastLevel: null});
13727                                         }
13728                                 }
13729
13730                                 editor.fire('TypingUndo');
13731                                 isFirstTypedCharacter = false;
13732                                 editor.nodeChanged();
13733                         }
13734                 });
13735
13736                 editor.on('KeyDown', function(e) {
13737                         var keyCode = e.keyCode;
13738
13739                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
13740                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
13741                                 if (self.typing) {
13742                                         addNonTypingUndoLevel();
13743                                 }
13744
13745                                 return;
13746                         }
13747
13748                         // If key isn't shift,ctrl,alt,capslock,metakey
13749                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
13750                                 self.beforeChange();
13751                                 self.typing = true;
13752                                 self.add();
13753                                 isFirstTypedCharacter = true;
13754                         }
13755                 });
13756
13757                 editor.on('MouseDown', function() {
13758                         if (self.typing) {
13759                                 addNonTypingUndoLevel();
13760                         }
13761                 });
13762
13763                 // Add keyboard shortcuts for undo/redo keys
13764                 editor.addShortcut('ctrl+z', '', 'Undo');
13765                 editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo');
13766
13767                 editor.on('AddUndo Undo Redo ClearUndos MouseUp', function(e) {
13768                         if (!e.isDefaultPrevented()) {
13769                                 editor.nodeChanged();
13770                         }
13771                 });
13772
13773                 self = {
13774                         // Explose for debugging reasons
13775                         data: data,
13776
13777                         /**
13778                          * State if the user is currently typing or not. This will add a typing operation into one undo
13779                          * level instead of one new level for each keystroke.
13780                          *
13781                          * @field {Boolean} typing
13782                          */
13783                         typing: false,
13784
13785                         /**
13786                          * Stores away a bookmark to be used when performing an undo action so that the selection is before
13787                          * the change has been made.
13788                          *
13789                          * @method beforeChange
13790                          */
13791                         beforeChange: function() {
13792                                 if (!lock) {
13793                                         beforeBookmark = editor.selection.getBookmark(2, true);
13794                                 }
13795                         },
13796
13797                         /**
13798                          * Adds a new undo level/snapshot to the undo list.
13799                          *
13800                          * @method add
13801                          * @param {Object} l Optional undo level object to add.
13802                          * @return {Object} Undo level that got added or null it a level wasn't needed.
13803                          */
13804                         add: function(level) {
13805                                 var i, settings = editor.settings, lastLevel;
13806
13807                                 level = level || {};
13808                                 level.content = getContent();
13809
13810                                 if (lock || editor.fire('BeforeAddUndo', {level: level}).isDefaultPrevented()) {
13811                                         return null;
13812                                 }
13813
13814                                 // Add undo level if needed
13815                                 lastLevel = data[index];
13816                                 if (lastLevel && lastLevel.content == level.content) {
13817                                         return null;
13818                                 }
13819
13820                                 // Set before bookmark on previous level
13821                                 if (data[index]) {
13822                                         data[index].beforeBookmark = beforeBookmark;
13823                                 }
13824
13825                                 // Time to compress
13826                                 if (settings.custom_undo_redo_levels) {
13827                                         if (data.length > settings.custom_undo_redo_levels) {
13828                                                 for (i = 0; i < data.length - 1; i++) {
13829                                                         data[i] = data[i + 1];
13830                                                 }
13831
13832                                                 data.length--;
13833                                                 index = data.length;
13834                                         }
13835                                 }
13836
13837                                 // Get a non intrusive normalized bookmark
13838                                 level.bookmark = editor.selection.getBookmark(2, true);
13839
13840                                 // Crop array if needed
13841                                 if (index < data.length - 1) {
13842                                         data.length = index + 1;
13843                                 }
13844
13845                                 data.push(level);
13846                                 index = data.length - 1;
13847
13848                                 var args = {level: level, lastLevel: lastLevel};
13849
13850                                 editor.fire('AddUndo', args);
13851
13852                                 if (index > 0) {
13853                                         editor.fire('change', args);
13854                                         editor.isNotDirty = false;
13855                                 }
13856
13857                                 return level;
13858                         },
13859
13860                         /**
13861                          * Undoes the last action.
13862                          *
13863                          * @method undo
13864                          * @return {Object} Undo level or null if no undo was performed.
13865                          */
13866                         undo: function() {
13867                                 var level;
13868
13869                                 if (self.typing) {
13870                                         self.add();
13871                                         self.typing = false;
13872                                 }
13873
13874                                 if (index > 0) {
13875                                         level = data[--index];
13876
13877                                         editor.setContent(level.content, {format: 'raw'});
13878                                         editor.selection.moveToBookmark(level.beforeBookmark);
13879
13880                                         editor.fire('undo', {level: level});
13881                                 }
13882
13883                                 return level;
13884                         },
13885
13886                         /**
13887                          * Redoes the last action.
13888                          *
13889                          * @method redo
13890                          * @return {Object} Redo level or null if no redo was performed.
13891                          */
13892                         redo: function() {
13893                                 var level;
13894
13895                                 if (index < data.length - 1) {
13896                                         level = data[++index];
13897
13898                                         editor.setContent(level.content, {format: 'raw'});
13899                                         editor.selection.moveToBookmark(level.bookmark);
13900
13901                                         editor.fire('redo', {level: level});
13902                                 }
13903
13904                                 return level;
13905                         },
13906
13907                         /**
13908                          * Removes all undo levels.
13909                          *
13910                          * @method clear
13911                          */
13912                         clear: function() {
13913                                 data = [];
13914                                 index = 0;
13915                                 self.typing = false;
13916                                 editor.fire('ClearUndos');
13917                         },
13918
13919                         /**
13920                          * Returns true/false if the undo manager has any undo levels.
13921                          *
13922                          * @method hasUndo
13923                          * @return {Boolean} true/false if the undo manager has any undo levels.
13924                          */
13925                         hasUndo: function() {
13926                                 // Has undo levels or typing and content isn't the same as the initial level
13927                                 return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
13928                         },
13929
13930                         /**
13931                          * Returns true/false if the undo manager has any redo levels.
13932                          *
13933                          * @method hasRedo
13934                          * @return {Boolean} true/false if the undo manager has any redo levels.
13935                          */
13936                         hasRedo: function() {
13937                                 return index < data.length - 1 && !this.typing;
13938                         },
13939
13940                         /**
13941                          * Executes the specified function in an undo transation. The selection
13942                          * before the modification will be stored to the undo stack and if the DOM changes
13943                          * it will add a new undo level. Any methods within the transation that adds undo levels will
13944                          * be ignored. So a transation can include calls to execCommand or editor.insertContent.
13945                          *
13946                          * @method transact
13947                          * @param {function} callback Function to execute dom manipulation logic in.
13948                          */
13949                         transact: function(callback) {
13950                                 self.beforeChange();
13951
13952                                 lock = true;
13953                                 callback();
13954                                 lock = false;
13955
13956                                 self.add();
13957                         }
13958                 };
13959
13960                 return self;
13961         };
13962 });
13963
13964 // Included from: js/tinymce/classes/EnterKey.js
13965
13966 /**
13967  * EnterKey.js
13968  *
13969  * Copyright, Moxiecode Systems AB
13970  * Released under LGPL License.
13971  *
13972  * License: http://www.tinymce.com/license
13973  * Contributing: http://www.tinymce.com/contributing
13974  */
13975
13976 /**
13977  * Contains logic for handling the enter key to split/generate block elements.
13978  */
13979 define("tinymce/EnterKey", [
13980         "tinymce/dom/TreeWalker",
13981         "tinymce/Env"
13982 ], function(TreeWalker, Env) {
13983         var isIE = Env.ie && Env.ie < 11;
13984
13985         return function(editor) {
13986                 var dom = editor.dom, selection = editor.selection, settings = editor.settings;
13987                 var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
13988
13989                 function handleEnterKey(evt) {
13990                         var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
13991                                 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
13992
13993                         // Returns true if the block can be split into two blocks or not
13994                         function canSplitBlock(node) {
13995                                 return node &&
13996                                         dom.isBlock(node) &&
13997                                         !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
13998                                         !/^(fixed|absolute)/i.test(node.style.position) &&
13999                                         dom.getContentEditable(node) !== "true";
14000                         }
14001
14002                         // Renders empty block on IE
14003                         function renderBlockOnIE(block) {
14004                                 var oldRng;
14005
14006                                 if (dom.isBlock(block)) {
14007                                         oldRng = selection.getRng();
14008                                         block.appendChild(dom.create('span', null, '\u00a0'));
14009                                         selection.select(block);
14010                                         block.lastChild.outerHTML = '';
14011                                         selection.setRng(oldRng);
14012                                 }
14013                         }
14014
14015                         // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
14016                         function trimInlineElementsOnLeftSideOfBlock(block) {
14017                                 var node = block, firstChilds = [], i;
14018
14019                                 // Find inner most first child ex: <p><i><b>*</b></i></p>
14020                                 while ((node = node.firstChild)) {
14021                                         if (dom.isBlock(node)) {
14022                                                 return;
14023                                         }
14024
14025                                         if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
14026                                                 firstChilds.push(node);
14027                                         }
14028                                 }
14029
14030                                 i = firstChilds.length;
14031                                 while (i--) {
14032                                         node = firstChilds[i];
14033                                         if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
14034                                                 dom.remove(node);
14035                                         } else {
14036                                                 // Remove <a> </a> see #5381
14037                                                 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
14038                                                         dom.remove(node);
14039                                                 }
14040                                         }
14041                                 }
14042                         }
14043
14044                         // Moves the caret to a suitable position within the root for example in the first non
14045                         // pure whitespace text node or before an image
14046                         function moveToCaretPosition(root) {
14047                                 var walker, node, rng, lastNode = root, tempElm;
14048
14049                                 rng = dom.createRng();
14050
14051                                 if (root.hasChildNodes()) {
14052                                         walker = new TreeWalker(root, root);
14053
14054                                         while ((node = walker.current())) {
14055                                                 if (node.nodeType == 3) {
14056                                                         rng.setStart(node, 0);
14057                                                         rng.setEnd(node, 0);
14058                                                         break;
14059                                                 }
14060
14061                                                 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
14062                                                         rng.setStartBefore(node);
14063                                                         rng.setEndBefore(node);
14064                                                         break;
14065                                                 }
14066
14067                                                 lastNode = node;
14068                                                 node = walker.next();
14069                                         }
14070
14071                                         if (!node) {
14072                                                 rng.setStart(lastNode, 0);
14073                                                 rng.setEnd(lastNode, 0);
14074                                         }
14075                                 } else {
14076                                         if (root.nodeName == 'BR') {
14077                                                 if (root.nextSibling && dom.isBlock(root.nextSibling)) {
14078                                                         // Trick on older IE versions to render the caret before the BR between two lists
14079                                                         if (!documentMode || documentMode < 9) {
14080                                                                 tempElm = dom.create('br');
14081                                                                 root.parentNode.insertBefore(tempElm, root);
14082                                                         }
14083
14084                                                         rng.setStartBefore(root);
14085                                                         rng.setEndBefore(root);
14086                                                 } else {
14087                                                         rng.setStartAfter(root);
14088                                                         rng.setEndAfter(root);
14089                                                 }
14090                                         } else {
14091                                                 rng.setStart(root, 0);
14092                                                 rng.setEnd(root, 0);
14093                                         }
14094                                 }
14095
14096                                 selection.setRng(rng);
14097
14098                                 // Remove tempElm created for old IE:s
14099                                 dom.remove(tempElm);
14100                                 selection.scrollIntoView(root);
14101                         }
14102
14103                         // Creates a new block element by cloning the current one or creating a new one if the name is specified
14104                         // This function will also copy any text formatting from the parent block and add it to the new one
14105                         function createNewBlock(name) {
14106                                 var node = container, block, clonedNode, caretNode;
14107
14108                                 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
14109                                 caretNode = block;
14110
14111                                 // Clone any parent styles
14112                                 if (settings.keep_styles !== false) {
14113                                         do {
14114                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
14115                                                         // Never clone a caret containers
14116                                                         if (node.id == '_mce_caret') {
14117                                                                 continue;
14118                                                         }
14119
14120                                                         clonedNode = node.cloneNode(false);
14121                                                         dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
14122
14123                                                         if (block.hasChildNodes()) {
14124                                                                 clonedNode.appendChild(block.firstChild);
14125                                                                 block.appendChild(clonedNode);
14126                                                         } else {
14127                                                                 caretNode = clonedNode;
14128                                                                 block.appendChild(clonedNode);
14129                                                         }
14130                                                 }
14131                                         } while ((node = node.parentNode));
14132                                 }
14133
14134                                 // BR is needed in empty blocks on non IE browsers
14135                                 if (!isIE) {
14136                                         caretNode.innerHTML = '<br data-mce-bogus="1">';
14137                                 }
14138
14139                                 return block;
14140                         }
14141
14142                         // Returns true/false if the caret is at the start/end of the parent block element
14143                         function isCaretAtStartOrEndOfBlock(start) {
14144                                 var walker, node, name;
14145
14146                                 // Caret is in the middle of a text node like "a|b"
14147                                 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
14148                                         return false;
14149                                 }
14150
14151                                 // If after the last element in block node edge case for #5091
14152                                 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
14153                                         return true;
14154                                 }
14155
14156                                 // If the caret if before the first element in parentBlock
14157                                 if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
14158                                         return true;
14159                                 }
14160
14161                                 // Caret can be before/after a table
14162                                 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
14163                                         return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
14164                                 }
14165
14166                                 // Walk the DOM and look for text nodes or non empty elements
14167                                 walker = new TreeWalker(container, parentBlock);
14168
14169                                 // If caret is in beginning or end of a text block then jump to the next/previous node
14170                                 if (container.nodeType == 3) {
14171                                         if (start && offset === 0) {
14172                                                 walker.prev();
14173                                         } else if (!start && offset == container.nodeValue.length) {
14174                                                 walker.next();
14175                                         }
14176                                 }
14177
14178                                 while ((node = walker.current())) {
14179                                         if (node.nodeType === 1) {
14180                                                 // Ignore bogus elements
14181                                                 if (!node.getAttribute('data-mce-bogus')) {
14182                                                         // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
14183                                                         name = node.nodeName.toLowerCase();
14184                                                         if (nonEmptyElementsMap[name] && name !== 'br') {
14185                                                                 return false;
14186                                                         }
14187                                                 }
14188                                         } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
14189                                                 return false;
14190                                         }
14191
14192                                         if (start) {
14193                                                 walker.prev();
14194                                         } else {
14195                                                 walker.next();
14196                                         }
14197                                 }
14198
14199                                 return true;
14200                         }
14201
14202                         // Wraps any text nodes or inline elements in the specified forced root block name
14203                         function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
14204                                 var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
14205
14206                                 // Not in a block element or in a table cell or caption
14207                                 parentBlock = dom.getParent(container, dom.isBlock);
14208                                 rootBlockName = editor.getBody().nodeName.toLowerCase();
14209                                 if (!parentBlock || !canSplitBlock(parentBlock)) {
14210                                         parentBlock = parentBlock || editableRoot;
14211
14212                                         if (!parentBlock.hasChildNodes()) {
14213                                                 newBlock = dom.create(blockName);
14214                                                 parentBlock.appendChild(newBlock);
14215                                                 rng.setStart(newBlock, 0);
14216                                                 rng.setEnd(newBlock, 0);
14217                                                 return newBlock;
14218                                         }
14219
14220                                         // Find parent that is the first child of parentBlock
14221                                         node = container;
14222                                         while (node.parentNode != parentBlock) {
14223                                                 node = node.parentNode;
14224                                         }
14225
14226                                         // Loop left to find start node start wrapping at
14227                                         while (node && !dom.isBlock(node)) {
14228                                                 startNode = node;
14229                                                 node = node.previousSibling;
14230                                         }
14231
14232                                         if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
14233                                                 newBlock = dom.create(blockName);
14234                                                 startNode.parentNode.insertBefore(newBlock, startNode);
14235
14236                                                 // Start wrapping until we hit a block
14237                                                 node = startNode;
14238                                                 while (node && !dom.isBlock(node)) {
14239                                                         next = node.nextSibling;
14240                                                         newBlock.appendChild(node);
14241                                                         node = next;
14242                                                 }
14243
14244                                                 // Restore range to it's past location
14245                                                 rng.setStart(container, offset);
14246                                                 rng.setEnd(container, offset);
14247                                         }
14248                                 }
14249
14250                                 return container;
14251                         }
14252
14253                         // Inserts a block or br before/after or in the middle of a split list of the LI is empty
14254                         function handleEmptyListItem() {
14255                                 function isFirstOrLastLi(first) {
14256                                         var node = containerBlock[first ? 'firstChild' : 'lastChild'];
14257
14258                                         // Find first/last element since there might be whitespace there
14259                                         while (node) {
14260                                                 if (node.nodeType == 1) {
14261                                                         break;
14262                                                 }
14263
14264                                                 node = node[first ? 'nextSibling' : 'previousSibling'];
14265                                         }
14266
14267                                         return node === parentBlock;
14268                                 }
14269
14270                                 function getContainerBlock() {
14271                                         var containerBlockParent = containerBlock.parentNode;
14272
14273                                         if (containerBlockParent.nodeName == 'LI') {
14274                                                 return containerBlockParent;
14275                                         }
14276
14277                                         return containerBlock;
14278                                 }
14279
14280                                 // Check if we are in an nested list
14281                                 var containerBlockParentName = containerBlock.parentNode.nodeName;
14282                                 if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
14283                                         newBlockName = 'LI';
14284                                 }
14285
14286                                 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
14287
14288                                 if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
14289                                         if (containerBlockParentName == 'LI') {
14290                                                 // Nested list is inside a LI
14291                                                 dom.insertAfter(newBlock, getContainerBlock());
14292                                         } else {
14293                                                 // Is first and last list item then replace the OL/UL with a text block
14294                                                 dom.replace(newBlock, containerBlock);
14295                                         }
14296                                 } else if (isFirstOrLastLi(true)) {
14297                                         if (containerBlockParentName == 'LI') {
14298                                                 // List nested in an LI then move the list to a new sibling LI
14299                                                 dom.insertAfter(newBlock, getContainerBlock());
14300                                                 newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
14301                                                 newBlock.appendChild(containerBlock);
14302                                         } else {
14303                                                 // First LI in list then remove LI and add text block before list
14304                                                 containerBlock.parentNode.insertBefore(newBlock, containerBlock);
14305                                         }
14306                                 } else if (isFirstOrLastLi()) {
14307                                         // Last LI in list then remove LI and add text block after list
14308                                         dom.insertAfter(newBlock, getContainerBlock());
14309                                         renderBlockOnIE(newBlock);
14310                                 } else {
14311                                         // Middle LI in list the split the list and insert a text block in the middle
14312                                         // Extract after fragment and insert it after the current block
14313                                         containerBlock = getContainerBlock();
14314                                         tmpRng = rng.cloneRange();
14315                                         tmpRng.setStartAfter(parentBlock);
14316                                         tmpRng.setEndAfter(containerBlock);
14317                                         fragment = tmpRng.extractContents();
14318                                         dom.insertAfter(fragment, containerBlock);
14319                                         dom.insertAfter(newBlock, containerBlock);
14320                                 }
14321
14322                                 dom.remove(parentBlock);
14323                                 moveToCaretPosition(newBlock);
14324                                 undoManager.add();
14325                         }
14326
14327                         // Walks the parent block to the right and look for BR elements
14328                         function hasRightSideContent() {
14329                                 var walker = new TreeWalker(container, parentBlock), node;
14330
14331                                 while ((node = walker.next())) {
14332                                         if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
14333                                                 return true;
14334                                         }
14335                                 }
14336                         }
14337
14338                         // Inserts a BR element if the forced_root_block option is set to false or empty string
14339                         function insertBr() {
14340                                 var brElm, extraBr, marker;
14341
14342                                 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
14343                                         // Insert extra BR element at the end block elements
14344                                         if (!isIE && !hasRightSideContent()) {
14345                                                 brElm = dom.create('br');
14346                                                 rng.insertNode(brElm);
14347                                                 rng.setStartAfter(brElm);
14348                                                 rng.setEndAfter(brElm);
14349                                                 extraBr = true;
14350                                         }
14351                                 }
14352
14353                                 brElm = dom.create('br');
14354                                 rng.insertNode(brElm);
14355
14356                                 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
14357                                 if (isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
14358                                         brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
14359                                 }
14360
14361                                 // Insert temp marker and scroll to that
14362                                 marker = dom.create('span', {}, '&nbsp;');
14363                                 brElm.parentNode.insertBefore(marker, brElm);
14364                                 selection.scrollIntoView(marker);
14365                                 dom.remove(marker);
14366
14367                                 if (!extraBr) {
14368                                         rng.setStartAfter(brElm);
14369                                         rng.setEndAfter(brElm);
14370                                 } else {
14371                                         rng.setStartBefore(brElm);
14372                                         rng.setEndBefore(brElm);
14373                                 }
14374
14375                                 selection.setRng(rng);
14376                                 undoManager.add();
14377                         }
14378
14379                         // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
14380                         function trimLeadingLineBreaks(node) {
14381                                 do {
14382                                         if (node.nodeType === 3) {
14383                                                 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
14384                                         }
14385
14386                                         node = node.firstChild;
14387                                 } while (node);
14388                         }
14389
14390                         function getEditableRoot(node) {
14391                                 var root = dom.getRoot(), parent, editableRoot;
14392
14393                                 // Get all parents until we hit a non editable parent or the root
14394                                 parent = node;
14395                                 while (parent !== root && dom.getContentEditable(parent) !== "false") {
14396                                         if (dom.getContentEditable(parent) === "true") {
14397                                                 editableRoot = parent;
14398                                         }
14399
14400                                         parent = parent.parentNode;
14401                                 }
14402
14403                                 return parent !== root ? editableRoot : root;
14404                         }
14405
14406                         // Adds a BR at the end of blocks that only contains an IMG or INPUT since
14407                         // these might be floated and then they won't expand the block
14408                         function addBrToBlockIfNeeded(block) {
14409                                 var lastChild;
14410
14411                                 // IE will render the blocks correctly other browsers needs a BR
14412                                 if (!isIE) {
14413                                         block.normalize(); // Remove empty text nodes that got left behind by the extract
14414
14415                                         // Check if the block is empty or contains a floated last child
14416                                         lastChild = block.lastChild;
14417                                         if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
14418                                                 dom.add(block, 'br');
14419                                         }
14420                                 }
14421                         }
14422
14423                         // Delete any selected contents
14424                         if (!rng.collapsed) {
14425                                 editor.execCommand('Delete');
14426                                 return;
14427                         }
14428
14429                         // Event is blocked by some other handler for example the lists plugin
14430                         if (evt.isDefaultPrevented()) {
14431                                 return;
14432                         }
14433
14434                         // Setup range items and newBlockName
14435                         container = rng.startContainer;
14436                         offset = rng.startOffset;
14437                         newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
14438                         newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
14439                         documentMode = dom.doc.documentMode;
14440                         shiftKey = evt.shiftKey;
14441
14442                         // Resolve node index
14443                         if (container.nodeType == 1 && container.hasChildNodes()) {
14444                                 isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
14445                                 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
14446                                 if (isAfterLastNodeInContainer && container.nodeType == 3) {
14447                                         offset = container.nodeValue.length;
14448                                 } else {
14449                                         offset = 0;
14450                                 }
14451                         }
14452
14453                         // Get editable root node normaly the body element but sometimes a div or span
14454                         editableRoot = getEditableRoot(container);
14455
14456                         // If there is no editable root then enter is done inside a contentEditable false element
14457                         if (!editableRoot) {
14458                                 return;
14459                         }
14460
14461                         undoManager.beforeChange();
14462
14463                         // If editable root isn't block nor the root of the editor
14464                         if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
14465                                 if (!newBlockName || shiftKey) {
14466                                         insertBr();
14467                                 }
14468
14469                                 return;
14470                         }
14471
14472                         // Wrap the current node and it's sibling in a default block if it's needed.
14473                         // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
14474                         // This won't happen if root blocks are disabled or the shiftKey is pressed
14475                         if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
14476                                 container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
14477                         }
14478
14479                         // Find parent block and setup empty block paddings
14480                         parentBlock = dom.getParent(container, dom.isBlock);
14481                         containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
14482
14483                         // Setup block names
14484                         parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
14485                         containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
14486
14487                         // Enter inside block contained within a LI then split or insert before/after LI
14488                         if (containerBlockName == 'LI' && !evt.ctrlKey) {
14489                                 parentBlock = containerBlock;
14490                                 parentBlockName = containerBlockName;
14491                         }
14492
14493                         // Handle enter in LI
14494                         if (parentBlockName == 'LI') {
14495                                 if (!newBlockName && shiftKey) {
14496                                         insertBr();
14497                                         return;
14498                                 }
14499
14500                                 // Handle enter inside an empty list item
14501                                 if (dom.isEmpty(parentBlock)) {
14502                                         handleEmptyListItem();
14503                                         return;
14504                                 }
14505                         }
14506
14507                         // Don't split PRE tags but insert a BR instead easier when writing code samples etc
14508                         if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
14509                                 if (!shiftKey) {
14510                                         insertBr();
14511                                         return;
14512                                 }
14513                         } else {
14514                                 // If no root block is configured then insert a BR by default or if the shiftKey is pressed
14515                                 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
14516                                         insertBr();
14517                                         return;
14518                                 }
14519                         }
14520
14521                         // If parent block is root then never insert new blocks
14522                         if (newBlockName && parentBlock === editor.getBody()) {
14523                                 return;
14524                         }
14525
14526                         // Default block name if it's not configured
14527                         newBlockName = newBlockName || 'P';
14528
14529                         // Insert new block before/after the parent block depending on caret location
14530                         if (isCaretAtStartOrEndOfBlock()) {
14531                                 // 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
14532                                 if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
14533                                         newBlock = createNewBlock(newBlockName);
14534                                 } else {
14535                                         newBlock = createNewBlock();
14536                                 }
14537
14538                                 // Split the current container block element if enter is pressed inside an empty inner block element
14539                                 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
14540                                         // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
14541                                         newBlock = dom.split(containerBlock, parentBlock);
14542                                 } else {
14543                                         dom.insertAfter(newBlock, parentBlock);
14544                                 }
14545
14546                                 moveToCaretPosition(newBlock);
14547                         } else if (isCaretAtStartOrEndOfBlock(true)) {
14548                                 // Insert new block before
14549                                 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
14550                                 renderBlockOnIE(newBlock);
14551                         } else {
14552                                 // Extract after fragment and insert it after the current block
14553                                 tmpRng = rng.cloneRange();
14554                                 tmpRng.setEndAfter(parentBlock);
14555                                 fragment = tmpRng.extractContents();
14556                                 trimLeadingLineBreaks(fragment);
14557                                 newBlock = fragment.firstChild;
14558                                 dom.insertAfter(fragment, parentBlock);
14559                                 trimInlineElementsOnLeftSideOfBlock(newBlock);
14560                                 addBrToBlockIfNeeded(parentBlock);
14561                                 moveToCaretPosition(newBlock);
14562                         }
14563
14564                         dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
14565                         undoManager.add();
14566                 }
14567
14568                 editor.on('keydown', function(evt) {
14569                         if (evt.keyCode == 13) {
14570                                 if (handleEnterKey(evt) !== false) {
14571                                         evt.preventDefault();
14572                                 }
14573                         }
14574                 });
14575         };
14576 });
14577
14578 // Included from: js/tinymce/classes/ForceBlocks.js
14579
14580 /**
14581  * ForceBlocks.js
14582  *
14583  * Copyright, Moxiecode Systems AB
14584  * Released under LGPL License.
14585  *
14586  * License: http://www.tinymce.com/license
14587  * Contributing: http://www.tinymce.com/contributing
14588  */
14589
14590 define("tinymce/ForceBlocks", [], function() {
14591         return function(editor) {
14592                 var settings = editor.settings, dom = editor.dom, selection = editor.selection;
14593                 var schema = editor.schema, blockElements = schema.getBlockElements();
14594
14595                 function addRootBlocks() {
14596                         var node = selection.getStart(), rootNode = editor.getBody(), rng;
14597                         var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
14598                         var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
14599                         var tmpRng, rootNodeName, forcedRootBlock;
14600
14601                         forcedRootBlock = settings.forced_root_block;
14602
14603                         if (!node || node.nodeType !== 1 || !forcedRootBlock) {
14604                                 return;
14605                         }
14606
14607                         // Check if node is wrapped in block
14608                         while (node && node != rootNode) {
14609                                 if (blockElements[node.nodeName]) {
14610                                         return;
14611                                 }
14612
14613                                 node = node.parentNode;
14614                         }
14615
14616                         // Get current selection
14617                         rng = selection.getRng();
14618                         if (rng.setStart) {
14619                                 startContainer = rng.startContainer;
14620                                 startOffset = rng.startOffset;
14621                                 endContainer = rng.endContainer;
14622                                 endOffset = rng.endOffset;
14623
14624                                 try {
14625                                         restoreSelection = editor.getDoc().activeElement === rootNode;
14626                                 } catch (ex) {
14627                                         // IE throws unspecified error here sometimes
14628                                 }
14629                         } else {
14630                                 // Force control range into text range
14631                                 if (rng.item) {
14632                                         node = rng.item(0);
14633                                         rng = editor.getDoc().body.createTextRange();
14634                                         rng.moveToElementText(node);
14635                                 }
14636
14637                                 restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
14638                                 tmpRng = rng.duplicate();
14639                                 tmpRng.collapse(true);
14640                                 startOffset = tmpRng.move('character', offset) * -1;
14641
14642                                 if (!tmpRng.collapsed) {
14643                                         tmpRng = rng.duplicate();
14644                                         tmpRng.collapse(false);
14645                                         endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
14646                                 }
14647                         }
14648
14649                         // Wrap non block elements and text nodes
14650                         node = rootNode.firstChild;
14651                         rootNodeName = rootNode.nodeName.toLowerCase();
14652                         while (node) {
14653                                 // TODO: Break this up, too complex
14654                                 if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
14655                                         schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
14656                                         // Remove empty text nodes
14657                                         if (node.nodeType === 3 && node.nodeValue.length === 0) {
14658                                                 tempNode = node;
14659                                                 node = node.nextSibling;
14660                                                 dom.remove(tempNode);
14661                                                 continue;
14662                                         }
14663
14664                                         if (!rootBlockNode) {
14665                                                 rootBlockNode = dom.create(forcedRootBlock);
14666                                                 node.parentNode.insertBefore(rootBlockNode, node);
14667                                                 wrapped = true;
14668                                         }
14669
14670                                         tempNode = node;
14671                                         node = node.nextSibling;
14672                                         rootBlockNode.appendChild(tempNode);
14673                                 } else {
14674                                         rootBlockNode = null;
14675                                         node = node.nextSibling;
14676                                 }
14677                         }
14678
14679                         if (wrapped && restoreSelection) {
14680                                 if (rng.setStart) {
14681                                         rng.setStart(startContainer, startOffset);
14682                                         rng.setEnd(endContainer, endOffset);
14683                                         selection.setRng(rng);
14684                                 } else {
14685                                         // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
14686                                         try {
14687                                                 rng = editor.getDoc().body.createTextRange();
14688                                                 rng.moveToElementText(rootNode);
14689                                                 rng.collapse(true);
14690                                                 rng.moveStart('character', startOffset);
14691
14692                                                 if (endOffset > 0) {
14693                                                         rng.moveEnd('character', endOffset);
14694                                                 }
14695
14696                                                 rng.select();
14697                                         } catch (ex) {
14698                                                 // Ignore
14699                                         }
14700                                 }
14701
14702                                 editor.nodeChanged();
14703                         }
14704                 }
14705
14706                 // Force root blocks
14707                 if (settings.forced_root_block) {
14708                         editor.on('NodeChange', addRootBlocks);
14709                 }
14710         };
14711 });
14712
14713 // Included from: js/tinymce/classes/EditorCommands.js
14714
14715 /**
14716  * EditorCommands.js
14717  *
14718  * Copyright, Moxiecode Systems AB
14719  * Released under LGPL License.
14720  *
14721  * License: http://www.tinymce.com/license
14722  * Contributing: http://www.tinymce.com/contributing
14723  */
14724
14725 /**
14726  * This class enables you to add custom editor commands and it contains
14727  * overrides for native browser commands to address various bugs and issues.
14728  *
14729  * @class tinymce.EditorCommands
14730  */
14731 define("tinymce/EditorCommands", [
14732         "tinymce/html/Serializer",
14733         "tinymce/Env",
14734         "tinymce/util/Tools"
14735 ], function(Serializer, Env, Tools) {
14736         // Added for compression purposes
14737         var each = Tools.each, extend = Tools.extend;
14738         var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
14739         var isGecko = Env.gecko, isIE = Env.ie;
14740         var TRUE = true, FALSE = false;
14741
14742         return function(editor) {
14743                 var dom = editor.dom,
14744                         selection = editor.selection,
14745                         commands = {state: {}, exec: {}, value: {}},
14746                         settings = editor.settings,
14747                         formatter = editor.formatter,
14748                         bookmark;
14749
14750                 /**
14751                  * Executes the specified command.
14752                  *
14753                  * @method execCommand
14754                  * @param {String} command Command to execute.
14755                  * @param {Boolean} ui Optional user interface state.
14756                  * @param {Object} value Optional value for command.
14757                  * @return {Boolean} true/false if the command was found or not.
14758                  */
14759                 function execCommand(command, ui, value) {
14760                         var func;
14761
14762                         command = command.toLowerCase();
14763                         if ((func = commands.exec[command])) {
14764                                 func(command, ui, value);
14765                                 return TRUE;
14766                         }
14767
14768                         return FALSE;
14769                 }
14770
14771                 /**
14772                  * Queries the current state for a command for example if the current selection is "bold".
14773                  *
14774                  * @method queryCommandState
14775                  * @param {String} command Command to check the state of.
14776                  * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
14777                  */
14778                 function queryCommandState(command) {
14779                         var func;
14780
14781                         command = command.toLowerCase();
14782                         if ((func = commands.state[command])) {
14783                                 return func(command);
14784                         }
14785
14786                         return -1;
14787                 }
14788
14789                 /**
14790                  * Queries the command value for example the current fontsize.
14791                  *
14792                  * @method queryCommandValue
14793                  * @param {String} command Command to check the value of.
14794                  * @return {Object} Command value of false if it's not found.
14795                  */
14796                 function queryCommandValue(command) {
14797                         var func;
14798
14799                         command = command.toLowerCase();
14800                         if ((func = commands.value[command])) {
14801                                 return func(command);
14802                         }
14803
14804                         return FALSE;
14805                 }
14806
14807                 /**
14808                  * Adds commands to the command collection.
14809                  *
14810                  * @method addCommands
14811                  * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
14812                  * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
14813                  */
14814                 function addCommands(command_list, type) {
14815                         type = type || 'exec';
14816
14817                         each(command_list, function(callback, command) {
14818                                 each(command.toLowerCase().split(','), function(command) {
14819                                         commands[type][command] = callback;
14820                                 });
14821                         });
14822                 }
14823
14824                 // Expose public methods
14825                 extend(this, {
14826                         execCommand: execCommand,
14827                         queryCommandState: queryCommandState,
14828                         queryCommandValue: queryCommandValue,
14829                         addCommands: addCommands
14830                 });
14831
14832                 // Private methods
14833
14834                 function execNativeCommand(command, ui, value) {
14835                         if (ui === undefined) {
14836                                 ui = FALSE;
14837                         }
14838
14839                         if (value === undefined) {
14840                                 value = null;
14841                         }
14842
14843                         return editor.getDoc().execCommand(command, ui, value);
14844                 }
14845
14846                 function isFormatMatch(name) {
14847                         return formatter.match(name);
14848                 }
14849
14850                 function toggleFormat(name, value) {
14851                         formatter.toggle(name, value ? {value: value} : undefined);
14852                         editor.nodeChanged();
14853                 }
14854
14855                 function storeSelection(type) {
14856                         bookmark = selection.getBookmark(type);
14857                 }
14858
14859                 function restoreSelection() {
14860                         selection.moveToBookmark(bookmark);
14861                 }
14862
14863                 // Add execCommand overrides
14864                 addCommands({
14865                         // Ignore these, added for compatibility
14866                         'mceResetDesignMode,mceBeginUndoLevel': function() {},
14867
14868                         // Add undo manager logic
14869                         'mceEndUndoLevel,mceAddUndoLevel': function() {
14870                                 editor.undoManager.add();
14871                         },
14872
14873                         'Cut,Copy,Paste': function(command) {
14874                                 var doc = editor.getDoc(), failed;
14875
14876                                 // Try executing the native command
14877                                 try {
14878                                         execNativeCommand(command);
14879                                 } catch (ex) {
14880                                         // Command failed
14881                                         failed = TRUE;
14882                                 }
14883
14884                                 // Present alert message about clipboard access not being available
14885                                 if (failed || !doc.queryCommandSupported(command)) {
14886                                         editor.windowManager.alert(
14887                                                 "Your browser doesn't support direct access to the clipboard. " +
14888                                                 "Please use the Ctrl+X/C/V keyboard shortcuts instead."
14889                                         );
14890                                 }
14891                         },
14892
14893                         // Override unlink command
14894                         unlink: function(command) {
14895                                 if (selection.isCollapsed()) {
14896                                         selection.select(selection.getNode());
14897                                 }
14898
14899                                 execNativeCommand(command);
14900                                 selection.collapse(FALSE);
14901                         },
14902
14903                         // Override justify commands to use the text formatter engine
14904                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
14905                                 var align = command.substring(7);
14906
14907                                 if (align == 'full') {
14908                                         align = 'justify';
14909                                 }
14910
14911                                 // Remove all other alignments first
14912                                 each('left,center,right,justify'.split(','), function(name) {
14913                                         if (align != name) {
14914                                                 formatter.remove('align' + name);
14915                                         }
14916                                 });
14917
14918                                 toggleFormat('align' + align);
14919                                 execCommand('mceRepaint');
14920                         },
14921
14922                         // Override list commands to fix WebKit bug
14923                         'InsertUnorderedList,InsertOrderedList': function(command) {
14924                                 var listElm, listParent;
14925
14926                                 execNativeCommand(command);
14927
14928                                 // WebKit produces lists within block elements so we need to split them
14929                                 // we will replace the native list creation logic to custom logic later on
14930                                 // TODO: Remove this when the list creation logic is removed
14931                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
14932                                 if (listElm) {
14933                                         listParent = listElm.parentNode;
14934
14935                                         // If list is within a text block then split that block
14936                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
14937                                                 storeSelection();
14938                                                 dom.split(listParent, listElm);
14939                                                 restoreSelection();
14940                                         }
14941                                 }
14942                         },
14943
14944                         // Override commands to use the text formatter engine
14945                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
14946                                 toggleFormat(command);
14947                         },
14948
14949                         // Override commands to use the text formatter engine
14950                         'ForeColor,HiliteColor,FontName': function(command, ui, value) {
14951                                 toggleFormat(command, value);
14952                         },
14953
14954                         FontSize: function(command, ui, value) {
14955                                 var fontClasses, fontSizes;
14956
14957                                 // Convert font size 1-7 to styles
14958                                 if (value >= 1 && value <= 7) {
14959                                         fontSizes = explode(settings.font_size_style_values);
14960                                         fontClasses = explode(settings.font_size_classes);
14961
14962                                         if (fontClasses) {
14963                                                 value = fontClasses[value - 1] || value;
14964                                         } else {
14965                                                 value = fontSizes[value - 1] || value;
14966                                         }
14967                                 }
14968
14969                                 toggleFormat(command, value);
14970                         },
14971
14972                         RemoveFormat: function(command) {
14973                                 formatter.remove(command);
14974                         },
14975
14976                         mceBlockQuote: function() {
14977                                 toggleFormat('blockquote');
14978                         },
14979
14980                         FormatBlock: function(command, ui, value) {
14981                                 return toggleFormat(value || 'p');
14982                         },
14983
14984                         mceCleanup: function() {
14985                                 var bookmark = selection.getBookmark();
14986
14987                                 editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
14988
14989                                 selection.moveToBookmark(bookmark);
14990                         },
14991
14992                         mceRemoveNode: function(command, ui, value) {
14993                                 var node = value || selection.getNode();
14994
14995                                 // Make sure that the body node isn't removed
14996                                 if (node != editor.getBody()) {
14997                                         storeSelection();
14998                                         editor.dom.remove(node, TRUE);
14999                                         restoreSelection();
15000                                 }
15001                         },
15002
15003                         mceSelectNodeDepth: function(command, ui, value) {
15004                                 var counter = 0;
15005
15006                                 dom.getParent(selection.getNode(), function(node) {
15007                                         if (node.nodeType == 1 && counter++ == value) {
15008                                                 selection.select(node);
15009                                                 return FALSE;
15010                                         }
15011                                 }, editor.getBody());
15012                         },
15013
15014                         mceSelectNode: function(command, ui, value) {
15015                                 selection.select(value);
15016                         },
15017
15018                         mceInsertContent: function(command, ui, value) {
15019                                 var parser, serializer, parentNode, rootNode, fragment, args;
15020                                 var marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
15021
15022                                 function trimOrPaddLeftRight(html) {
15023                                         var rng, container, offset;
15024
15025                                         rng = selection.getRng(true);
15026                                         container = rng.startContainer;
15027                                         offset = rng.startOffset;
15028
15029                                         function hasSiblingText(siblingName) {
15030                                                 return container[siblingName] && container[siblingName].nodeType == 3;
15031                                         }
15032
15033                                         if (container.nodeType == 3) {
15034                                                 if (offset > 0) {
15035                                                         html = html.replace(/^&nbsp;/, ' ');
15036                                                 } else if (!hasSiblingText('previousSibling')) {
15037                                                         html = html.replace(/^ /, '&nbsp;');
15038                                                 }
15039
15040                                                 if (offset < container.length) {
15041                                                         html = html.replace(/&nbsp;(<br>|)$/, ' ');
15042                                                 } else if (!hasSiblingText('nextSibling')) {
15043                                                         html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
15044                                                 }
15045                                         }
15046
15047                                         return html;
15048                                 }
15049
15050                                 // Check for whitespace before/after value
15051                                 if (/^ | $/.test(value)) {
15052                                         value = trimOrPaddLeftRight(value);
15053                                 }
15054
15055                                 // Setup parser and serializer
15056                                 parser = editor.parser;
15057                                 serializer = new Serializer({}, editor.schema);
15058                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;</span>';
15059
15060                                 // Run beforeSetContent handlers on the HTML to be inserted
15061                                 args = {content: value, format: 'html', selection: true};
15062                                 editor.fire('BeforeSetContent', args);
15063                                 value = args.content;
15064
15065                                 // Add caret at end of contents if it's missing
15066                                 if (value.indexOf('{$caret}') == -1) {
15067                                         value += '{$caret}';
15068                                 }
15069
15070                                 // Replace the caret marker with a span bookmark element
15071                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);
15072
15073                                 // Insert node maker where we will insert the new HTML and get it's parent
15074                                 if (!selection.isCollapsed()) {
15075                                         editor.getDoc().execCommand('Delete', false, null);
15076                                 }
15077
15078                                 parentNode = selection.getNode();
15079
15080                                 // Parse the fragment within the context of the parent node
15081                                 args = {context: parentNode.nodeName.toLowerCase()};
15082                                 fragment = parser.parse(value, args);
15083
15084                                 // Move the caret to a more suitable location
15085                                 node = fragment.lastChild;
15086                                 if (node.attr('id') == 'mce_marker') {
15087                                         marker = node;
15088
15089                                         for (node = node.prev; node; node = node.walk(true)) {
15090                                                 if (node.type == 3 || !dom.isBlock(node.name)) {
15091                                                         node.parent.insert(marker, node, node.name === 'br');
15092                                                         break;
15093                                                 }
15094                                         }
15095                                 }
15096
15097                                 // If parser says valid we can insert the contents into that parent
15098                                 if (!args.invalid) {
15099                                         value = serializer.serialize(fragment);
15100
15101                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent
15102                                         node = parentNode.firstChild;
15103                                         node2 = parentNode.lastChild;
15104                                         if (!node || (node === node2 && node.nodeName === 'BR')) {
15105                                                 dom.setHTML(parentNode, value);
15106                                         } else {
15107                                                 selection.setContent(value);
15108                                         }
15109                                 } else {
15110                                         // If the fragment was invalid within that context then we need
15111                                         // to parse and process the parent it's inserted into
15112
15113                                         // Insert bookmark node and get the parent
15114                                         selection.setContent(bookmarkHtml);
15115                                         parentNode = selection.getNode();
15116                                         rootNode = editor.getBody();
15117
15118                                         // Opera will return the document node when selection is in root
15119                                         if (parentNode.nodeType == 9) {
15120                                                 parentNode = node = rootNode;
15121                                         } else {
15122                                                 node = parentNode;
15123                                         }
15124
15125                                         // Find the ancestor just before the root element
15126                                         while (node !== rootNode) {
15127                                                 parentNode = node;
15128                                                 node = node.parentNode;
15129                                         }
15130
15131                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15132                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15133                                         value = serializer.serialize(
15134                                                 parser.parse(
15135                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem
15136                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15137                                                                 return serializer.serialize(fragment);
15138                                                         })
15139                                                 )
15140                                         );
15141
15142                                         // Set the inner/outer HTML depending on if we are in the root or not
15143                                         if (parentNode == rootNode) {
15144                                                 dom.setHTML(rootNode, value);
15145                                         } else {
15146                                                 dom.setOuterHTML(parentNode, value);
15147                                         }
15148                                 }
15149
15150                                 marker = dom.get('mce_marker');
15151
15152                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15153                                 nodeRect = dom.getRect(marker);
15154                                 viewPortRect = dom.getViewPort(editor.getWin());
15155
15156                                 // Check if node is out side the viewport if it is then scroll to it
15157                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15158                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15159                                         viewportBodyElement = isIE ? editor.getDoc().documentElement : editor.getBody();
15160                                         viewportBodyElement.scrollLeft = nodeRect.x;
15161                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15162                                 }
15163
15164                                 // Move selection before marker and remove it
15165                                 rng = dom.createRng();
15166
15167                                 // If previous sibling is a text node set the selection to the end of that node
15168                                 node = marker.previousSibling;
15169                                 if (node && node.nodeType == 3) {
15170                                         rng.setStart(node, node.nodeValue.length);
15171
15172                                         // TODO: Why can't we normalize on IE
15173                                         if (!isIE) {
15174                                                 node2 = marker.nextSibling;
15175                                                 if (node2 && node2.nodeType == 3) {
15176                                                         node.appendData(node2.data);
15177                                                         node2.parentNode.removeChild(node2);
15178                                                 }
15179                                         }
15180                                 } else {
15181                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15182                                         rng.setStartBefore(marker);
15183                                         rng.setEndBefore(marker);
15184                                 }
15185
15186                                 // Remove the marker node and set the new range
15187                                 dom.remove(marker);
15188                                 selection.setRng(rng);
15189
15190                                 // Dispatch after event and add any visual elements needed
15191                                 editor.fire('SetContent', args);
15192                                 editor.addVisual();
15193                         },
15194
15195                         mceInsertRawHTML: function(command, ui, value) {
15196                                 selection.setContent('tiny_mce_marker');
15197                                 editor.setContent(
15198                                         editor.getContent().replace(/tiny_mce_marker/g, function() {
15199                                                 return value;
15200                                         })
15201                                 );
15202                         },
15203
15204                         mceToggleFormat: function(command, ui, value) {
15205                                 toggleFormat(value);
15206                         },
15207
15208                         mceSetContent: function(command, ui, value) {
15209                                 editor.setContent(value);
15210                         },
15211
15212                         'Indent,Outdent': function(command) {
15213                                 var intentValue, indentUnit, value;
15214
15215                                 // Setup indent level
15216                                 intentValue = settings.indentation;
15217                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
15218                                 intentValue = parseInt(intentValue, 10);
15219
15220                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15221                                         // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15222                                         if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15223                                                 formatter.apply('div');
15224                                         }
15225
15226                                         each(selection.getSelectedBlocks(), function(element) {
15227                                                 var indentStyleName;
15228
15229                                                 if (element.nodeName != "LI") {
15230                                                         indentStyleName = dom.getStyle(element, 'direction', true) == 'rtl' ? 'paddingRight' : 'paddingLeft';
15231
15232                                                         if (command == 'outdent') {
15233                                                                 value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
15234                                                                 dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
15235                                                         } else {
15236                                                                 value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
15237                                                                 dom.setStyle(element, indentStyleName, value);
15238                                                         }
15239                                                 }
15240                                         });
15241                                 } else {
15242                                         execNativeCommand(command);
15243                                 }
15244                         },
15245
15246                         mceRepaint: function() {
15247                                 if (isGecko) {
15248                                         try {
15249                                                 storeSelection(TRUE);
15250
15251                                                 if (selection.getSel()) {
15252                                                         selection.getSel().selectAllChildren(editor.getBody());
15253                                                 }
15254
15255                                                 selection.collapse(TRUE);
15256                                                 restoreSelection();
15257                                         } catch (ex) {
15258                                                 // Ignore
15259                                         }
15260                                 }
15261                         },
15262
15263                         InsertHorizontalRule: function() {
15264                                 editor.execCommand('mceInsertContent', false, '<hr />');
15265                         },
15266
15267                         mceToggleVisualAid: function() {
15268                                 editor.hasVisual = !editor.hasVisual;
15269                                 editor.addVisual();
15270                         },
15271
15272                         mceReplaceContent: function(command, ui, value) {
15273                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
15274                         },
15275
15276                         mceInsertLink: function(command, ui, value) {
15277                                 var anchor;
15278
15279                                 if (typeof(value) == 'string') {
15280                                         value = {href: value};
15281                                 }
15282
15283                                 anchor = dom.getParent(selection.getNode(), 'a');
15284
15285                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15286                                 value.href = value.href.replace(' ', '%20');
15287
15288                                 // Remove existing links if there could be child links or that the href isn't specified
15289                                 if (!anchor || !value.href) {
15290                                         formatter.remove('link');
15291                                 }
15292
15293                                 // Apply new link to selection
15294                                 if (value.href) {
15295                                         formatter.apply('link', value, anchor);
15296                                 }
15297                         },
15298
15299                         selectAll: function() {
15300                                 var root = dom.getRoot(), rng = dom.createRng();
15301
15302                                 // Old IE does a better job with selectall than new versions
15303                                 if (selection.getRng().setStart) {
15304                                         rng.setStart(root, 0);
15305                                         rng.setEnd(root, root.childNodes.length);
15306
15307                                         selection.setRng(rng);
15308                                 } else {
15309                                         execNativeCommand('SelectAll');
15310                                 }
15311                         },
15312
15313                         mceNewDocument: function() {
15314                                 editor.setContent('');
15315                         }
15316                 });
15317
15318                 // Add queryCommandState overrides
15319                 addCommands({
15320                         // Override justify commands
15321                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
15322                                 var name = 'align' + command.substring(7);
15323                                 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15324                                 var matches = map(nodes, function(node) {
15325                                         return !!formatter.matchNode(node, name);
15326                                 });
15327                                 return inArray(matches, TRUE) !== -1;
15328                         },
15329
15330                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
15331                                 return isFormatMatch(command);
15332                         },
15333
15334                         mceBlockQuote: function() {
15335                                 return isFormatMatch('blockquote');
15336                         },
15337
15338                         Outdent: function() {
15339                                 var node;
15340
15341                                 if (settings.inline_styles) {
15342                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
15343                                                 return TRUE;
15344                                         }
15345
15346                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
15347                                                 return TRUE;
15348                                         }
15349                                 }
15350
15351                                 return (
15352                                         queryCommandState('InsertUnorderedList') ||
15353                                         queryCommandState('InsertOrderedList') ||
15354                                         (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
15355                                 );
15356                         },
15357
15358                         'InsertUnorderedList,InsertOrderedList': function(command) {
15359                                 var list = dom.getParent(selection.getNode(), 'ul,ol');
15360
15361                                 return list &&
15362                                         (
15363                                                 command === 'insertunorderedlist' && list.tagName === 'UL' ||
15364                                                 command === 'insertorderedlist' && list.tagName === 'OL'
15365                                         );
15366                         }
15367                 }, 'state');
15368
15369                 // Add queryCommandValue overrides
15370                 addCommands({
15371                         'FontSize,FontName': function(command) {
15372                                 var value = 0, parent;
15373
15374                                 if ((parent = dom.getParent(selection.getNode(), 'span'))) {
15375                                         if (command == 'fontsize') {
15376                                                 value = parent.style.fontSize;
15377                                         } else {
15378                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15379                                         }
15380                                 }
15381
15382                                 return value;
15383                         }
15384                 }, 'value');
15385
15386                 // Add undo manager logic
15387                 addCommands({
15388                         Undo: function() {
15389                                 editor.undoManager.undo();
15390                         },
15391
15392                         Redo: function() {
15393                                 editor.undoManager.redo();
15394                         }
15395                 });
15396         };
15397 });
15398
15399 // Included from: js/tinymce/classes/util/URI.js
15400
15401 /**
15402  * URI.js
15403  *
15404  * Copyright, Moxiecode Systems AB
15405  * Released under LGPL License.
15406  *
15407  * License: http://www.tinymce.com/license
15408  * Contributing: http://www.tinymce.com/contributing
15409  */
15410
15411 /**
15412  * This class handles parsing, modification and serialization of URI/URL strings.
15413  * @class tinymce.util.URI
15414  */
15415 define("tinymce/util/URI", [
15416         "tinymce/util/Tools"
15417 ], function(Tools) {
15418         var each = Tools.each, trim = Tools.trim;
15419
15420         /**
15421          * Constructs a new URI instance.
15422          *
15423          * @constructor
15424          * @method URI
15425          * @param {String} url URI string to parse.
15426          * @param {Object} settings Optional settings object.
15427          */
15428         function URI(url, settings) {
15429                 var self = this, baseUri, base_url;
15430
15431                 // Trim whitespace
15432                 url = trim(url);
15433
15434                 // Default settings
15435                 settings = self.settings = settings || {};
15436
15437                 // Strange app protocol that isn't http/https or local anchor
15438                 // For example: mailto,skype,tel etc.
15439                 if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
15440                         self.source = url;
15441                         return;
15442                 }
15443
15444                 // Absolute path with no host, fake host and protocol
15445                 if (url.indexOf('/') === 0 && url.indexOf('//') !== 0) {
15446                         url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url;
15447                 }
15448
15449                 // Relative path http:// or protocol relative //path
15450                 if (!/^[\w\-]*:?\/\//.test(url)) {
15451                         base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
15452                         url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url);
15453                 }
15454
15455                 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
15456                 url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
15457
15458                 /*jshint maxlen: 255 */
15459                 url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
15460
15461                 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
15462                         var part = url[i];
15463
15464                         // Zope 3 workaround, they use @@something
15465                         if (part) {
15466                                 part = part.replace(/\(mce_at\)/g, '@@');
15467                         }
15468
15469                         self[v] = part;
15470                 });
15471
15472                 baseUri = settings.base_uri;
15473                 if (baseUri) {
15474                         if (!self.protocol) {
15475                                 self.protocol = baseUri.protocol;
15476                         }
15477
15478                         if (!self.userInfo) {
15479                                 self.userInfo = baseUri.userInfo;
15480                         }
15481
15482                         if (!self.port && self.host === 'mce_host') {
15483                                 self.port = baseUri.port;
15484                         }
15485
15486                         if (!self.host || self.host === 'mce_host') {
15487                                 self.host = baseUri.host;
15488                         }
15489
15490                         self.source = '';
15491                 }
15492
15493                 //t.path = t.path || '/';
15494         }
15495
15496         URI.prototype = {
15497                 /**
15498                  * Sets the internal path part of the URI.
15499                  *
15500                  * @method setPath
15501                  * @param {string} path Path string to set.
15502                  */
15503                 setPath: function(path) {
15504                         var self = this;
15505
15506                         path = /^(.*?)\/?(\w+)?$/.exec(path);
15507
15508                         // Update path parts
15509                         self.path = path[0];
15510                         self.directory = path[1];
15511                         self.file = path[2];
15512
15513                         // Rebuild source
15514                         self.source = '';
15515                         self.getURI();
15516                 },
15517
15518                 /**
15519                  * Converts the specified URI into a relative URI based on the current URI instance location.
15520                  *
15521                  * @method toRelative
15522                  * @param {String} uri URI to convert into a relative path/URI.
15523                  * @return {String} Relative URI from the point specified in the current URI instance.
15524                  * @example
15525                  * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
15526                  * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
15527                  */
15528                 toRelative: function(uri) {
15529                         var self = this, output;
15530
15531                         if (uri === "./") {
15532                                 return uri;
15533                         }
15534
15535                         uri = new URI(uri, {base_uri: self});
15536
15537                         // Not on same domain/port or protocol
15538                         if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || self.protocol != uri.protocol) {
15539                                 return uri.getURI();
15540                         }
15541
15542                         var tu = self.getURI(), uu = uri.getURI();
15543
15544                         // Allow usage of the base_uri when relative_urls = true
15545                         if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
15546                                 return tu;
15547                         }
15548
15549                         output = self.toRelPath(self.path, uri.path);
15550
15551                         // Add query
15552                         if (uri.query) {
15553                                 output += '?' + uri.query;
15554                         }
15555
15556                         // Add anchor
15557                         if (uri.anchor) {
15558                                 output += '#' + uri.anchor;
15559                         }
15560
15561                         return output;
15562                 },
15563
15564                 /**
15565                  * Converts the specified URI into a absolute URI based on the current URI instance location.
15566                  *
15567                  * @method toAbsolute
15568                  * @param {String} uri URI to convert into a relative path/URI.
15569                  * @param {Boolean} noHost No host and protocol prefix.
15570                  * @return {String} Absolute URI from the point specified in the current URI instance.
15571                  * @example
15572                  * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
15573                  * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
15574                  */
15575                 toAbsolute: function(uri, noHost) {
15576                         uri = new URI(uri, {base_uri: this});
15577
15578                         return uri.getURI(this.host == uri.host && this.protocol == uri.protocol ? noHost : 0);
15579                 },
15580
15581                 /**
15582                  * Converts a absolute path into a relative path.
15583                  *
15584                  * @method toRelPath
15585                  * @param {String} base Base point to convert the path from.
15586                  * @param {String} path Absolute path to convert into a relative path.
15587                  */
15588                 toRelPath: function(base, path) {
15589                         var items, breakPoint = 0, out = '', i, l;
15590
15591                         // Split the paths
15592                         base = base.substring(0, base.lastIndexOf('/'));
15593                         base = base.split('/');
15594                         items = path.split('/');
15595
15596                         if (base.length >= items.length) {
15597                                 for (i = 0, l = base.length; i < l; i++) {
15598                                         if (i >= items.length || base[i] != items[i]) {
15599                                                 breakPoint = i + 1;
15600                                                 break;
15601                                         }
15602                                 }
15603                         }
15604
15605                         if (base.length < items.length) {
15606                                 for (i = 0, l = items.length; i < l; i++) {
15607                                         if (i >= base.length || base[i] != items[i]) {
15608                                                 breakPoint = i + 1;
15609                                                 break;
15610                                         }
15611                                 }
15612                         }
15613
15614                         if (breakPoint === 1) {
15615                                 return path;
15616                         }
15617
15618                         for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
15619                                 out += "../";
15620                         }
15621
15622                         for (i = breakPoint - 1, l = items.length; i < l; i++) {
15623                                 if (i != breakPoint - 1) {
15624                                         out += "/" + items[i];
15625                                 } else {
15626                                         out += items[i];
15627                                 }
15628                         }
15629
15630                         return out;
15631                 },
15632
15633                 /**
15634                  * Converts a relative path into a absolute path.
15635                  *
15636                  * @method toAbsPath
15637                  * @param {String} base Base point to convert the path from.
15638                  * @param {String} path Relative path to convert into an absolute path.
15639                  */
15640                 toAbsPath: function(base, path) {
15641                         var i, nb = 0, o = [], tr, outPath;
15642
15643                         // Split paths
15644                         tr = /\/$/.test(path) ? '/' : '';
15645                         base = base.split('/');
15646                         path = path.split('/');
15647
15648                         // Remove empty chunks
15649                         each(base, function(k) {
15650                                 if (k) {
15651                                         o.push(k);
15652                                 }
15653                         });
15654
15655                         base = o;
15656
15657                         // Merge relURLParts chunks
15658                         for (i = path.length - 1, o = []; i >= 0; i--) {
15659                                 // Ignore empty or .
15660                                 if (path[i].length === 0 || path[i] === ".") {
15661                                         continue;
15662                                 }
15663
15664                                 // Is parent
15665                                 if (path[i] === '..') {
15666                                         nb++;
15667                                         continue;
15668                                 }
15669
15670                                 // Move up
15671                                 if (nb > 0) {
15672                                         nb--;
15673                                         continue;
15674                                 }
15675
15676                                 o.push(path[i]);
15677                         }
15678
15679                         i = base.length - nb;
15680
15681                         // If /a/b/c or /
15682                         if (i <= 0) {
15683                                 outPath = o.reverse().join('/');
15684                         } else {
15685                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
15686                         }
15687
15688                         // Add front / if it's needed
15689                         if (outPath.indexOf('/') !== 0) {
15690                                 outPath = '/' + outPath;
15691                         }
15692
15693                         // Add traling / if it's needed
15694                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
15695                                 outPath += tr;
15696                         }
15697
15698                         return outPath;
15699                 },
15700
15701                 /**
15702                  * Returns the full URI of the internal structure.
15703                  *
15704                  * @method getURI
15705                  * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
15706                  */
15707                 getURI: function(noProtoHost) {
15708                         var s, self = this;
15709
15710                         // Rebuild source
15711                         if (!self.source || noProtoHost) {
15712                                 s = '';
15713
15714                                 if (!noProtoHost) {
15715                                         if (self.protocol) {
15716                                                 s += self.protocol + '://';
15717                                         }
15718
15719                                         if (self.userInfo) {
15720                                                 s += self.userInfo + '@';
15721                                         }
15722
15723                                         if (self.host) {
15724                                                 s += self.host;
15725                                         }
15726
15727                                         if (self.port) {
15728                                                 s += ':' + self.port;
15729                                         }
15730                                 }
15731
15732                                 if (self.path) {
15733                                         s += self.path;
15734                                 }
15735
15736                                 if (self.query) {
15737                                         s += '?' + self.query;
15738                                 }
15739
15740                                 if (self.anchor) {
15741                                         s += '#' + self.anchor;
15742                                 }
15743
15744                                 self.source = s;
15745                         }
15746
15747                         return self.source;
15748                 }
15749         };
15750
15751         return URI;
15752 });
15753
15754 // Included from: js/tinymce/classes/util/Class.js
15755
15756 /**
15757  * Class.js
15758  *
15759  * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved.
15760  */
15761
15762 /**
15763  * This utilitiy class is used for easier inheritage.
15764  *
15765  * Features:
15766  * * Exposed super functions: this._super();
15767  * * Mixins
15768  * * Dummy functions
15769  * * Property functions: var value = object.value(); and object.value(newValue);
15770  * * Static functions
15771  * * Defaults settings
15772  */
15773 define("tinymce/util/Class", [
15774         "tinymce/util/Tools"
15775 ], function(Tools) {
15776         var each = Tools.each, extend = Tools.extend;
15777
15778         var extendClass, initializing;
15779
15780         function Class() {
15781         }
15782
15783         // Provides classical inheritance, based on code made by John Resig
15784         Class.extend = extendClass = function(prop) {
15785                 var Self = this, _super = Self.prototype, prototype, name, member;
15786
15787                 // The dummy class constructor
15788                 function Class() {
15789                         var i, mixins, mixin, self;
15790
15791                         // All construction is actually done in the init method
15792                         if (!initializing) {
15793                                 self = this;
15794
15795                                 // Run class constuctor
15796                                 if (self.init) {
15797                                         self.init.apply(self, arguments);
15798                                 }
15799
15800                                 // Run mixin constructors
15801                                 mixins = self.Mixins;
15802                                 if (mixins) {
15803                                         i = mixins.length;
15804                                         while (i--) {
15805                                                 mixin = mixins[i];
15806                                                 if (mixin.init) {
15807                                                         mixin.init.apply(self, arguments);
15808                                                 }
15809                                         }
15810                                 }
15811                         }
15812                 }
15813
15814                 // Dummy function, needs to be extended in order to provide functionality
15815                 function dummy() {
15816                         return this;
15817                 }
15818
15819                 // Creates a overloaded method for the class
15820                 // this enables you to use this._super(); to call the super function
15821                 function createMethod(name, fn) {
15822                         return function(){
15823                                 var self = this, tmp = self._super, ret;
15824
15825                                 self._super = _super[name];
15826                                 ret = fn.apply(self, arguments);
15827                                 self._super = tmp;
15828
15829                                 return ret;
15830                         };
15831                 }
15832
15833                 // Instantiate a base class (but only create the instance,
15834                 // don't run the init constructor)
15835                 initializing = true;
15836                 prototype = new Self();
15837                 initializing = false;
15838
15839                 // Add mixins
15840                 if (prop.Mixins) {
15841                         each(prop.Mixins, function(mixin) {
15842                                 mixin = mixin;
15843
15844                                 for (var name in mixin) {
15845                                         if (name !== "init") {
15846                                                 prop[name] = mixin[name];
15847                                         }
15848                                 }
15849                         });
15850
15851                         if (_super.Mixins) {
15852                                 prop.Mixins = _super.Mixins.concat(prop.Mixins);
15853                         }
15854                 }
15855
15856                 // Generate dummy methods
15857                 if (prop.Methods) {
15858                         each(prop.Methods.split(','), function(name) {
15859                                 prop[name] = dummy;
15860                         });
15861                 }
15862
15863                 // Generate property methods
15864                 if (prop.Properties) {
15865                         each(prop.Properties.split(','), function(name) {
15866                                 var fieldName = '_' + name;
15867
15868                                 prop[name] = function(value) {
15869                                         var self = this, undef;
15870
15871                                         // Set value
15872                                         if (value !== undef) {
15873                                                 self[fieldName] = value;
15874
15875                                                 return self;
15876                                         }
15877
15878                                         // Get value
15879                                         return self[fieldName];
15880                                 };
15881                         });
15882                 }
15883
15884                 // Static functions
15885                 if (prop.Statics) {
15886                         each(prop.Statics, function(func, name) {
15887                                 Class[name] = func;
15888                         });
15889                 }
15890
15891                 // Default settings
15892                 if (prop.Defaults && _super.Defaults) {
15893                         prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
15894                 }
15895
15896                 // Copy the properties over onto the new prototype
15897                 for (name in prop) {
15898                         member = prop[name];
15899
15900                         if (typeof member == "function" && _super[name]) {
15901                                 prototype[name] = createMethod(name, member);
15902                         } else {
15903                                 prototype[name] = member;
15904                         }
15905                 }
15906
15907                 // Populate our constructed prototype object
15908                 Class.prototype = prototype;
15909
15910                 // Enforce the constructor to be what we expect
15911                 Class.constructor = Class;
15912
15913                 // And make this class extendible
15914                 Class.extend = extendClass;
15915
15916                 return Class;
15917         };
15918
15919         return Class;
15920 });
15921
15922 // Included from: js/tinymce/classes/ui/Selector.js
15923
15924 /**
15925  * Selector.js
15926  *
15927  * Copyright, Moxiecode Systems AB
15928  * Released under LGPL License.
15929  *
15930  * License: http://www.tinymce.com/license
15931  * Contributing: http://www.tinymce.com/contributing
15932  */
15933
15934 /**
15935  * Selector engine, enables you to select controls by using CSS like expressions.
15936  * We currently only support basic CSS expressions to reduce the size of the core
15937  * and the ones we support should be enough for most cases.
15938  *
15939  * @example
15940  * Supported expressions:
15941  *  element
15942  *  element#name
15943  *  element.class
15944  *  element[attr]
15945  *  element[attr*=value]
15946  *  element[attr~=value]
15947  *  element[attr!=value]
15948  *  element[attr^=value]
15949  *  element[attr$=value]
15950  *  element:<state>
15951  *  element:not(<expression>)
15952  *  element:first
15953  *  element:last
15954  *  element:odd
15955  *  element:even
15956  *  element element
15957  *  element > element
15958  *
15959  * @class tinymce.ui.Selector
15960  */
15961 define("tinymce/ui/Selector", [
15962         "tinymce/util/Class",
15963         "tinymce/util/Tools"
15964 ], function(Class, Tools) {
15965         "use strict";
15966
15967         /**
15968          * Produces an array with a unique set of objects. It will not compare the values
15969          * but the references of the objects.
15970          *
15971          * @private
15972          * @method unqiue
15973          * @param {Array} array Array to make into an array with unique items.
15974          * @return {Array} Array with unique items.
15975          */
15976         function unique(array) {
15977                 var uniqueItems = [], i = array.length, item;
15978
15979                 while (i--) {
15980                         item = array[i];
15981
15982                         if (!item.__checked) {
15983                                 uniqueItems.push(item);
15984                                 item.__checked = 1;
15985                         }
15986                 }
15987
15988                 i = uniqueItems.length;
15989                 while (i--) {
15990                         delete uniqueItems[i].__checked;
15991                 }
15992
15993                 return uniqueItems;
15994         }
15995
15996         var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
15997
15998         /*jshint maxlen:255 */
15999         var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
16000                 whiteSpace = /^\s*|\s*$/g,
16001                 Collection;
16002
16003         var Selector = Class.extend({
16004                 /**
16005                  * Constructs a new Selector instance.
16006                  *
16007                  * @constructor
16008                  * @method init
16009                  * @param {String} selector CSS like selector expression.
16010                  */
16011                 init: function(selector) {
16012                         var match = this.match;
16013
16014                         function compileNameFilter(name) {
16015                                 if (name) {
16016                                         name = name.toLowerCase();
16017
16018                                         return function(item) {
16019                                                 return name === '*' || item.type === name;
16020                                         };
16021                                 }
16022                         }
16023
16024                         function compileIdFilter(id) {
16025                                 if (id) {
16026                                         return function(item) {
16027                                                 return item._name === id;
16028                                         };
16029                                 }
16030                         }
16031
16032                         function compileClassesFilter(classes) {
16033                                 if (classes) {
16034                                         classes = classes.split('.');
16035
16036                                         return function(item) {
16037                                                 var i = classes.length;
16038
16039                                                 while (i--) {
16040                                                         if (!item.hasClass(classes[i])) {
16041                                                                 return false;
16042                                                         }
16043                                                 }
16044
16045                                                 return true;
16046                                         };
16047                                 }
16048                         }
16049
16050                         function compileAttrFilter(name, cmp, check) {
16051                                 if (name) {
16052                                         return function(item) {
16053                                                 var value = item[name] ? item[name]() : '';
16054
16055                                                 return !cmp ? !!check :
16056                                                         cmp === "=" ? value === check :
16057                                                         cmp === "*=" ? value.indexOf(check) >= 0 :
16058                                                         cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
16059                                                         cmp === "!=" ? value != check :
16060                                                         cmp === "^=" ? value.indexOf(check) === 0 :
16061                                                         cmp === "$=" ? value.substr(value.length - check.length) === check :
16062                                                         false;
16063                                         };
16064                                 }
16065                         }
16066
16067                         function compilePsuedoFilter(name) {
16068                                 var notSelectors;
16069
16070                                 if (name) {
16071                                         name = /(?:not\((.+)\))|(.+)/i.exec(name);
16072
16073                                         if (!name[1]) {
16074                                                 name = name[2];
16075
16076                                                 return function(item, index, length) {
16077                                                         return name === 'first' ? index === 0 :
16078                                                                 name === 'last' ? index === length - 1 :
16079                                                                 name === 'even' ? index % 2 === 0 :
16080                                                                 name === 'odd' ? index % 2 === 1 :
16081                                                                 item[name] ? item[name]() :
16082                                                                 false;
16083                                                 };
16084                                         } else {
16085                                                 // Compile not expression
16086                                                 notSelectors = parseChunks(name[1], []);
16087
16088                                                 return function(item) {
16089                                                         return !match(item, notSelectors);
16090                                                 };
16091                                         }
16092                                 }
16093                         }
16094
16095                         function compile(selector, filters, direct) {
16096                                 var parts;
16097
16098                                 function add(filter) {
16099                                         if (filter) {
16100                                                 filters.push(filter);
16101                                         }
16102                                 }
16103
16104                                 // Parse expression into parts
16105                                 parts = expression.exec(selector.replace(whiteSpace, ''));
16106
16107                                 add(compileNameFilter(parts[1]));
16108                                 add(compileIdFilter(parts[2]));
16109                                 add(compileClassesFilter(parts[3]));
16110                                 add(compileAttrFilter(parts[4], parts[5], parts[6]));
16111                                 add(compilePsuedoFilter(parts[7]));
16112
16113                                 // Mark the filter with psuedo for performance
16114                                 filters.psuedo = !!parts[7];
16115                                 filters.direct = direct;
16116
16117                                 return filters;
16118                         }
16119
16120                         // Parser logic based on Sizzle by John Resig
16121                         function parseChunks(selector, selectors) {
16122                                 var parts = [], extra, matches, i;
16123
16124                                 do {
16125                                         chunker.exec("");
16126                                         matches = chunker.exec(selector);
16127
16128                                         if (matches) {
16129                                                 selector = matches[3];
16130                                                 parts.push(matches[1]);
16131
16132                                                 if (matches[2]) {
16133                                                         extra = matches[3];
16134                                                         break;
16135                                                 }
16136                                         }
16137                                 } while (matches);
16138
16139                                 if (extra) {
16140                                         parseChunks(extra, selectors);
16141                                 }
16142
16143                                 selector = [];
16144                                 for (i = 0; i < parts.length; i++) {
16145                                         if (parts[i] != '>') {
16146                                                 selector.push(compile(parts[i], [], parts[i - 1] === '>'));
16147                                         }
16148                                 }
16149
16150                                 selectors.push(selector);
16151
16152                                 return selectors;
16153                         }
16154
16155                         this._selectors = parseChunks(selector, []);
16156                 },
16157
16158                 /**
16159                  * Returns true/false if the selector matches the specified control.
16160                  *
16161                  * @method match
16162                  * @param {tinymce.ui.Control} control Control to match agains the selector.
16163                  * @param {Array} selectors Optional array of selectors, mostly used internally.
16164                  * @return {Boolean} true/false state if the control matches or not.
16165                  */
16166                 match: function(control, selectors) {
16167                         var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
16168
16169                         selectors = selectors || this._selectors;
16170                         for (i = 0, l = selectors.length; i < l; i++) {
16171                                 selector = selectors[i];
16172                                 sl = selector.length;
16173                                 item = control;
16174                                 count = 0;
16175
16176                                 for (si = sl - 1; si >= 0; si--) {
16177                                         filters = selector[si];
16178
16179                                         while (item) {
16180                                                 // Find the index and length since a psuedo filter like :first needs it
16181                                                 if (filters.psuedo) {
16182                                                         siblings = item.parent().items();
16183                                                         index = Tools.inArray(item, siblings);
16184                                                         length = siblings.length;
16185                                                 }
16186
16187                                                 for (fi = 0, fl = filters.length; fi < fl; fi++) {
16188                                                         if (!filters[fi](item, index, length)) {
16189                                                                 fi = fl + 1;
16190                                                                 break;
16191                                                         }
16192                                                 }
16193
16194                                                 if (fi === fl) {
16195                                                         count++;
16196                                                         break;
16197                                                 } else {
16198                                                         // If it didn't match the right most expression then
16199                                                         // break since it's no point looking at the parents
16200                                                         if (si === sl - 1) {
16201                                                                 break;
16202                                                         }
16203                                                 }
16204
16205                                                 item = item.parent();
16206                                         }
16207                                 }
16208
16209                                 // If we found all selectors then return true otherwise continue looking
16210                                 if (count === sl) {
16211                                         return true;
16212                                 }
16213                         }
16214
16215                         return false;
16216                 },
16217
16218                 /**
16219                  * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
16220                  *
16221                  * @method find
16222                  * @param {tinymce.ui.Control} container Container to look for items in.
16223                  * @return {tinymce.ui.Collection} Collection with matched elements.
16224                  */
16225                 find: function(container) {
16226                         var matches = [], i, l, selectors = this._selectors;
16227
16228                         function collect(items, selector, index) {
16229                                 var i, l, fi, fl, item, filters = selector[index];
16230
16231                                 for (i = 0, l = items.length; i < l; i++) {
16232                                         item = items[i];
16233
16234                                         // Run each filter agains the item
16235                                         for (fi = 0, fl = filters.length; fi < fl; fi++) {
16236                                                 if (!filters[fi](item, i, l)) {
16237                                                         fi = fl + 1;
16238                                                         break;
16239                                                 }
16240                                         }
16241
16242                                         // All filters matched the item
16243                                         if (fi === fl) {
16244                                                 // Matched item is on the last expression like: panel toolbar [button]
16245                                                 if (index == selector.length - 1) {
16246                                                         matches.push(item);
16247                                                 } else {
16248                                                         // Collect next expression type
16249                                                         if (item.items) {
16250                                                                 collect(item.items(), selector, index + 1);
16251                                                         }
16252                                                 }
16253                                         } else if (filters.direct) {
16254                                                 return;
16255                                         }
16256
16257                                         // Collect child items
16258                                         if (item.items) {
16259                                                 collect(item.items(), selector, index);
16260                                         }
16261                                 }
16262                         }
16263
16264                         if (container.items) {
16265                                 for (i = 0, l = selectors.length; i < l; i++) {
16266                                         collect(container.items(), selectors[i], 0);
16267                                 }
16268
16269                                 // Unique the matches if needed
16270                                 if (l > 1) {
16271                                         matches = unique(matches);
16272                                 }
16273                         }
16274
16275                         // Fix for circular reference
16276                         if (!Collection) {
16277                                 // TODO: Fix me!
16278                                 Collection = Selector.Collection;
16279                         }
16280
16281                         return new Collection(matches);
16282                 }
16283         });
16284
16285         return Selector;
16286 });
16287
16288 // Included from: js/tinymce/classes/ui/Collection.js
16289
16290 /**
16291  * Collection.js
16292  *
16293  * Copyright, Moxiecode Systems AB
16294  * Released under LGPL License.
16295  *
16296  * License: http://www.tinymce.com/license
16297  * Contributing: http://www.tinymce.com/contributing
16298  */
16299
16300 /**
16301  * Control collection, this class contains control instances and it enables you to
16302  * perform actions on all the contained items. This is very similar to how jQuery works.
16303  *
16304  * @example
16305  * someCollection.show().disabled(true);
16306  *
16307  * @class tinymce.ui.Collection
16308  */
16309 define("tinymce/ui/Collection", [
16310         "tinymce/util/Tools",
16311         "tinymce/ui/Selector",
16312         "tinymce/util/Class"
16313 ], function(Tools, Selector, Class) {
16314         "use strict";
16315
16316         var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
16317
16318         proto = {
16319                 /**
16320                  * Current number of contained control instances.
16321                  *
16322                  * @field length
16323                  * @type Number
16324                  */
16325                 length: 0,
16326
16327                 /**
16328                  * Constructor for the collection.
16329                  *
16330                  * @constructor
16331                  * @method init
16332                  * @param {Array} items Optional array with items to add.
16333                  */
16334                 init: function(items) {
16335                         if (items) {
16336                                 this.add(items);
16337                         }
16338                 },
16339
16340                 /**
16341                  * Adds new items to the control collection.
16342                  *
16343                  * @method add
16344                  * @param {Array} items Array if items to add to collection.
16345                  * @return {tinymce.ui.Collection} Current collection instance.
16346                  */
16347                 add: function(items) {
16348                         var self = this;
16349
16350                         // Force single item into array
16351                         if (!Tools.isArray(items)) {
16352                                 if (items instanceof Collection) {
16353                                         self.add(items.toArray());
16354                                 } else {
16355                                         push.call(self, items);
16356                                 }
16357                         } else {
16358                                 push.apply(self, items);
16359                         }
16360
16361                         return self;
16362                 },
16363
16364                 /**
16365                  * Sets the contents of the collection. This will remove any existing items
16366                  * and replace them with the ones specified in the input array.
16367                  *
16368                  * @method set
16369                  * @param {Array} items Array with items to set into the Collection.
16370                  * @return {tinymce.ui.Collection} Collection instance.
16371                  */
16372                 set: function(items) {
16373                         var self = this, len = self.length, i;
16374
16375                         self.length = 0;
16376                         self.add(items);
16377
16378                         // Remove old entries
16379                         for (i = self.length; i < len; i++) {
16380                                 delete self[i];
16381                         }
16382
16383                         return self;
16384                 },
16385
16386                 /**
16387                  * Filters the collection item based on the specified selector expression or selector function.
16388                  *
16389                  * @method filter
16390                  * @param {String} selector Selector expression to filter items by.
16391                  * @return {tinymce.ui.Collection} Collection containing the filtered items.
16392                  */
16393                 filter: function(selector) {
16394                         var self = this, i, l, matches = [], item, match;
16395
16396                         // Compile string into selector expression
16397                         if (typeof(selector) === "string") {
16398                                 selector = new Selector(selector);
16399
16400                                 match = function(item) {
16401                                         return selector.match(item);
16402                                 };
16403                         } else {
16404                                 // Use selector as matching function
16405                                 match = selector;
16406                         }
16407
16408                         for (i = 0, l = self.length; i < l; i++) {
16409                                 item = self[i];
16410
16411                                 if (match(item)) {
16412                                         matches.push(item);
16413                                 }
16414                         }
16415
16416                         return new Collection(matches);
16417                 },
16418
16419                 /**
16420                  * Slices the items within the collection.
16421                  *
16422                  * @method slice
16423                  * @param {Number} index Index to slice at.
16424                  * @param {Number} len Optional length to slice.
16425                  * @return {tinymce.ui.Collection} Current collection.
16426                  */
16427                 slice: function() {
16428                         return new Collection(slice.apply(this, arguments));
16429                 },
16430
16431                 /**
16432                  * Makes the current collection equal to the specified index.
16433                  *
16434                  * @method eq
16435                  * @param {Number} index Index of the item to set the collection to.
16436                  * @return {tinymce.ui.Collection} Current collection.
16437                  */
16438                 eq: function(index) {
16439                         return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
16440                 },
16441
16442                 /**
16443                  * Executes the specified callback on each item in collection.
16444                  *
16445                  * @method each
16446                  * @param {function} callback Callback to execute for each item in collection.
16447                  * @return {tinymce.ui.Collection} Current collection instance.
16448                  */
16449                 each: function(callback) {
16450                         Tools.each(this, callback);
16451
16452                         return this;
16453                 },
16454
16455                 /**
16456                  * Returns an JavaScript array object of the contents inside the collection.
16457                  *
16458                  * @method toArray
16459                  * @return {Array} Array with all items from collection.
16460                  */
16461                 toArray: function() {
16462                         return Tools.toArray(this);
16463                 },
16464
16465                 /**
16466                  * Finds the index of the specified control or return -1 if it isn't in the collection.
16467                  *
16468                  * @method indexOf
16469                  * @param {Control} ctrl Control instance to look for.
16470                  * @return {Number} Index of the specified control or -1.
16471                  */
16472                 indexOf: function(ctrl) {
16473                         var self = this, i = self.length;
16474
16475                         while (i--) {
16476                                 if (self[i] === ctrl) {
16477                                         break;
16478                                 }
16479                         }
16480
16481                         return i;
16482                 },
16483
16484                 /**
16485                  * Returns a new collection of the contents in reverse order.
16486                  *
16487                  * @method reverse
16488                  * @return {tinymce.ui.Collection} Collection instance with reversed items.
16489                  */
16490                 reverse: function() {
16491                         return new Collection(Tools.toArray(this).reverse());
16492                 },
16493
16494                 /**
16495                  * Returns true/false if the class exists or not.
16496                  *
16497                  * @method hasClass
16498                  * @param {String} cls Class to check for.
16499                  * @return {Boolean} true/false state if the class exists or not.
16500                  */
16501                 hasClass: function(cls) {
16502                         return this[0] ? this[0].hasClass(cls) : false;
16503                 },
16504
16505                 /**
16506                  * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
16507                  *
16508                  * @method prop
16509                  * @param {String} name Property name to get/set.
16510                  * @param {Object} value Optional object value to set.
16511                  * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
16512                  */
16513                 prop: function(name, value) {
16514                         var self = this, undef, item;
16515
16516                         if (value !== undef) {
16517                                 self.each(function(item) {
16518                                         if (item[name]) {
16519                                                 item[name](value);
16520                                         }
16521                                 });
16522
16523                                 return self;
16524                         }
16525
16526                         item = self[0];
16527
16528                         if (item && item[name]) {
16529                                 return item[name]();
16530                         }
16531                 },
16532
16533                 /**
16534                  * Executes the specific function name with optional arguments an all items in collection if it exists.
16535                  *
16536                  * @example collection.exec("myMethod", arg1, arg2, arg3);
16537                  * @method exec
16538                  * @param {String} name Name of the function to execute.
16539                  * @param {Object} ... Multiple arguments to pass to each function.
16540                  * @return {tinymce.ui.Collection} Current collection.
16541                  */
16542                 exec: function(name) {
16543                         var self = this, args = Tools.toArray(arguments).slice(1);
16544
16545                         self.each(function(item) {
16546                                 if (item[name]) {
16547                                         item[name].apply(item, args);
16548                                 }
16549                         });
16550
16551                         return self;
16552                 },
16553
16554                 /**
16555                  * Remove all items from collection and DOM.
16556                  *
16557                  * @method remove
16558                  * @return {tinymce.ui.Collection} Current collection.
16559                  */
16560                 remove: function() {
16561                         var i = this.length;
16562
16563                         while (i--) {
16564                                 this[i].remove();
16565                         }
16566
16567                         return this;
16568                 }
16569
16570                 /**
16571                  * Fires the specified event by name and arguments on the control. This will execute all
16572                  * bound event handlers.
16573                  *
16574                  * @method fire
16575                  * @param {String} name Name of the event to fire.
16576                  * @param {Object} args Optional arguments to pass to the event.
16577                  * @return {tinymce.ui.Collection} Current collection instance.
16578                  */
16579                 // fire: function(event, args) {}, -- Generated by code below
16580
16581                 /**
16582                  * Binds a callback to the specified event. This event can both be
16583                  * native browser events like "click" or custom ones like PostRender.
16584                  *
16585                  * The callback function will have two parameters the first one being the control that received the event
16586                  * the second one will be the event object either the browsers native event object or a custom JS object.
16587                  *
16588                  * @method on
16589                  * @param {String} name Name of the event to bind. For example "click".
16590                  * @param {String/function} callback Callback function to execute ones the event occurs.
16591                  * @return {tinymce.ui.Collection} Current collection instance.
16592                  */
16593                 // on: function(name, callback) {}, -- Generated by code below
16594
16595                 /**
16596                  * Unbinds the specified event and optionally a specific callback. If you omit the name
16597                  * parameter all event handlers will be removed. If you omit the callback all event handles
16598                  * by the specified name will be removed.
16599                  *
16600                  * @method off
16601                  * @param {String} name Optional name for the event to unbind.
16602                  * @param {function} callback Optional callback function to unbind.
16603                  * @return {tinymce.ui.Collection} Current collection instance.
16604                  */
16605                 // off: function(name, callback) {}, -- Generated by code below
16606
16607                 /**
16608                  * Shows the items in the current collection.
16609                  *
16610                  * @method show
16611                  * @return {tinymce.ui.Collection} Current collection instance.
16612                  */
16613                 // show: function() {}, -- Generated by code below
16614
16615                 /**
16616                  * Hides the items in the current collection.
16617                  *
16618                  * @method hide
16619                  * @return {tinymce.ui.Collection} Current collection instance.
16620                  */
16621                 // hide: function() {}, -- Generated by code below
16622
16623                 /**
16624                  * Sets/gets the text contents of the items in the current collection.
16625                  *
16626                  * @method text
16627                  * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
16628                  */
16629                 // text: function(value) {}, -- Generated by code below
16630
16631                 /**
16632                  * Sets/gets the name contents of the items in the current collection.
16633                  *
16634                  * @method name
16635                  * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
16636                  */
16637                 // name: function(value) {}, -- Generated by code below
16638
16639                 /**
16640                  * Sets/gets the disabled state on the items in the current collection.
16641                  *
16642                  * @method disabled
16643                  * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
16644                  */
16645                 // disabled: function(state) {}, -- Generated by code below
16646
16647                 /**
16648                  * Sets/gets the active state on the items in the current collection.
16649                  *
16650                  * @method active
16651                  * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
16652                  */
16653                 // active: function(state) {}, -- Generated by code below
16654
16655                 /**
16656                  * Sets/gets the selected state on the items in the current collection.
16657                  *
16658                  * @method selected
16659                  * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
16660                  */
16661                 // selected: function(state) {}, -- Generated by code below
16662
16663                 /**
16664                  * Sets/gets the selected state on the items in the current collection.
16665                  *
16666                  * @method visible
16667                  * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
16668                  */
16669                 // visible: function(state) {}, -- Generated by code below
16670
16671                 /**
16672                  * Adds a class to all items in the collection.
16673                  *
16674                  * @method addClass
16675                  * @param {String} cls Class to add to each item.
16676                  * @return {tinymce.ui.Collection} Current collection instance.
16677                  */
16678                 // addClass: function(cls) {}, -- Generated by code below
16679
16680                 /**
16681                  * Removes the specified class from all items in collection.
16682                  *
16683                  * @method removeClass
16684                  * @param {String} cls Class to remove from each item.
16685                  * @return {tinymce.ui.Collection} Current collection instance.
16686                  */
16687                 // removeClass: function(cls) {}, -- Generated by code below
16688         };
16689
16690         // Extend tinymce.ui.Collection prototype with some generated control specific methods
16691         Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) {
16692                 proto[name] = function() {
16693                         var args = Tools.toArray(arguments);
16694
16695                         this.each(function(ctrl) {
16696                                 if (name in ctrl) {
16697                                         ctrl[name].apply(ctrl, args);
16698                                 }
16699                         });
16700
16701                         return this;
16702                 };
16703         });
16704
16705         // Extend tinymce.ui.Collection prototype with some property methods
16706         Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
16707                 proto[name] = function(value) {
16708                         return this.prop(name, value);
16709                 };
16710         });
16711
16712         // Create class based on the new prototype
16713         Collection = Class.extend(proto);
16714
16715         // Stick Collection into Selector to prevent circual references
16716         Selector.Collection = Collection;
16717
16718         return Collection;
16719 });
16720
16721 // Included from: js/tinymce/classes/ui/DomUtils.js
16722
16723 /**
16724  * DOMUtils.js
16725  *
16726  * Copyright, Moxiecode Systems AB
16727  * Released under LGPL License.
16728  *
16729  * License: http://www.tinymce.com/license
16730  * Contributing: http://www.tinymce.com/contributing
16731  */
16732
16733 define("tinymce/ui/DomUtils", [
16734         "tinymce/util/Tools",
16735         "tinymce/dom/DOMUtils"
16736 ], function(Tools, DOMUtils) {
16737         "use strict";
16738
16739         return {
16740                 id: function() {
16741                         return DOMUtils.DOM.uniqueId();
16742                 },
16743
16744                 createFragment: function(html) {
16745                         return DOMUtils.DOM.createFragment(html);
16746                 },
16747
16748                 getWindowSize: function() {
16749                         return DOMUtils.DOM.getViewPort();
16750                 },
16751
16752                 getSize: function(elm) {
16753                         return DOMUtils.DOM.getSize(elm);
16754                 },
16755
16756                 getPos: function(elm, root) {
16757                         return DOMUtils.DOM.getPos(elm, root);
16758                 },
16759
16760                 getViewPort: function(win) {
16761                         return DOMUtils.DOM.getViewPort(win);
16762                 },
16763
16764                 get: function(id) {
16765                         return document.getElementById(id);
16766                 },
16767
16768                 addClass : function(elm, cls) {
16769                         return DOMUtils.DOM.addClass(elm, cls);
16770                 },
16771
16772                 removeClass : function(elm, cls) {
16773                         return DOMUtils.DOM.removeClass(elm, cls);
16774                 },
16775
16776                 hasClass : function(elm, cls) {
16777                         return DOMUtils.DOM.hasClass(elm, cls);
16778                 },
16779
16780                 toggleClass: function(elm, cls, state) {
16781                         return DOMUtils.DOM.toggleClass(elm, cls, state);
16782                 },
16783
16784                 css: function(elm, name, value) {
16785                         return DOMUtils.DOM.setStyle(elm, name, value);
16786                 },
16787
16788                 on: function(target, name, callback, scope) {
16789                         return DOMUtils.DOM.bind(target, name, callback, scope);
16790                 },
16791
16792                 off: function(target, name, callback) {
16793                         return DOMUtils.DOM.unbind(target, name, callback);
16794                 },
16795
16796                 fire: function(target, name, args) {
16797                         return DOMUtils.DOM.fire(target, name, args);
16798                 },
16799
16800                 innerHtml: function(elm, html) {
16801                         // Workaround for <div> in <p> bug on IE 8 #6178
16802                         DOMUtils.DOM.setHTML(elm, html);
16803                 }
16804         };
16805 });
16806
16807 // Included from: js/tinymce/classes/ui/Control.js
16808
16809 /**
16810  * Control.js
16811  *
16812  * Copyright, Moxiecode Systems AB
16813  * Released under LGPL License.
16814  *
16815  * License: http://www.tinymce.com/license
16816  * Contributing: http://www.tinymce.com/contributing
16817  */
16818
16819 /**
16820  * This is the base class for all controls and containers. All UI control instances inherit
16821  * from this one as it has the base logic needed by all of them.
16822  *
16823  * @class tinymce.ui.Control
16824  */
16825 define("tinymce/ui/Control", [
16826         "tinymce/util/Class",
16827         "tinymce/util/Tools",
16828         "tinymce/ui/Collection",
16829         "tinymce/ui/DomUtils"
16830 ], function(Class, Tools, Collection, DomUtils) {
16831         "use strict";
16832
16833         var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" +
16834                                                                 " mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu", " ");
16835
16836         var elementIdCache = {};
16837         var hasMouseWheelEventSupport = "onmousewheel" in document;
16838         var hasWheelEventSupport = false;
16839
16840         var Control = Class.extend({
16841                 Statics: {
16842                         controlIdLookup: {}
16843                 },
16844
16845                 /**
16846                  * Class/id prefix to use for all controls.
16847                  *
16848                  * @final
16849                  * @field {String} classPrefix
16850                  */
16851                 classPrefix: "mce-",
16852
16853                 /**
16854                  * Constructs a new control instance with the specified settings.
16855                  *
16856                  * @constructor
16857                  * @param {Object} settings Name/value object with settings.
16858                  * @setting {String} style Style CSS properties to add.
16859                  * @setting {String} border Border box values example: 1 1 1 1
16860                  * @setting {String} padding Padding box values example: 1 1 1 1
16861                  * @setting {String} margin Margin box values example: 1 1 1 1
16862                  * @setting {Number} minWidth Minimal width for the control.
16863                  * @setting {Number} minHeight Minimal height for the control.
16864                  * @setting {String} classes Space separated list of classes to add.
16865                  * @setting {String} role WAI-ARIA role to use for control.
16866                  * @setting {Boolean} hidden Is the control hidden by default.
16867                  * @setting {Boolean} disabled Is the control disabled by default.
16868                  * @setting {String} name Name of the control instance.
16869                  */
16870                 init: function(settings) {
16871                         var self = this, classes, i;
16872
16873                         self.settings = settings = Tools.extend({}, self.Defaults, settings);
16874
16875                         // Initial states
16876                         self._id = DomUtils.id();
16877                         self._text = self._name = '';
16878                         self._width = self._height = 0;
16879                         self._aria = {role: settings.role};
16880
16881                         // Setup classes
16882                         classes = settings.classes;
16883                         if (classes) {
16884                                 classes = classes.split(' ');
16885                                 classes.map = {};
16886                                 i = classes.length;
16887                                 while (i--) {
16888                                         classes.map[classes[i]] = true;
16889                                 }
16890                         }
16891
16892                         self._classes = classes || [];
16893                         self.visible(true);
16894
16895                         // Set some properties
16896                         Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
16897                                 var value = settings[name], undef;
16898
16899                                 if (value !== undef) {
16900                                         self[name](value);
16901                                 } else if (self['_' + name] === undef) {
16902                                         self['_' + name] = false;
16903                                 }
16904                         });
16905
16906                         self.on('click', function() {
16907                                 if (self.disabled()) {
16908                                         return false;
16909                                 }
16910                         });
16911
16912                         // TODO: Is this needed duplicate code see above?
16913                         if (settings.classes) {
16914                                 Tools.each(settings.classes.split(' '), function(cls) {
16915                                         self.addClass(cls);
16916                                 });
16917                         }
16918
16919                         /**
16920                          * Name/value object with settings for the current control.
16921                          *
16922                          * @field {Object} settings
16923                          */
16924                         self.settings = settings;
16925
16926                         self._borderBox = self.parseBox(settings.border);
16927                         self._paddingBox = self.parseBox(settings.padding);
16928                         self._marginBox = self.parseBox(settings.margin);
16929
16930                         if (settings.hidden) {
16931                                 self.hide();
16932                         }
16933                 },
16934
16935                 // Will generate getter/setter methods for these properties
16936                 Properties: 'parent,title,text,width,height,disabled,active,name,value',
16937
16938                 // Will generate empty dummy functions for these
16939                 Methods: 'renderHtml',
16940
16941                 /**
16942                  * Returns the root element to render controls into.
16943                  *
16944                  * @method getContainerElm
16945                  * @return {Element} HTML DOM element to render into.
16946                  */
16947                 getContainerElm: function() {
16948                         return document.body;
16949                 },
16950
16951                 /**
16952                  * Returns a control instance for the current DOM element.
16953                  *
16954                  * @method getParentCtrl
16955                  * @param {Element} elm HTML dom element to get parent control from.
16956                  * @return {tinymce.ui.Control} Control instance or undefined.
16957                  */
16958                 getParentCtrl: function(elm) {
16959                         var ctrl;
16960
16961                         while (elm) {
16962                                 ctrl = Control.controlIdLookup[elm.id];
16963                                 if (ctrl) {
16964                                         break;
16965                                 }
16966
16967                                 elm = elm.parentNode;
16968                         }
16969
16970                         return ctrl;
16971                 },
16972
16973                 /**
16974                  * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
16975                  *
16976                  * @method parseBox
16977                  * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
16978                  * @return {Object} Object with top/right/bottom/left properties.
16979                  * @private
16980                  */
16981                 parseBox: function(value) {
16982                         var len, radix = 10;
16983
16984                         if (!value) {
16985                                 return;
16986                         }
16987
16988                         if (typeof(value) === "number") {
16989                                 value = value || 0;
16990
16991                                 return {
16992                                         top: value,
16993                                         left: value,
16994                                         bottom: value,
16995                                         right: value
16996                                 };
16997                         }
16998
16999                         value = value.split(' ');
17000                         len = value.length;
17001
17002                         if (len === 1) {
17003                                 value[1] = value[2] = value[3] = value[0];
17004                         } else if (len === 2) {
17005                                 value[2] = value[0];
17006                                 value[3] = value[1];
17007                         } else if (len === 3) {
17008                                 value[3] = value[1];
17009                         }
17010
17011                         return {
17012                                 top: parseInt(value[0], radix) || 0,
17013                                 right: parseInt(value[1], radix) || 0,
17014                                 bottom: parseInt(value[2], radix) || 0,
17015                                 left: parseInt(value[3], radix) || 0
17016                         };
17017                 },
17018
17019                 borderBox: function() {
17020                         return this._borderBox;
17021                 },
17022
17023                 paddingBox: function() {
17024                         return this._paddingBox;
17025                 },
17026
17027                 marginBox: function() {
17028                         return this._marginBox;
17029                 },
17030
17031                 measureBox: function(elm, prefix) {
17032                         function getStyle(name) {
17033                                 var defaultView = document.defaultView;
17034
17035                                 if (defaultView) {
17036                                         // Remove camelcase
17037                                         name = name.replace(/[A-Z]/g, function(a) {
17038                                                 return '-' + a;
17039                                         });
17040
17041                                         return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
17042                                 }
17043
17044                                 return elm.currentStyle[name];
17045                         }
17046
17047                         function getSide(name) {
17048                                 var val = parseInt(getStyle(name), 10);
17049
17050                                 return isNaN(val) ? 0 : val;
17051                         }
17052
17053                         return {
17054                                 top: getSide(prefix + "TopWidth"),
17055                                 right: getSide(prefix + "RightWidth"),
17056                                 bottom: getSide(prefix + "BottomWidth"),
17057                                 left: getSide(prefix + "LeftWidth")
17058                         };
17059                 },
17060
17061                 /**
17062                  * Initializes the current controls layout rect.
17063                  * This will be executed by the layout managers to determine the
17064                  * default minWidth/minHeight etc.
17065                  *
17066                  * @method initLayoutRect
17067                  * @return {Object} Layout rect instance.
17068                  */
17069                 initLayoutRect: function() {
17070                         var self = this, settings = self.settings, borderBox, layoutRect;
17071                         var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
17072                         var startMinWidth, startMinHeight;
17073
17074                         // Measure boxes
17075                         borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
17076                         self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
17077                         self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
17078
17079                         // Setup minWidth/minHeight and width/height
17080                         startMinWidth = settings.minWidth;
17081                         startMinHeight = settings.minHeight;
17082                         minWidth = startMinWidth || elm.offsetWidth;
17083                         minHeight = startMinHeight || elm.offsetHeight;
17084                         width = settings.width;
17085                         height = settings.height;
17086                         autoResize = settings.autoResize;
17087                         autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
17088
17089                         width = width || minWidth;
17090                         height = height || minHeight;
17091
17092                         var deltaW = borderBox.left + borderBox.right;
17093                         var deltaH = borderBox.top + borderBox.bottom;
17094
17095                         var maxW = settings.maxWidth || 0xFFFF;
17096                         var maxH = settings.maxHeight || 0xFFFF;
17097
17098                         // Setup initial layout rect
17099                         self._layoutRect = layoutRect = {
17100                                 x: settings.x || 0,
17101                                 y: settings.y || 0,
17102                                 w: width,
17103                                 h: height,
17104                                 deltaW: deltaW,
17105                                 deltaH: deltaH,
17106                                 contentW: width - deltaW,
17107                                 contentH: height - deltaH,
17108                                 innerW: width - deltaW,
17109                                 innerH: height - deltaH,
17110                                 startMinWidth: startMinWidth || 0,
17111                                 startMinHeight: startMinHeight || 0,
17112                                 minW: Math.min(minWidth, maxW),
17113                                 minH: Math.min(minHeight, maxH),
17114                                 maxW: maxW,
17115                                 maxH: maxH,
17116                                 autoResize: autoResize,
17117                                 scrollW: 0
17118                         };
17119
17120                         self._lastLayoutRect = {};
17121
17122                         return layoutRect;
17123                 },
17124
17125                 /**
17126                  * Getter/setter for the current layout rect.
17127                  *
17128                  * @method layoutRect
17129                  * @param {Object} [newRect] Optional new layout rect.
17130                  * @return {tinymce.ui.Control/Object} Current control or rect object.
17131                  */
17132                 layoutRect: function(newRect) {
17133                         var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
17134
17135                         // Initialize default layout rect
17136                         if (!curRect) {
17137                                 curRect = self.initLayoutRect();
17138                         }
17139
17140                         // Set new rect values
17141                         if (newRect) {
17142                                 // Calc deltas between inner and outer sizes
17143                                 deltaWidth = curRect.deltaW;
17144                                 deltaHeight = curRect.deltaH;
17145
17146                                 // Set x position
17147                                 if (newRect.x !== undef) {
17148                                         curRect.x = newRect.x;
17149                                 }
17150
17151                                 // Set y position
17152                                 if (newRect.y !== undef) {
17153                                         curRect.y = newRect.y;
17154                                 }
17155
17156                                 // Set minW
17157                                 if (newRect.minW !== undef) {
17158                                         curRect.minW = newRect.minW;
17159                                 }
17160
17161                                 // Set minH
17162                                 if (newRect.minH !== undef) {
17163                                         curRect.minH = newRect.minH;
17164                                 }
17165
17166                                 // Set new width and calculate inner width
17167                                 size = newRect.w;
17168                                 if (size !== undef) {
17169                                         size = size < curRect.minW ? curRect.minW : size;
17170                                         size = size > curRect.maxW ? curRect.maxW : size;
17171                                         curRect.w = size;
17172                                         curRect.innerW = size - deltaWidth;
17173                                 }
17174
17175                                 // Set new height and calculate inner height
17176                                 size = newRect.h;
17177                                 if (size !== undef) {
17178                                         size = size < curRect.minH ? curRect.minH : size;
17179                                         size = size > curRect.maxH ? curRect.maxH : size;
17180                                         curRect.h = size;
17181                                         curRect.innerH = size - deltaHeight;
17182                                 }
17183
17184                                 // Set new inner width and calculate width
17185                                 size = newRect.innerW;
17186                                 if (size !== undef) {
17187                                         size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
17188                                         size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
17189                                         curRect.innerW = size;
17190                                         curRect.w = size + deltaWidth;
17191                                 }
17192
17193                                 // Set new height and calculate inner height
17194                                 size = newRect.innerH;
17195                                 if (size !== undef) {
17196                                         size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
17197                                         size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
17198                                         curRect.innerH = size;
17199                                         curRect.h = size + deltaHeight;
17200                                 }
17201
17202                                 // Set new contentW
17203                                 if (newRect.contentW !== undef) {
17204                                         curRect.contentW = newRect.contentW;
17205                                 }
17206
17207                                 // Set new contentH
17208                                 if (newRect.contentH !== undef) {
17209                                         curRect.contentH = newRect.contentH;
17210                                 }
17211
17212                                 // Compare last layout rect with the current one to see if we need to repaint or not
17213                                 lastLayoutRect = self._lastLayoutRect;
17214                                 if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
17215                                         lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
17216                                         repaintControls = Control.repaintControls;
17217
17218                                         if (repaintControls) {
17219                                                 if (repaintControls.map && !repaintControls.map[self._id]) {
17220                                                         repaintControls.push(self);
17221                                                         repaintControls.map[self._id] = true;
17222                                                 }
17223                                         }
17224
17225                                         lastLayoutRect.x = curRect.x;
17226                                         lastLayoutRect.y = curRect.y;
17227                                         lastLayoutRect.w = curRect.w;
17228                                         lastLayoutRect.h = curRect.h;
17229                                 }
17230
17231                                 return self;
17232                         }
17233
17234                         return curRect;
17235                 },
17236
17237                 /**
17238                  * Repaints the control after a layout operation.
17239                  *
17240                  * @method repaint
17241                  */
17242                 repaint: function() {
17243                         var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
17244
17245                         style = self.getEl().style;
17246                         rect = self._layoutRect;
17247                         lastRepaintRect = self._lastRepaintRect || {};
17248
17249                         borderBox = self._borderBox;
17250                         borderW = borderBox.left + borderBox.right;
17251                         borderH = borderBox.top + borderBox.bottom;
17252
17253                         if (rect.x !== lastRepaintRect.x) {
17254                                 style.left = rect.x + 'px';
17255                                 lastRepaintRect.x = rect.x;
17256                         }
17257
17258                         if (rect.y !== lastRepaintRect.y) {
17259                                 style.top = rect.y + 'px';
17260                                 lastRepaintRect.y = rect.y;
17261                         }
17262
17263                         if (rect.w !== lastRepaintRect.w) {
17264                                 style.width = (rect.w - borderW) + 'px';
17265                                 lastRepaintRect.w = rect.w;
17266                         }
17267
17268                         if (rect.h !== lastRepaintRect.h) {
17269                                 style.height = (rect.h - borderH) + 'px';
17270                                 lastRepaintRect.h = rect.h;
17271                         }
17272
17273                         // Update body if needed
17274                         if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
17275                                 bodyStyle = self.getEl('body').style;
17276                                 bodyStyle.width = (rect.innerW) + 'px';
17277                                 lastRepaintRect.innerW = rect.innerW;
17278                         }
17279
17280                         if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
17281                                 bodyStyle = bodyStyle || self.getEl('body').style;
17282                                 bodyStyle.height = (rect.innerH) + 'px';
17283                                 lastRepaintRect.innerH = rect.innerH;
17284                         }
17285
17286                         self._lastRepaintRect = lastRepaintRect;
17287                         self.fire('repaint', {}, false);
17288                 },
17289
17290                 /**
17291                  * Binds a callback to the specified event. This event can both be
17292                  * native browser events like "click" or custom ones like PostRender.
17293                  *
17294                  * The callback function will be passed a DOM event like object that enables yout do stop propagation.
17295                  *
17296                  * @method on
17297                  * @param {String} name Name of the event to bind. For example "click".
17298                  * @param {String/function} callback Callback function to execute ones the event occurs.
17299                  * @return {tinymce.ui.Control} Current control object.
17300                  */
17301                 on: function(name, callback) {
17302                         var self = this, bindings, handlers, names, i;
17303
17304                         function resolveCallbackName(name) {
17305                                 var callback, scope;
17306
17307                                 return function(e) {
17308                                         if (!callback) {
17309                                                 self.parents().each(function(ctrl) {
17310                                                         var callbacks = ctrl.settings.callbacks;
17311
17312                                                         if (callbacks && (callback = callbacks[name])) {
17313                                                                 scope = ctrl;
17314                                                                 return false;
17315                                                         }
17316                                                 });
17317                                         }
17318
17319                                         return callback.call(scope, e);
17320                                 };
17321                         }
17322
17323                         if (callback) {
17324                                 if (typeof(callback) == 'string') {
17325                                         callback = resolveCallbackName(callback);
17326                                 }
17327
17328                                 names = name.toLowerCase().split(' ');
17329                                 i = names.length;
17330                                 while (i--) {
17331                                         name = names[i];
17332
17333                                         bindings = self._bindings;
17334                                         if (!bindings) {
17335                                                 bindings = self._bindings = {};
17336                                         }
17337
17338                                         handlers = bindings[name];
17339                                         if (!handlers) {
17340                                                 handlers = bindings[name] = [];
17341                                         }
17342
17343                                         handlers.push(callback);
17344
17345                                         if (nativeEvents[name]) {
17346                                                 if (!self._nativeEvents) {
17347                                                         self._nativeEvents = {name: true};
17348                                                 } else {
17349                                                         self._nativeEvents[name] = true;
17350                                                 }
17351
17352                                                 if (self._rendered) {
17353                                                         self.bindPendingEvents();
17354                                                 }
17355                                         }
17356                                 }
17357                         }
17358
17359                         return self;
17360                 },
17361
17362                 /**
17363                  * Unbinds the specified event and optionally a specific callback. If you omit the name
17364                  * parameter all event handlers will be removed. If you omit the callback all event handles
17365                  * by the specified name will be removed.
17366                  *
17367                  * @method off
17368                  * @param {String} [name] Name for the event to unbind.
17369                  * @param {function} [callback] Callback function to unbind.
17370                  * @return {mxex.ui.Control} Current control object.
17371                  */
17372                 off: function(name, callback) {
17373                         var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi;
17374
17375                         if (bindings) {
17376                                 if (name) {
17377                                         names = name.toLowerCase().split(' ');
17378                                         i = names.length;
17379                                         while (i--) {
17380                                                 name = names[i];
17381                                                 handlers = bindings[name];
17382
17383                                                 // Unbind all handlers
17384                                                 if (!name) {
17385                                                         for (bindingName in bindings) {
17386                                                                 bindings[bindingName].length = 0;
17387                                                         }
17388
17389                                                         return self;
17390                                                 }
17391
17392                                                 if (handlers) {
17393                                                         // Unbind all by name
17394                                                         if (!callback) {
17395                                                                 handlers.length = 0;
17396                                                         } else {
17397                                                                 // Unbind specific ones
17398                                                                 hi = handlers.length;
17399                                                                 while (hi--) {
17400                                                                         if (handlers[hi] === callback) {
17401                                                                                 handlers.splice(hi, 1);
17402                                                                         }
17403                                                                 }
17404                                                         }
17405                                                 }
17406                                         }
17407                                 } else {
17408                                         self._bindings = [];
17409                                 }
17410                         }
17411
17412                         return self;
17413                 },
17414
17415                 /**
17416                  * Fires the specified event by name and arguments on the control. This will execute all
17417                  * bound event handlers.
17418                  *
17419                  * @method fire
17420                  * @param {String} name Name of the event to fire.
17421                  * @param {Object} [args] Arguments to pass to the event.
17422                  * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
17423                  * @return {Object} Current arguments object.
17424                  */
17425                 fire: function(name, args, bubble) {
17426                         var self = this, i, l, handlers, parentCtrl;
17427
17428                         name = name.toLowerCase();
17429
17430                         // Dummy function that gets replaced on the delegation state functions
17431                         function returnFalse() {
17432                                 return false;
17433                         }
17434
17435                         // Dummy function that gets replaced on the delegation state functions
17436                         function returnTrue() {
17437                                 return true;
17438                         }
17439
17440                         // Setup empty object if args is omited
17441                         args = args || {};
17442
17443                         // Stick type into event object
17444                         if (!args.type) {
17445                                 args.type = name;
17446                         }
17447
17448                         // Stick control into event
17449                         if (!args.control) {
17450                                 args.control = self;
17451                         }
17452
17453                         // Add event delegation methods if they are missing
17454                         if (!args.preventDefault) {
17455                                 // Add preventDefault method
17456                                 args.preventDefault = function() {
17457                                         args.isDefaultPrevented = returnTrue;
17458                                 };
17459
17460                                 // Add stopPropagation
17461                                 args.stopPropagation = function() {
17462                                         args.isPropagationStopped = returnTrue;
17463                                 };
17464
17465                                 // Add stopImmediatePropagation
17466                                 args.stopImmediatePropagation = function() {
17467                                         args.isImmediatePropagationStopped = returnTrue;
17468                                 };
17469
17470                                 // Add event delegation states
17471                                 args.isDefaultPrevented = returnFalse;
17472                                 args.isPropagationStopped = returnFalse;
17473                                 args.isImmediatePropagationStopped = returnFalse;
17474                         }
17475
17476                         if (self._bindings) {
17477                                 handlers = self._bindings[name];
17478
17479                                 if (handlers) {
17480                                         for (i = 0, l = handlers.length; i < l; i++) {
17481                                                 // Execute callback and break if the callback returns a false
17482                                                 if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) {
17483                                                         break;
17484                                                 }
17485                                         }
17486                                 }
17487                         }
17488
17489                         // Bubble event up to parent controls
17490                         if (bubble !== false) {
17491                                 parentCtrl = self.parent();
17492                                 while (parentCtrl && !args.isPropagationStopped()) {
17493                                         parentCtrl.fire(name, args, false);
17494                                         parentCtrl = parentCtrl.parent();
17495                                 }
17496                         }
17497
17498                         return args;
17499                 },
17500
17501                 /**
17502                  * Returns a control collection with all parent controls.
17503                  *
17504                  * @method parents
17505                  * @param {String} selector Optional selector expression to find parents.
17506                  * @return {tinymce.ui.Collection} Collection with all parent controls.
17507                  */
17508                 parents: function(selector) {
17509                         var ctrl = this, parents = new Collection();
17510
17511                         // Add each parent to collection
17512                         for (ctrl = ctrl.parent(); ctrl; ctrl = ctrl.parent()) {
17513                                 parents.add(ctrl);
17514                         }
17515
17516                         // Filter away everything that doesn't match the selector
17517                         if (selector) {
17518                                 parents = parents.filter(selector);
17519                         }
17520
17521                         return parents;
17522                 },
17523
17524                 /**
17525                  * Returns the control next to the current control.
17526                  *
17527                  * @method next
17528                  * @return {tinymce.ui.Control} Next control instance.
17529                  */
17530                 next: function() {
17531                         var parentControls = this.parent().items();
17532
17533                         return parentControls[parentControls.indexOf(this) + 1];
17534                 },
17535
17536                 /**
17537                  * Returns the control previous to the current control.
17538                  *
17539                  * @method prev
17540                  * @return {tinymce.ui.Control} Previous control instance.
17541                  */
17542                 prev: function() {
17543                         var parentControls = this.parent().items();
17544
17545                         return parentControls[parentControls.indexOf(this) - 1];
17546                 },
17547
17548                 /**
17549                  * Find the common ancestor for two control instances.
17550                  *
17551                  * @method findCommonAncestor
17552                  * @param {tinymce.ui.Control} ctrl1 First control.
17553                  * @param {tinymce.ui.Control} ctrl2 Second control.
17554                  * @return {tinymce.ui.Control} Ancestor control instance.
17555                  */
17556                 findCommonAncestor: function(ctrl1, ctrl2) {
17557                         var parentCtrl;
17558
17559                         while (ctrl1) {
17560                                 parentCtrl = ctrl2;
17561
17562                                 while (parentCtrl && ctrl1 != parentCtrl) {
17563                                         parentCtrl = parentCtrl.parent();
17564                                 }
17565
17566                                 if (ctrl1 == parentCtrl) {
17567                                         break;
17568                                 }
17569
17570                                 ctrl1 = ctrl1.parent();
17571                         }
17572
17573                         return ctrl1;
17574                 },
17575
17576                 /**
17577                  * Returns true/false if the specific control has the specific class.
17578                  *
17579                  * @method hasClass
17580                  * @param {String} cls Class to check for.
17581                  * @param {String} [group] Sub element group name.
17582                  * @return {Boolean} True/false if the control has the specified class.
17583                  */
17584                 hasClass: function(cls, group) {
17585                         var classes = this._classes[group || 'control'];
17586
17587                         cls = this.classPrefix + cls;
17588
17589                         return classes && !!classes.map[cls];
17590                 },
17591
17592                 /**
17593                  * Adds the specified class to the control
17594                  *
17595                  * @method addClass
17596                  * @param {String} cls Class to check for.
17597                  * @param {String} [group] Sub element group name.
17598                  * @return {tinymce.ui.Control} Current control object.
17599                  */
17600                 addClass: function(cls, group) {
17601                         var self = this, classes, elm;
17602
17603                         cls = this.classPrefix + cls;
17604                         classes = self._classes[group || 'control'];
17605
17606                         if (!classes) {
17607                                 classes = [];
17608                                 classes.map = {};
17609                                 self._classes[group || 'control'] = classes;
17610                         }
17611
17612                         if (!classes.map[cls]) {
17613                                 classes.map[cls] = cls;
17614                                 classes.push(cls);
17615
17616                                 if (self._rendered) {
17617                                         elm = self.getEl(group);
17618
17619                                         if (elm) {
17620                                                 elm.className = classes.join(' ');
17621                                         }
17622                                 }
17623                         }
17624
17625                         return self;
17626                 },
17627
17628                 /**
17629                  * Removes the specified class from the control.
17630                  *
17631                  * @method removeClass
17632                  * @param {String} cls Class to remove.
17633                  * @param {String} [group] Sub element group name.
17634                  * @return {tinymce.ui.Control} Current control object.
17635                  */
17636                 removeClass: function(cls, group) {
17637                         var self = this, classes, i, elm;
17638
17639                         cls = this.classPrefix + cls;
17640                         classes = self._classes[group || 'control'];
17641                         if (classes && classes.map[cls]) {
17642                                 delete classes.map[cls];
17643
17644                                 i = classes.length;
17645                                 while (i--) {
17646                                         if (classes[i] === cls) {
17647                                                 classes.splice(i, 1);
17648                                         }
17649                                 }
17650                         }
17651
17652                         if (self._rendered) {
17653                                 elm = self.getEl(group);
17654
17655                                 if (elm) {
17656                                         elm.className = classes.join(' ');
17657                                 }
17658                         }
17659
17660                         return self;
17661                 },
17662
17663                 /**
17664                  * Toggles the specified class on the control.
17665                  *
17666                  * @method toggleClass
17667                  * @param {String} cls Class to remove.
17668                  * @param {Boolean} state True/false state to add/remove class.
17669                  * @param {String} [group] Sub element group name.
17670                  * @return {tinymce.ui.Control} Current control object.
17671                  */
17672                 toggleClass: function(cls, state, group) {
17673                         var self = this;
17674
17675                         if (state) {
17676                                 self.addClass(cls, group);
17677                         } else {
17678                                 self.removeClass(cls, group);
17679                         }
17680
17681                         return self;
17682                 },
17683
17684                 /**
17685                  * Returns the class string for the specified group name.
17686                  *
17687                  * @method classes
17688                  * @param {String} [group] Group to get clases by.
17689                  * @return {String} Classes for the specified group.
17690                  */
17691                 classes: function(group) {
17692                         var classes = this._classes[group || 'control'];
17693
17694                         return classes ? classes.join(' ') : '';
17695                 },
17696
17697                 /**
17698                  * Sets the inner HTML of the control element.
17699                  *
17700                  * @method innerHtml
17701                  * @param {String} html Html string to set as inner html.
17702                  * @return {tinymce.ui.Control} Current control object.
17703                  */
17704                 innerHtml: function(html) {
17705                         DomUtils.innerHtml(this.getEl(), html);
17706                         return this;
17707                 },
17708
17709                 /**
17710                  * Returns the control DOM element or sub element.
17711                  *
17712                  * @method getEl
17713                  * @param {String} [suffix] Suffix to get element by.
17714                  * @param {Boolean} [dropCache] True if the cache for the element should be dropped.
17715                  * @return {Element} HTML DOM element for the current control or it's children.
17716                  */
17717                 getEl: function(suffix, dropCache) {
17718                         var elm, id = suffix ? this._id + '-' + suffix : this._id;
17719
17720                         elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id);
17721
17722                         return elm;
17723                 },
17724
17725                 /**
17726                  * Sets/gets the visible for the control.
17727                  *
17728                  * @method visible
17729                  * @param {Boolean} state Value to set to control.
17730                  * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
17731                  */
17732                 visible: function(state) {
17733                         var self = this, parentCtrl;
17734
17735                         if (typeof(state) !== "undefined") {
17736                                 if (self._visible !== state) {
17737                                         if (self._rendered) {
17738                                                 self.getEl().style.display = state ? '' : 'none';
17739                                         }
17740
17741                                         self._visible = state;
17742
17743                                         // Parent container needs to reflow
17744                                         parentCtrl = self.parent();
17745                                         if (parentCtrl) {
17746                                                 parentCtrl._lastRect = null;
17747                                         }
17748
17749                                         self.fire(state ? 'show' : 'hide');
17750                                 }
17751
17752                                 return self;
17753                         }
17754
17755                         return self._visible;
17756                 },
17757
17758                 /**
17759                  * Sets the visible state to true.
17760                  *
17761                  * @method show
17762                  * @return {tinymce.ui.Control} Current control instance.
17763                  */
17764                 show: function() {
17765                         return this.visible(true);
17766                 },
17767
17768                 /**
17769                  * Sets the visible state to false.
17770                  *
17771                  * @method hide
17772                  * @return {tinymce.ui.Control} Current control instance.
17773                  */
17774                 hide: function() {
17775                         return this.visible(false);
17776                 },
17777
17778                 /**
17779                  * Focuses the current control.
17780                  *
17781                  * @method focus
17782                  * @return {tinymce.ui.Control} Current control instance.
17783                  */
17784                 focus: function() {
17785                         try {
17786                                 this.getEl().focus();
17787                         } catch (ex) {
17788                                 // Ignore IE error
17789                         }
17790
17791                         return this;
17792                 },
17793
17794                 /**
17795                  * Blurs the current control.
17796                  *
17797                  * @method blur
17798                  * @return {tinymce.ui.Control} Current control instance.
17799                  */
17800                 blur: function() {
17801                         this.getEl().blur();
17802
17803                         return this;
17804                 },
17805
17806                 /**
17807                  * Sets the specified aria property.
17808                  *
17809                  * @method aria
17810                  * @param {String} name Name of the aria property to set.
17811                  * @param {String} value Value of the aria property.
17812                  * @return {tinymce.ui.Control} Current control instance.
17813                  */
17814                 aria: function(name, value) {
17815                         var self = this, elm = self.getEl();
17816
17817                         if (typeof(value) === "undefined") {
17818                                 return self._aria[name];
17819                         } else {
17820                                 self._aria[name] = value;
17821                         }
17822
17823                         if (self._rendered) {
17824                                 if (name == 'label') {
17825                                         elm.setAttribute('aria-labeledby', self._id);
17826                                 }
17827
17828                                 elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
17829                         }
17830
17831                         return self;
17832                 },
17833
17834                 /**
17835                  * Encodes the specified string with HTML entities. It will also
17836                  * translate the string to different languages.
17837                  *
17838                  * @method encode
17839                  * @param {String/Object/Array} text Text to entity encode.
17840                  * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
17841                  * @return {String} Encoded and possible traslated string. 
17842                  */
17843                 encode: function(text, translate) {
17844                         if (translate !== false && Control.translate) {
17845                                 text = Control.translate(text);
17846                         }
17847
17848                         return (text || '').replace(/[&<>"]/g, function(match) {
17849                                 return '&#' + match.charCodeAt(0) + ';';
17850                         });
17851                 },
17852
17853                 /**
17854                  * Adds items before the current control.
17855                  *
17856                  * @method before
17857                  * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
17858                  * @return {tinymce.ui.Control} Current control instance.
17859                  */
17860                 before: function(items) {
17861                         var self = this, parent = self.parent();
17862
17863                         if (parent) {
17864                                 parent.insert(items, parent.items().indexOf(self), true);
17865                         }
17866
17867                         return self;
17868                 },
17869
17870                 /**
17871                  * Adds items after the current control.
17872                  *
17873                  * @method after
17874                  * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
17875                  * @return {tinymce.ui.Control} Current control instance.
17876                  */
17877                 after: function(items) {
17878                         var self = this, parent = self.parent();
17879
17880                         if (parent) {
17881                                 parent.insert(items, parent.items().indexOf(self));
17882                         }
17883
17884                         return self;
17885                 },
17886
17887                 /**
17888                  * Removes the current control from DOM and from UI collections.
17889                  *
17890                  * @method remove
17891                  * @return {tinymce.ui.Control} Current control instance.
17892                  */
17893                 remove: function() {
17894                         var self = this, elm = self.getEl(), parent = self.parent(), newItems;
17895
17896                         if (self.items) {
17897                                 var controls = self.items().toArray();
17898                                 var i = controls.length;
17899                                 while (i--) {
17900                                         controls[i].remove();
17901                                 }
17902                         }
17903
17904                         if (parent && parent.items) {
17905                                 newItems = [];
17906
17907                                 parent.items().each(function(item) {
17908                                         if (item !== self) {
17909                                                 newItems.push(item);
17910                                         }
17911                                 });
17912
17913                                 parent.items().set(newItems);
17914                                 parent._lastRect = null;
17915                         }
17916
17917                         if (self._eventsRoot && self._eventsRoot == self) {
17918                                 DomUtils.off(elm);
17919                         }
17920
17921                         delete Control.controlIdLookup[self._id];
17922
17923                         if (elm.parentNode) {
17924                                 elm.parentNode.removeChild(elm);
17925                         }
17926
17927                         return self;
17928                 },
17929
17930                 /**
17931                  * Renders the control before the specified element.
17932                  *
17933                  * @method renderBefore
17934                  * @param {Element} elm Element to render before.
17935                  * @return {tinymce.ui.Control} Current control instance.
17936                  */
17937                 renderBefore: function(elm) {
17938                         var self = this;
17939
17940                         elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
17941                         self.postRender();
17942
17943                         return self;
17944                 },
17945
17946                 /**
17947                  * Renders the control to the specified element.
17948                  *
17949                  * @method renderBefore
17950                  * @param {Element} elm Element to render to.
17951                  * @return {tinymce.ui.Control} Current control instance.
17952                  */
17953                 renderTo: function(elm) {
17954                         var self = this;
17955
17956                         elm = elm || self.getContainerElm();
17957                         elm.appendChild(DomUtils.createFragment(self.renderHtml()));
17958                         self.postRender();
17959
17960                         return self;
17961                 },
17962
17963                 /**
17964                  * Post render method. Called after the control has been rendered to the target.
17965                  *
17966                  * @method postRender
17967                  * @return {tinymce.ui.Control} Current control instance.
17968                  */
17969                 postRender: function() {
17970                         var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
17971
17972                         // Bind on<event> settings
17973                         for (name in settings) {
17974                                 if (name.indexOf("on") === 0) {
17975                                         self.on(name.substr(2), settings[name]);
17976                                 }
17977                         }
17978
17979                         if (self._eventsRoot) {
17980                                 for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
17981                                         parentEventsRoot = parent._eventsRoot;
17982                                 }
17983
17984                                 if (parentEventsRoot) {
17985                                         for (name in parentEventsRoot._nativeEvents) {
17986                                                 self._nativeEvents[name] = true;
17987                                         }
17988                                 }
17989                         }
17990
17991                         self.bindPendingEvents();
17992
17993                         if (settings.style) {
17994                                 elm = self.getEl();
17995                                 if (elm) {
17996                                         elm.setAttribute('style', settings.style);
17997                                         elm.style.cssText = settings.style;
17998                                 }
17999                         }
18000
18001                         if (!self._visible) {
18002                                 DomUtils.css(self.getEl(), 'display', 'none');
18003                         }
18004
18005                         if (self.settings.border) {
18006                                 box = self.borderBox();
18007                                 DomUtils.css(self.getEl(), {
18008                                         'border-top-width': box.top,
18009                                         'border-right-width': box.right,
18010                                         'border-bottom-width': box.bottom,
18011                                         'border-left-width': box.left
18012                                 });
18013                         }
18014
18015                         // Add instance to lookup
18016                         Control.controlIdLookup[self._id] = self;
18017
18018                         for (var key in self._aria) {
18019                                 self.aria(key, self._aria[key]);
18020                         }
18021
18022                         self.fire('postrender', {}, false);
18023                 },
18024
18025                 /**
18026                  * Scrolls the current control into view.
18027                  *
18028                  * @method scrollIntoView
18029                  * @param {String} align Alignment in view top|center|bottom.
18030                  * @return {tinymce.ui.Control} Current control instance.
18031                  */
18032                 scrollIntoView: function(align) {
18033                         function getOffset(elm, rootElm) {
18034                                 var x, y, parent = elm;
18035
18036                                 x = y = 0;
18037                                 while (parent && parent != rootElm && parent.nodeType) {
18038                                         x += parent.offsetLeft || 0;
18039                                         y += parent.offsetTop || 0;
18040                                         parent = parent.offsetParent;
18041                                 }
18042
18043                                 return {x: x, y: y};
18044                         }
18045
18046                         var elm = this.getEl(), parentElm = elm.parentNode;
18047                         var x, y, width, height, parentWidth, parentHeight;
18048                         var pos = getOffset(elm, parentElm);
18049
18050                         x = pos.x;
18051                         y = pos.y;
18052                         width = elm.offsetWidth;
18053                         height = elm.offsetHeight;
18054                         parentWidth = parentElm.clientWidth;
18055                         parentHeight = parentElm.clientHeight;
18056
18057                         if (align == "end") {
18058                                 x -= parentWidth - width;
18059                                 y -= parentHeight - height;
18060                         } else if (align == "center") {
18061                                 x -= (parentWidth / 2) - (width / 2);
18062                                 y -= (parentHeight / 2) - (height / 2);
18063                         }
18064
18065                         parentElm.scrollLeft = x;
18066                         parentElm.scrollTop = y;
18067
18068                         return this;
18069                 },
18070
18071                 /**
18072                  * Binds pending DOM events.
18073                  *
18074                  * @private
18075                  */
18076                 bindPendingEvents: function() {
18077                         var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;
18078
18079                         function delegate(e) {
18080                                 var control = self.getParentCtrl(e.target);
18081
18082                                 if (control) {
18083                                         control.fire(e.type, e);
18084                                 }
18085                         }
18086
18087                         function mouseLeaveHandler() {
18088                                 var ctrl = eventRootCtrl._lastHoverCtrl;
18089
18090                                 if (ctrl) {
18091                                         ctrl.fire("mouseleave", {target: ctrl.getEl()});
18092
18093                                         ctrl.parents().each(function(ctrl) {
18094                                                 ctrl.fire("mouseleave", {target: ctrl.getEl()});
18095                                         });
18096
18097                                         eventRootCtrl._lastHoverCtrl = null;
18098                                 }
18099                         }
18100
18101                         function mouseEnterHandler(e) {
18102                                 var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
18103
18104                                 // Over on a new control
18105                                 if (ctrl !== lastCtrl) {
18106                                         eventRootCtrl._lastHoverCtrl = ctrl;
18107
18108                                         parents = ctrl.parents().toArray().reverse();
18109                                         parents.push(ctrl);
18110
18111                                         if (lastCtrl) {
18112                                                 lastParents = lastCtrl.parents().toArray().reverse();
18113                                                 lastParents.push(lastCtrl);
18114
18115                                                 for (idx = 0; idx < lastParents.length; idx++) {
18116                                                         if (parents[idx] !== lastParents[idx]) {
18117                                                                 break;
18118                                                         }
18119                                                 }
18120
18121                                                 for (i = lastParents.length - 1; i >= idx; i--) {
18122                                                         lastCtrl = lastParents[i];
18123                                                         lastCtrl.fire("mouseleave", {
18124                                                                 target : lastCtrl.getEl()
18125                                                         });
18126                                                 }
18127                                         }
18128
18129                                         for (i = idx; i < parents.length; i++) {
18130                                                 ctrl = parents[i];
18131                                                 ctrl.fire("mouseenter", {
18132                                                         target : ctrl.getEl()
18133                                                 });
18134                                         }
18135                                 }
18136                         }
18137
18138                         function fixWheelEvent(e) {
18139                                 e.preventDefault();
18140
18141                                 if (e.type == "mousewheel") {
18142                                         e.deltaY = - 1/40 * e.wheelDelta;
18143
18144                                         if (e.wheelDeltaX) {
18145                                                 e.deltaX = -1/40 * e.wheelDeltaX;
18146                                         }
18147                                 } else {
18148                                         e.deltaX = 0;
18149                                         e.deltaY = e.detail;
18150                                 }
18151
18152                                 e = self.fire("wheel", e);
18153                         }
18154
18155                         self._rendered = true;
18156
18157                         nativeEvents = self._nativeEvents;
18158                         if (nativeEvents) {
18159                                 // Find event root element if it exists
18160                                 parents = self.parents().toArray();
18161                                 parents.unshift(self);
18162                                 for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
18163                                         eventRootCtrl = parents[i]._eventsRoot;
18164                                 }
18165
18166                                 // Event root wasn't found the use the root control
18167                                 if (!eventRootCtrl) {
18168                                         eventRootCtrl = parents[parents.length - 1] || self;
18169                                 }
18170
18171                                 // Set the eventsRoot property on children that didn't have it
18172                                 self._eventsRoot = eventRootCtrl;
18173                                 for (l = i, i = 0; i < l; i++) {
18174                                         parents[i]._eventsRoot = eventRootCtrl;
18175                                 }
18176
18177                                 // Bind native event delegates
18178                                 for (name in nativeEvents) {
18179                                         if (!nativeEvents) {
18180                                                 return false;
18181                                         }
18182
18183                                         if (name === "wheel" && !hasWheelEventSupport) {
18184                                                 if (hasMouseWheelEventSupport) {
18185                                                         DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
18186                                                 } else {
18187                                                         DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
18188                                                 }
18189
18190                                                 continue;
18191                                         }
18192
18193                                         // Special treatment for mousenter/mouseleave since these doesn't bubble
18194                                         if (name === "mouseenter" || name === "mouseleave") {
18195                                                 // Fake mousenter/mouseleave
18196                                                 if (!eventRootCtrl._hasMouseEnter) {
18197                                                         DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
18198                                                         DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
18199                                                         eventRootCtrl._hasMouseEnter = 1;
18200                                                 }
18201                                         } else if (!eventRootCtrl[name]) {
18202                                                 DomUtils.on(eventRootCtrl.getEl(), name, delegate);
18203                                                 eventRootCtrl[name] = true;
18204                                         }
18205
18206                                         // Remove the event once it's bound
18207                                         nativeEvents[name] = false;
18208                                 }
18209                         }
18210                 },
18211
18212                 /**
18213                  * Reflows the current control and it's parents.
18214                  * This should be used after you for example append children to the current control so
18215                  * that the layout managers know that they need to reposition everything.
18216                  *
18217                  * @example
18218                  * container.append({type: 'button', text: 'My button'}).reflow();
18219                  *
18220                  * @method reflow
18221                  * @return {tinymce.ui.Control} Current control instance.
18222                  */
18223                 reflow: function() {
18224                         this.repaint();
18225
18226                         return this;
18227                 }
18228
18229                 /**
18230                  * Sets/gets the parent container for the control.
18231                  *
18232                  * @method parent
18233                  * @param {tinymce.ui.Container} parent Optional parent to set.
18234                  * @return {tinymce.ui.Control} Parent control or the current control on a set action.
18235                  */
18236                 // parent: function(parent) {} -- Generated
18237
18238                 /**
18239                  * Sets/gets the text for the control.
18240                  *
18241                  * @method text
18242                  * @param {String} value Value to set to control.
18243                  * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
18244                  */
18245                 // text: function(value) {} -- Generated
18246
18247                 /**
18248                  * Sets/gets the width for the control.
18249                  *
18250                  * @method width
18251                  * @param {Number} value Value to set to control.
18252                  * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
18253                  */
18254                 // width: function(value) {} -- Generated
18255
18256                 /**
18257                  * Sets/gets the height for the control.
18258                  *
18259                  * @method height
18260                  * @param {Number} value Value to set to control.
18261                  * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
18262                  */
18263                 // height: function(value) {} -- Generated
18264
18265                 /**
18266                  * Sets/gets the disabled state on the control.
18267                  *
18268                  * @method disabled
18269                  * @param {Boolean} state Value to set to control.
18270                  * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
18271                  */
18272                 // disabled: function(state) {} -- Generated
18273
18274                 /**
18275                  * Sets/gets the active for the control.
18276                  *
18277                  * @method active
18278                  * @param {Boolean} state Value to set to control.
18279                  * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
18280                  */
18281                 // active: function(state) {} -- Generated
18282
18283                 /**
18284                  * Sets/gets the name for the control.
18285                  *
18286                  * @method name
18287                  * @param {String} value Value to set to control.
18288                  * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
18289                  */
18290                 // name: function(value) {} -- Generated
18291
18292                 /**
18293                  * Sets/gets the title for the control.
18294                  *
18295                  * @method title
18296                  * @param {String} value Value to set to control.
18297                  * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
18298                  */
18299                 // title: function(value) {} -- Generated
18300         });
18301
18302         return Control;
18303 });
18304
18305 // Included from: js/tinymce/classes/ui/Factory.js
18306
18307 /**
18308  * Factory.js
18309  *
18310  * Copyright, Moxiecode Systems AB
18311  * Released under LGPL License.
18312  *
18313  * License: http://www.tinymce.com/license
18314  * Contributing: http://www.tinymce.com/contributing
18315  */
18316
18317 /*global tinymce:true */
18318
18319 /**
18320  * This class is a factory for control instances. This enables you
18321  * to create instances of controls without having to require the UI controls directly.
18322  *
18323  * It also allow you to override or add new control types.
18324  *
18325  * @class tinymce.ui.Factory
18326  */
18327 define("tinymce/ui/Factory", [], function() {
18328         "use strict";
18329
18330         var types = {}, namespaceInit;
18331
18332         return {
18333                 /**
18334                  * Adds a new control instance type to the factory.
18335                  *
18336                  * @method add
18337                  * @param {String} type Type name for example "button".
18338                  * @param {function} typeClass Class type function.
18339                  */
18340                 add: function(type, typeClass) {
18341                         types[type.toLowerCase()] = typeClass;
18342                 },
18343
18344                 /**
18345                  * Returns true/false if the specified type exists or not.
18346                  *
18347                  * @method has
18348                  * @param {String} type Type to look for.
18349                  * @return {Boolean} true/false if the control by name exists.
18350                  */
18351                 has: function(type) {
18352                         return !!types[type.toLowerCase()];
18353                 },
18354
18355                 /**
18356                  * Creates a new control instance based on the settings provided. The instance created will be
18357                  * based on the specified type property it can also create whole structures of components out of
18358                  * the specified JSON object.
18359                  *
18360                  * @example
18361                  * tinymce.ui.Factory.create({
18362                  *     type: 'button',
18363                  *     text: 'Hello world!'
18364                  * });
18365                  *
18366                  * @method create
18367                  * @param {Object/String} settings Name/Value object with items used to create the type.
18368                  * @return {tinymce.ui.Control} Control instance based on the specified type.
18369                  */
18370                 create: function(type, settings) {
18371                         var ControlType, name, namespace;
18372
18373                         // Build type lookup
18374                         if (!namespaceInit) {
18375                                 namespace = tinymce.ui;
18376
18377                                 for (name in namespace) {
18378                                         types[name.toLowerCase()] = namespace[name];
18379                                 }
18380
18381                                 namespaceInit = true;
18382                         }
18383
18384                         // If string is specified then use it as the type
18385                         if (typeof(type) == 'string') {
18386                                 settings = settings || {};
18387                                 settings.type = type;
18388                         } else {
18389                                 settings = type;
18390                                 type = settings.type;
18391                         }
18392
18393                         // Find control type
18394                         type = type.toLowerCase();
18395                         ControlType = types[type];
18396
18397                         // #if debug
18398
18399                         if (!ControlType) {
18400                                 throw new Error("Could not find control by type: " + type);
18401                         }
18402
18403                         // #endif
18404
18405                         ControlType = new ControlType(settings);
18406                         ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
18407
18408                         return ControlType;
18409                 }
18410         };
18411 });
18412
18413 // Included from: js/tinymce/classes/ui/Container.js
18414
18415 /**
18416  * Container.js
18417  *
18418  * Copyright, Moxiecode Systems AB
18419  * Released under LGPL License.
18420  *
18421  * License: http://www.tinymce.com/license
18422  * Contributing: http://www.tinymce.com/contributing
18423  */
18424
18425 /**
18426  * Container control. This is extended by all controls that can have
18427  * children such as panels etc. You can also use this class directly as an
18428  * generic container instance. The container doesn't have any specific role or style.
18429  *
18430  * @-x-less Container.less
18431  * @class tinymce.ui.Container
18432  * @extends tinymce.ui.Control
18433  */
18434 define("tinymce/ui/Container", [
18435         "tinymce/ui/Control",
18436         "tinymce/ui/Collection",
18437         "tinymce/ui/Selector",
18438         "tinymce/ui/Factory",
18439         "tinymce/util/Tools",
18440         "tinymce/ui/DomUtils"
18441 ], function(Control, Collection, Selector, Factory, Tools, DomUtils) {
18442         "use strict";
18443
18444         var selectorCache = {};
18445
18446         return Control.extend({
18447                 layout: '',
18448                 innerClass: 'container-inner',
18449
18450                 /**
18451                  * Constructs a new control instance with the specified settings.
18452                  *
18453                  * @constructor
18454                  * @param {Object} settings Name/value object with settings.
18455                  * @setting {Array} items Items to add to container in JSON format or control instances.
18456                  * @setting {String} layout Layout manager by name to use.
18457                  * @setting {Object} defaults Default settings to apply to all items.
18458                  */
18459                 init: function(settings) {
18460                         var self = this;
18461
18462                         self._super(settings);
18463                         settings = self.settings;
18464                         self._fixed = settings.fixed;
18465                         self._items = new Collection();
18466
18467                         self.addClass('container');
18468                         self.addClass('container-body', 'body');
18469
18470                         if (settings.containerCls) {
18471                                 self.addClass(settings.containerCls);
18472                         }
18473
18474                         self._layout = Factory.create((settings.layout || self.layout) + 'layout');
18475
18476                         if (self.settings.items) {
18477                                 self.add(self.settings.items);
18478                         }
18479
18480                         // TODO: Fix this!
18481                         self._hasBody = true;
18482                 },
18483
18484                 /**
18485                  * Returns a collection of child items that the container currently have.
18486                  *
18487                  * @method items
18488                  * @return {tinymce.ui.Collection} Control collection direct child controls.
18489                  */
18490                 items: function() {
18491                         return this._items;
18492                 },
18493
18494                 /**
18495                  * Find child controls by selector.
18496                  *
18497                  * @method find
18498                  * @param {String} selector Selector CSS pattern to find children by.
18499                  * @return {tinymce.ui.Collection} Control collection with child controls.
18500                  */
18501                 find: function(selector) {
18502                         selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
18503
18504                         return selector.find(this);
18505                 },
18506
18507                 /**
18508                  * Adds one or many items to the current container. This will create instances of
18509                  * the object representations if needed.
18510                  *
18511                  * @method add
18512                  * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
18513                  * @return {tinymce.ui.Collection} Current collection control.
18514                  */
18515                 add: function(items) {
18516                         var self = this;
18517
18518                         self.items().add(self.create(items)).parent(self);
18519
18520                         return self;
18521                 },
18522
18523                 /**
18524                  * Focuses the current container instance. This will look
18525                  * for the first control in the container and focus that.
18526                  *
18527                  * @method focus
18528                  * @return {tinymce.ui.Collection} Current instance.
18529                  */
18530                 focus: function() {
18531                         var self = this;
18532
18533                         if (self.keyNav) {
18534                                 self.keyNav.focusFirst();
18535                         } else {
18536                                 self._super();
18537                         }
18538
18539                         return self;
18540                 },
18541
18542                 /**
18543                  * Replaces the specified child control with a new control.
18544                  *
18545                  * @method replace
18546                  * @param {tinymce.ui.Control} oldItem Old item to be replaced.
18547                  * @param {tinymce.ui.Control} newItem New item to be inserted.
18548                  */
18549                 replace: function(oldItem, newItem) {
18550                         var ctrlElm, items = this.items(), i = items.length;
18551
18552                         // Replace the item in collection
18553                         while (i--) {
18554                                 if (items[i] === oldItem) {
18555                                         items[i] = newItem;
18556                                         break;
18557                                 }
18558                         }
18559
18560                         if (i >= 0) {
18561                                 // Remove new item from DOM
18562                                 ctrlElm = newItem.getEl();
18563                                 if (ctrlElm) {
18564                                         ctrlElm.parentNode.removeChild(ctrlElm);
18565                                 }
18566
18567                                 // Remove old item from DOM
18568                                 ctrlElm = oldItem.getEl();
18569                                 if (ctrlElm) {
18570                                         ctrlElm.parentNode.removeChild(ctrlElm);
18571                                 }
18572                         }
18573
18574                         // Adopt the item
18575                         newItem.parent(this);
18576                 },
18577
18578                 /**
18579                  * Creates the specified items. If any of the items is plain JSON style objects
18580                  * it will convert these into real tinymce.ui.Control instances.
18581                  *
18582                  * @method create
18583                  * @param {Array} items Array of items to convert into control instances.
18584                  * @return {Array} Array with control instances.
18585                  */
18586                 create: function(items) {
18587                         var self = this, settings, ctrlItems = [];
18588
18589                         // Non array structure, then force it into an array
18590                         if (!Tools.isArray(items)) {
18591                                 items = [items];
18592                         }
18593
18594                         // Add default type to each child control
18595                         Tools.each(items, function(item) {
18596                                 if (item) {
18597                                         // Construct item if needed
18598                                         if (!(item instanceof Control)) {
18599                                                 // Name only then convert it to an object
18600                                                 if (typeof(item) == "string") {
18601                                                         item = {type: item};
18602                                                 }
18603
18604                                                 // Create control instance based on input settings and default settings
18605                                                 settings = Tools.extend({}, self.settings.defaults, item);
18606                                                 item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
18607                                                         (settings.defaults ? settings.defaults.type : null);
18608                                                 item = Factory.create(settings);
18609                                         }
18610
18611                                         ctrlItems.push(item);
18612                                 }
18613                         });
18614
18615                         return ctrlItems;
18616                 },
18617
18618                 /**
18619                  * Renders new control instances.
18620                  *
18621                  * @private
18622                  */
18623                 renderNew: function() {
18624                         var self = this;
18625
18626                         // Render any new items
18627                         self.items().each(function(ctrl, index) {
18628                                 var containerElm, fragment;
18629
18630                                 ctrl.parent(self);
18631
18632                                 if (!ctrl._rendered) {
18633                                         containerElm = self.getEl('body');
18634                                         fragment = DomUtils.createFragment(ctrl.renderHtml());
18635
18636                                         // Insert or append the item
18637                                         if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
18638                                                 containerElm.insertBefore(fragment, containerElm.childNodes[index]);
18639                                         } else {
18640                                                 containerElm.appendChild(fragment);
18641                                         }
18642
18643                                         ctrl.postRender();
18644                                 }
18645                         });
18646
18647                         self._layout.applyClasses(self);
18648                         self._lastRect = null;
18649
18650                         return self;
18651                 },
18652
18653                 /**
18654                  * Appends new instances to the current container.
18655                  *
18656                  * @method append
18657                  * @param {Array/tinymce.ui.Collection} items Array if controls to append.
18658                  * @return {tinymce.ui.Container} Current container instance.
18659                  */
18660                 append: function(items) {
18661                         return this.add(items).renderNew();
18662                 },
18663
18664                 /**
18665                  * Prepends new instances to the current container.
18666                  *
18667                  * @method prepend
18668                  * @param {Array/tinymce.ui.Collection} items Array if controls to prepend.
18669                  * @return {tinymce.ui.Container} Current container instance.
18670                  */
18671                 prepend: function(items) {
18672                         var self = this;
18673
18674                         self.items().set(self.create(items).concat(self.items().toArray()));
18675
18676                         return self.renderNew();
18677                 },
18678
18679                 /**
18680                  * Inserts an control at a specific index.
18681                  *
18682                  * @method insert
18683                  * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
18684                  * @param {Number} index Index to insert controls at.
18685                  * @param {Boolean} [before=false] Inserts controls before the index.
18686                  */
18687                 insert: function(items, index, before) {
18688                         var self = this, curItems, beforeItems, afterItems;
18689
18690                         items = self.create(items);
18691                         curItems = self.items();
18692
18693                         if (!before && index < curItems.length - 1) {
18694                                 index += 1;
18695                         }
18696
18697                         if (index >= 0 && index < curItems.length) {
18698                                 beforeItems = curItems.slice(0, index).toArray();
18699                                 afterItems = curItems.slice(index).toArray();
18700                                 curItems.set(beforeItems.concat(items, afterItems));
18701                         }
18702
18703                         return self.renderNew();
18704                 },
18705
18706                 /**
18707                  * Populates the form fields from the specified JSON data object.
18708                  *
18709                  * Control items in the form that matches the data will have it's value set.
18710                  *
18711                  * @method fromJSON
18712                  * @param {Object} data JSON data object to set control values by.
18713                  * @return {tinymce.ui.Container} Current form instance.
18714                  */
18715                 fromJSON: function(data) {
18716                         var self = this;
18717
18718                         for (var name in data) {
18719                                 self.find('#' + name).value(data[name]);
18720                         }
18721
18722                         return self;
18723                 },
18724
18725                 /**
18726                  * Serializes the form into a JSON object by getting all items
18727                  * that has a name and a value.
18728                  *
18729                  * @method toJSON
18730                  * @return {Object} JSON object with form data.
18731                  */
18732                 toJSON: function() {
18733                         var self = this, data = {};
18734
18735                         self.find('*').each(function(ctrl) {
18736                                 var name = ctrl.name(), value = ctrl.value();
18737
18738                                 if (name && typeof(value) != "undefined") {
18739                                         data[name] = value;
18740                                 }
18741                         });
18742
18743                         return data;
18744                 },
18745
18746                 preRender: function() {
18747                 },
18748
18749                 /**
18750                  * Renders the control as a HTML string.
18751                  *
18752                  * @method renderHtml
18753                  * @return {String} HTML representing the control.
18754                  */
18755                 renderHtml: function() {
18756                         var self = this, layout = self._layout;
18757
18758                         self.preRender();
18759                         layout.preRender(self);
18760
18761                         return (
18762                                 '<div id="' + self._id + '" class="' + self.classes() + '" role="' + this.settings.role + '">' +
18763                                         '<div id="' + self._id + '-body" class="' + self.classes('body') + '">'+
18764                                                 (self.settings.html || '') + layout.renderHtml(self) +
18765                                         '</div>' +
18766                                 '</div>'
18767                         );
18768                 },
18769
18770                 /**
18771                  * Post render method. Called after the control has been rendered to the target.
18772                  *
18773                  * @method postRender
18774                  * @return {tinymce.ui.Container} Current combobox instance.
18775                  */
18776                 postRender: function() {
18777                         var self = this, box;
18778
18779                         self.items().exec('postRender');
18780                         self._super();
18781
18782                         self._layout.postRender(self);
18783                         self._rendered = true;
18784
18785                         if (self.settings.style) {
18786                                 DomUtils.css(self.getEl(), self.settings.style);
18787                         }
18788
18789                         if (self.settings.border) {
18790                                 box = self.borderBox();
18791                                 DomUtils.css(self.getEl(), {
18792                                         'border-top-width': box.top,
18793                                         'border-right-width': box.right,
18794                                         'border-bottom-width': box.bottom,
18795                                         'border-left-width': box.left
18796                                 });
18797                         }
18798
18799                         return self;
18800                 },
18801
18802                 /**
18803                  * Initializes the current controls layout rect.
18804                  * This will be executed by the layout managers to determine the
18805                  * default minWidth/minHeight etc.
18806                  *
18807                  * @method initLayoutRect
18808                  * @return {Object} Layout rect instance.
18809                  */
18810                 initLayoutRect: function() {
18811                         var self = this, layoutRect = self._super();
18812
18813                         // Recalc container size by asking layout manager
18814                         self._layout.recalc(self);
18815
18816                         return layoutRect;
18817                 },
18818
18819                 /**
18820                  * Recalculates the positions of the controls in the current container.
18821                  * This is invoked by the reflow method and shouldn't be called directly.
18822                  *
18823                  * @method recalc
18824                  */
18825                 recalc: function() {
18826                         var self = this, rect = self._layoutRect, lastRect = self._lastRect;
18827
18828                         if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
18829                                 self._layout.recalc(self);
18830                                 rect = self.layoutRect();
18831                                 self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
18832                                 return true;
18833                         }
18834                 },
18835
18836                 /**
18837                  * Reflows the current container and it's children and possible parents.
18838                  * This should be used after you for example append children to the current control so
18839                  * that the layout managers know that they need to reposition everything.
18840                  *
18841                  * @example
18842                  * container.append({type: 'button', text: 'My button'}).reflow();
18843                  *
18844                  * @method reflow
18845                  * @return {tinymce.ui.Container} Current container instance.
18846                  */
18847                 reflow: function() {
18848                         var i, items;
18849
18850                         if (this.visible()) {
18851                                 Control.repaintControls = [];
18852                                 Control.repaintControls.map = {};
18853
18854                                 items = this.recalc();
18855                                 i = Control.repaintControls.length;
18856
18857                                 while (i--) {
18858                                         Control.repaintControls[i].repaint();
18859                                 }
18860
18861                                 // TODO: Fix me!
18862                                 if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
18863                                         this.repaint();
18864                                 }
18865
18866                                 Control.repaintControls = [];
18867                         }
18868
18869                         return this;
18870                 }
18871         });
18872 });
18873
18874 // Included from: js/tinymce/classes/ui/DragHelper.js
18875
18876 /**
18877  * DragHelper.js
18878  *
18879  * Copyright, Moxiecode Systems AB
18880  * Released under LGPL License.
18881  *
18882  * License: http://www.tinymce.com/license
18883  * Contributing: http://www.tinymce.com/contributing
18884  */
18885
18886 /**
18887  * Drag/drop helper class.
18888  *
18889  * @example
18890  * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
18891  *     start: function(evt) {
18892  *     },
18893  *
18894  *     drag: function(evt) {
18895  *     },
18896  *
18897  *     end: function(evt) {
18898  *     }
18899  * });
18900  *
18901  * @class tinymce.ui.DragHelper
18902  */
18903 define("tinymce/ui/DragHelper", [
18904         "tinymce/ui/DomUtils"
18905 ], function(DomUtils) {
18906         "use strict";
18907
18908         function getDocumentSize() {
18909                 var doc = document, documentElement, body, scrollWidth, clientWidth;
18910                 var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
18911
18912                 documentElement = doc.documentElement;
18913                 body = doc.body;
18914
18915                 scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
18916                 clientWidth = max(documentElement.clientWidth, body.clientWidth);
18917                 offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
18918
18919                 scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
18920                 clientHeight = max(documentElement.clientHeight, body.clientHeight);
18921                 offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
18922
18923                 return {
18924                         width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
18925                         height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
18926                 };
18927         }
18928
18929         return function(id, settings) {
18930                 var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY;
18931
18932                 settings = settings || {};
18933
18934                 function getHandleElm() {
18935                         return doc.getElementById(settings.handle || id);
18936                 }
18937
18938                 start = function(e) {
18939                         var docSize = getDocumentSize(), handleElm, cursor;
18940
18941                         e.preventDefault();
18942                         downButton = e.button;
18943                         handleElm = getHandleElm();
18944                         startX = e.screenX;
18945                         startY = e.screenY;
18946
18947                         // Grab cursor from handle
18948                         if (window.getComputedStyle) {
18949                                 cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
18950                         } else {
18951                                 cursor = handleElm.runtimeStyle.cursor;
18952                         }
18953
18954                         // Create event overlay and add it to document
18955                         eventOverlayElm = doc.createElement('div');
18956                         DomUtils.css(eventOverlayElm, {
18957                                 position: "absolute",
18958                                 top: 0, left: 0,
18959                                 width: docSize.width,
18960                                 height: docSize.height,
18961                                 zIndex: 0x7FFFFFFF,
18962                                 opacity: 0.0001,
18963                                 background: 'red',
18964                                 cursor: cursor
18965                         });
18966
18967                         doc.body.appendChild(eventOverlayElm);
18968
18969                         // Bind mouse events
18970                         DomUtils.on(doc, 'mousemove', drag);
18971                         DomUtils.on(doc, 'mouseup', stop);
18972
18973                         // Begin drag
18974                         settings.start(e);
18975                 };
18976
18977                 drag = function(e) {
18978                         if (e.button !== downButton) {
18979                                 return stop(e);
18980                         }
18981
18982                         e.deltaX = e.screenX - startX;
18983                         e.deltaY = e.screenY - startY;
18984
18985                         e.preventDefault();
18986                         settings.drag(e);
18987                 };
18988
18989                 stop = function(e) {
18990                         DomUtils.off(doc, 'mousemove', drag);
18991                         DomUtils.off(doc, 'mouseup', stop);
18992
18993                         eventOverlayElm.parentNode.removeChild(eventOverlayElm);
18994
18995                         if (settings.stop) {
18996                                 settings.stop(e);
18997                         }
18998                 };
18999
19000                 /**
19001                  * Destroys the drag/drop helper instance.
19002                  *
19003                  * @method destroy
19004                  */
19005                 this.destroy = function() {
19006                         DomUtils.off(getHandleElm());
19007                 };
19008
19009                 DomUtils.on(getHandleElm(), 'mousedown', start);
19010         };
19011 });
19012
19013 // Included from: js/tinymce/classes/ui/Scrollable.js
19014
19015 /**
19016  * Scrollable.js
19017  *
19018  * Copyright, Moxiecode Systems AB
19019  * Released under LGPL License.
19020  *
19021  * License: http://www.tinymce.com/license
19022  * Contributing: http://www.tinymce.com/contributing
19023  */
19024
19025 /**
19026  * This mixin makes controls scrollable using custom scrollbars.
19027  *
19028  * @-x-less Scrollable.less
19029  * @mixin tinymce.ui.Scrollable
19030  */
19031 define("tinymce/ui/Scrollable", [
19032         "tinymce/ui/DomUtils",
19033         "tinymce/ui/DragHelper"
19034 ], function(DomUtils, DragHelper) {
19035         "use strict";
19036
19037         return {
19038                 init: function() {
19039                         var self = this;
19040                         self.on('repaint', self.renderScroll);
19041                 },
19042
19043                 renderScroll: function() {
19044                         var self = this, margin = 2;
19045
19046                         function repaintScroll() {
19047                                 var hasScrollH, hasScrollV, bodyElm;
19048
19049                                 function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
19050                                         var containerElm, scrollBarElm, scrollThumbElm;
19051                                         var containerSize, scrollSize, ratio, rect;
19052                                         var posNameLower, sizeNameLower;
19053
19054                                         scrollBarElm = self.getEl('scroll' + axisName);
19055                                         if (scrollBarElm) {
19056                                                 posNameLower = posName.toLowerCase();
19057                                                 sizeNameLower = sizeName.toLowerCase();
19058
19059                                                 if (self.getEl('absend')) {
19060                                                         DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1);
19061                                                 }
19062
19063                                                 if (!hasScroll) {
19064                                                         DomUtils.css(scrollBarElm, 'display', 'none');
19065                                                         return;
19066                                                 }
19067
19068                                                 DomUtils.css(scrollBarElm, 'display', 'block');
19069                                                 containerElm = self.getEl('body');
19070                                                 scrollThumbElm = self.getEl('scroll' + axisName + "t");
19071                                                 containerSize = containerElm["client" + sizeName] - (margin * 2);
19072                                                 containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
19073                                                 scrollSize = containerElm["scroll" + sizeName];
19074                                                 ratio = containerSize / scrollSize;
19075
19076                                                 rect = {};
19077                                                 rect[posNameLower] = containerElm["offset" + posName] + margin;
19078                                                 rect[sizeNameLower] = containerSize;
19079                                                 DomUtils.css(scrollBarElm, rect);
19080
19081                                                 rect = {};
19082                                                 rect[posNameLower] = containerElm["scroll" + posName] * ratio;
19083                                                 rect[sizeNameLower] = containerSize * ratio;
19084                                                 DomUtils.css(scrollThumbElm, rect);
19085                                         }
19086                                 }
19087
19088                                 bodyElm = self.getEl('body');
19089                                 hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
19090                                 hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
19091
19092                                 repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
19093                                 repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
19094                         }
19095
19096                         function addScroll() {
19097                                 function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
19098                                         var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
19099
19100                                         self.getEl().appendChild(DomUtils.createFragment(
19101                                                 '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
19102                                                         '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
19103                                                 '</div>'
19104                                         ));
19105
19106                                         self.draghelper = new DragHelper(axisId + 't', {
19107                                                 start: function() {
19108                                                         scrollStart = self.getEl('body')["scroll" + posName];
19109                                                         DomUtils.addClass(DomUtils.get(axisId), prefix + 'active');
19110                                                 },
19111
19112                                                 drag: function(e) {
19113                                                         var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
19114
19115                                                         hasScrollH = layoutRect.contentW > layoutRect.innerW;
19116                                                         hasScrollV = layoutRect.contentH > layoutRect.innerH;
19117                                                         containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
19118                                                         containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
19119
19120                                                         ratio = containerSize / self.getEl('body')["scroll" + sizeName];
19121                                                         self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
19122                                                 },
19123
19124                                                 stop: function() {
19125                                                         DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active');
19126                                                 }
19127                                         });
19128 /*
19129                                         self.on('click', function(e) {
19130                                                 if (e.target.id == self._id + '-scrollv') {
19131
19132                                                 }
19133                                         });*/
19134                                 }
19135
19136                                 self.addClass('scroll');
19137
19138                                 addScrollAxis("v", "Top", "Height", "Y", "Width");
19139                                 addScrollAxis("h", "Left", "Width", "X", "Height");
19140                         }
19141
19142                         if (self.settings.autoScroll) {
19143                                 if (!self._hasScroll) {
19144                                         self._hasScroll = true;
19145                                         addScroll();
19146
19147                                         self.on('wheel', function(e) {
19148                                                 var bodyEl = self.getEl('body');
19149
19150                                                 bodyEl.scrollLeft += (e.deltaX || 0) * 10;
19151                                                 bodyEl.scrollTop += e.deltaY * 10;
19152
19153                                                 repaintScroll();
19154                                         });
19155
19156                                         DomUtils.on(self.getEl('body'), "scroll", repaintScroll);
19157                                 }
19158
19159                                 repaintScroll();
19160                         }
19161                 }
19162         };
19163 });
19164
19165 // Included from: js/tinymce/classes/ui/Panel.js
19166
19167 /**
19168  * Panel.js
19169  *
19170  * Copyright, Moxiecode Systems AB
19171  * Released under LGPL License.
19172  *
19173  * License: http://www.tinymce.com/license
19174  * Contributing: http://www.tinymce.com/contributing
19175  */
19176
19177 /**
19178  * Creates a new panel.
19179  *
19180  * @-x-less Panel.less
19181  * @class tinymce.ui.Panel
19182  * @extends tinymce.ui.Container
19183  * @mixes tinymce.ui.Scrollable
19184  */
19185 define("tinymce/ui/Panel", [
19186         "tinymce/ui/Container",
19187         "tinymce/ui/Scrollable"
19188 ], function(Container, Scrollable) {
19189         "use strict";
19190
19191         return Container.extend({
19192                 Defaults: {
19193                         layout: 'fit',
19194                         containerCls: 'panel'
19195                 },
19196
19197                 Mixins: [Scrollable],
19198
19199                 /**
19200                  * Renders the control as a HTML string.
19201                  *
19202                  * @method renderHtml
19203                  * @return {String} HTML representing the control.
19204                  */
19205                 renderHtml: function() {
19206                         var self = this, layout = self._layout, innerHtml = self.settings.html;
19207
19208                         self.preRender();
19209                         layout.preRender(self);
19210
19211                         if (typeof(innerHtml) == "undefined") {
19212                                 innerHtml = (
19213                                         '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
19214                                                 layout.renderHtml(self) +
19215                                         '</div>'
19216                                 );
19217                         } else {
19218                                 if (typeof(innerHtml) == 'function') {
19219                                         innerHtml = innerHtml.call(self);
19220                                 }
19221
19222                                 self._hasBody = false;
19223                         }
19224
19225                         return (
19226                                 '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
19227                                         (self._preBodyHtml || '') +
19228                                         innerHtml +
19229                                 '</div>'
19230                         );
19231                 }
19232         });
19233 });
19234
19235 // Included from: js/tinymce/classes/ui/Movable.js
19236
19237 /**
19238  * Movable.js
19239  *
19240  * Copyright, Moxiecode Systems AB
19241  * Released under LGPL License.
19242  *
19243  * License: http://www.tinymce.com/license
19244  * Contributing: http://www.tinymce.com/contributing
19245  */
19246
19247 /**
19248  * Movable mixin. Makes controls movable absolute and relative to other elements.
19249  *
19250  * @mixin tinymce.ui.Movable
19251  */
19252 define("tinymce/ui/Movable", [
19253         "tinymce/ui/DomUtils"
19254 ], function(DomUtils) {
19255         "use strict";
19256
19257         function calculateRelativePosition(ctrl, targetElm, rel) {
19258                 var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport;
19259
19260                 viewport = DomUtils.getViewPort();
19261
19262                 // Get pos of target
19263                 pos = DomUtils.getPos(targetElm);
19264                 x = pos.x;
19265                 y = pos.y;
19266
19267                 if (ctrl._fixed) {
19268                         x -= viewport.x;
19269                         y -= viewport.y;
19270                 }
19271
19272                 // Get size of self
19273                 ctrlElm = ctrl.getEl();
19274                 selfW = ctrlElm.offsetWidth;
19275                 selfH = ctrlElm.offsetHeight;
19276
19277                 // Get size of target
19278                 targetW = targetElm.offsetWidth;
19279                 targetH = targetElm.offsetHeight;
19280
19281                 // Parse align string
19282                 rel = (rel || '').split('');
19283
19284                 // Target corners
19285                 if (rel[0] === 'b') {
19286                         y += targetH;
19287                 }
19288
19289                 if (rel[1] === 'r') {
19290                         x += targetW;
19291                 }
19292
19293                 if (rel[0] === 'c') {
19294                         y += Math.round(targetH / 2);
19295                 }
19296
19297                 if (rel[1] === 'c') {
19298                         x += Math.round(targetW / 2);
19299                 }
19300
19301                 // Self corners
19302                 if (rel[3] === 'b') {
19303                         y -= selfH;
19304                 }
19305
19306                 if (rel[4] === 'r') {
19307                         x -= selfW;
19308                 }
19309
19310                 if (rel[3] === 'c') {
19311                         y -= Math.round(selfH / 2);
19312                 }
19313
19314                 if (rel[4] === 'c') {
19315                         x -= Math.round(selfW / 2);
19316                 }
19317
19318                 return {
19319                         x: x,
19320                         y: y,
19321                         w: selfW,
19322                         h: selfH
19323                 };
19324         }
19325
19326         return {
19327                 /**
19328                  * Tests various positions to get the most suitable one.
19329                  *
19330                  * @method testMoveRel
19331                  * @param {DOMElement} elm Element to position against.
19332                  * @param {Array} rels Array with relative positions.
19333                  * @return {String} Best suitable relative position.
19334                  */
19335                 testMoveRel: function(elm, rels) {
19336                         var viewPortRect = DomUtils.getViewPort();
19337
19338                         for (var i = 0; i < rels.length; i++) {
19339                                 var pos = calculateRelativePosition(this, elm, rels[i]);
19340
19341                                 if (this._fixed) {
19342                                         if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
19343                                                 return rels[i];
19344                                         }
19345                                 } else {
19346                                         if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
19347                                                 pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
19348                                                 return rels[i];
19349                                         }
19350                                 }
19351                         }
19352
19353                         return rels[0];
19354                 },
19355
19356                 /**
19357                  * Move relative to the specified element.
19358                  *
19359                  * @method moveRel
19360                  * @param {Element} elm Element to move relative to.
19361                  * @param {String} rel Relative mode. For example: br-tl.
19362                  * @return {tinymce.ui.Control} Current control instance.
19363                  */
19364                 moveRel: function(elm, rel) {
19365                         if (typeof(rel) != 'string') {
19366                                 rel = this.testMoveRel(elm, rel);
19367                         }
19368
19369                         var pos = calculateRelativePosition(this, elm, rel);
19370                         return this.moveTo(pos.x, pos.y);
19371                 },
19372
19373                 /**
19374                  * Move by a relative x, y values.
19375                  *
19376                  * @method moveBy
19377                  * @param {Number} dx Relative x position.
19378                  * @param {Number} dy Relative y position.
19379                  * @return {tinymce.ui.Control} Current control instance.
19380                  */
19381                 moveBy: function(dx, dy) {
19382                         var self = this, rect = self.layoutRect();
19383
19384                         self.moveTo(rect.x + dx, rect.y + dy);
19385
19386                         return self;
19387                 },
19388
19389                 /**
19390                  * Move to absolute position.
19391                  *
19392                  * @method moveTo
19393                  * @param {Number} x Absolute x position.
19394                  * @param {Number} y Absolute y position.
19395                  * @return {tinymce.ui.Control} Current control instance.
19396                  */
19397                 moveTo: function(x, y) {
19398                         var self = this;
19399
19400                         // TODO: Move this to some global class
19401                         function contrain(value, max, size) {
19402                                 if (value < 0) {
19403                                         return 0;
19404                                 }
19405
19406                                 if (value + size > max) {
19407                                         value = max - size;
19408                                         return value < 0 ? 0 : value;
19409                                 }
19410
19411                                 return value;
19412                         }
19413
19414                         if (self.settings.constrainToViewport) {
19415                                 var viewPortRect = DomUtils.getViewPort(window);
19416                                 var layoutRect = self.layoutRect();
19417
19418                                 x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
19419                                 y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
19420                         }
19421
19422                         if (self._rendered) {
19423                                 self.layoutRect({x: x, y: y}).repaint();
19424                         } else {
19425                                 self.settings.x = x;
19426                                 self.settings.y = y;
19427                         }
19428
19429                         self.fire('move', {x: x, y: y});
19430
19431                         return self;
19432                 }
19433         };
19434 });
19435
19436 // Included from: js/tinymce/classes/ui/Resizable.js
19437
19438 /**
19439  * Resizable.js
19440  *
19441  * Copyright, Moxiecode Systems AB
19442  * Released under LGPL License.
19443  *
19444  * License: http://www.tinymce.com/license
19445  * Contributing: http://www.tinymce.com/contributing
19446  */
19447
19448 /**
19449  * Resizable mixin. Enables controls to be resized.
19450  *
19451  * @mixin tinymce.ui.Resizable
19452  */
19453 define("tinymce/ui/Resizable", [
19454         "tinymce/ui/DomUtils"
19455 ], function(DomUtils) {
19456         "use strict";
19457
19458         return {
19459                 /** 
19460                  * Resizes the control to contents.
19461                  *
19462                  * @method resizeToContent
19463                  */
19464                 resizeToContent: function() {
19465                         this._layoutRect.autoResize = true;
19466                         this._lastRect = null;
19467                         this.reflow();
19468                 },
19469
19470                 /** 
19471                  * Resizes the control to a specific width/height.
19472                  *
19473                  * @method resizeTo
19474                  * @param {Number} w Control width.
19475                  * @param {Number} h Control height.
19476                  * @return {tinymce.ui.Control} Current control instance.
19477                  */
19478                 resizeTo: function(w, h) {
19479                         // TODO: Fix hack
19480                         if (w <= 1 || h <= 1) {
19481                                 var rect = DomUtils.getWindowSize();
19482
19483                                 w = w <= 1 ? w * rect.w : w;
19484                                 h = h <= 1 ? h * rect.h : h;
19485                         }
19486
19487                         this._layoutRect.autoResize = false;
19488                         return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
19489                 },
19490
19491                 /** 
19492                  * Resizes the control to a specific relative width/height.
19493                  *
19494                  * @method resizeBy
19495                  * @param {Number} dw Relative control width.
19496                  * @param {Number} dh Relative control height.
19497                  * @return {tinymce.ui.Control} Current control instance.
19498                  */
19499                 resizeBy: function(dw, dh) {
19500                         var self = this, rect = self.layoutRect();
19501
19502                         return self.resizeTo(rect.w + dw, rect.h + dh);
19503                 }
19504         };
19505 });
19506
19507 // Included from: js/tinymce/classes/ui/FloatPanel.js
19508
19509 /**
19510  * FloatPanel.js
19511  *
19512  * Copyright, Moxiecode Systems AB
19513  * Released under LGPL License.
19514  *
19515  * License: http://www.tinymce.com/license
19516  * Contributing: http://www.tinymce.com/contributing
19517  */
19518
19519 /**
19520  * This class creates a floating panel.
19521  *
19522  * @-x-less FloatPanel.less
19523  * @class tinymce.ui.FloatPanel
19524  * @extends tinymce.ui.Panel
19525  * @mixes tinymce.ui.Movable
19526  * @mixes tinymce.ui.Resizable
19527  */
19528 define("tinymce/ui/FloatPanel", [
19529         "tinymce/ui/Panel",
19530         "tinymce/ui/Movable",
19531         "tinymce/ui/Resizable",
19532         "tinymce/ui/DomUtils"
19533 ], function(Panel, Movable, Resizable, DomUtils) {
19534         "use strict";
19535
19536         var documentClickHandler, documentScrollHandler, visiblePanels = [];
19537         var zOrder = [], hasModal;
19538
19539         var FloatPanel = Panel.extend({
19540                 Mixins: [Movable, Resizable],
19541
19542                 /**
19543                  * Constructs a new control instance with the specified settings.
19544                  *
19545                  * @constructor
19546                  * @param {Object} settings Name/value object with settings.
19547                  * @setting {Boolean} autohide Automatically hide the panel.
19548                  */
19549                 init: function(settings) {
19550                         var self = this;
19551
19552                         function reorder() {
19553                                 var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
19554
19555                                 if (zOrder.length) {
19556                                         for (i = 0; i < zOrder.length; i++) {
19557                                                 if (zOrder[i].modal) {
19558                                                         zIndex++;
19559                                                         topModal = zOrder[i];
19560                                                 }
19561
19562                                                 zOrder[i].getEl().style.zIndex = zIndex;
19563                                                 zOrder[i].zIndex = zIndex;
19564                                                 zIndex++;
19565                                         }
19566                                 }
19567
19568                                 var modalBlockEl = document.getElementById(self.classPrefix + 'modal-block');
19569
19570                                 if (topModal) {
19571                                         DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
19572                                 } else if (modalBlockEl) {
19573                                         modalBlockEl.parentNode.removeChild(modalBlockEl);
19574                                         hasModal = false;
19575                                 }
19576
19577                                 FloatPanel.currentZIndex = zIndex;
19578                         }
19579
19580                         function isChildOf(ctrl, parent) {
19581                                 while (ctrl) {
19582                                         if (ctrl == parent) {
19583                                                 return true;
19584                                         }
19585
19586                                         ctrl = ctrl.parent();
19587                                 }
19588                         }
19589
19590                         /**
19591                          * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
19592                          * also reposition all child panels of the current panel.
19593                          */
19594                         function repositionPanel(panel) {
19595                                 var scrollY = DomUtils.getViewPort().y;
19596
19597                                 function toggleFixedChildPanels(fixed, deltaY) {
19598                                         var parent;
19599
19600                                         for (var i = 0; i < visiblePanels.length; i++) {
19601                                                 if (visiblePanels[i] != panel) {
19602                                                         parent = visiblePanels[i].parent();
19603
19604                                                         while (parent && (parent = parent.parent())) {
19605                                                                 if (parent == panel) {
19606                                                                         visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
19607                                                                 }
19608                                                         }
19609                                                 }
19610                                         }
19611                                 }
19612
19613                                 if (panel.settings.autofix) {
19614                                         if (!panel._fixed) {
19615                                                 panel._autoFixY = panel.layoutRect().y;
19616
19617                                                 if (panel._autoFixY < scrollY) {
19618                                                         panel.fixed(true).layoutRect({y: 0}).repaint();
19619                                                         toggleFixedChildPanels(true, scrollY - panel._autoFixY);
19620                                                 }
19621                                         } else {
19622                                                 if (panel._autoFixY > scrollY) {
19623                                                         panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
19624                                                         toggleFixedChildPanels(false, panel._autoFixY - scrollY);
19625                                                 }
19626                                         }
19627                                 }
19628                         }
19629
19630                         self._super(settings);
19631                         self._eventsRoot = self;
19632
19633                         self.addClass('floatpanel');
19634
19635                         // Hide floatpanes on click out side the root button
19636                         if (settings.autohide) {
19637                                 if (!documentClickHandler) {
19638                                         documentClickHandler = function(e) {
19639                                                 var i, clickCtrl = self.getParentCtrl(e.target);
19640
19641                                                 // Hide any float panel when a click is out side that float panel and the
19642                                                 // float panels direct parent for example a click on a menu button
19643                                                 i = visiblePanels.length;
19644                                                 while (i--) {
19645                                                         var panel = visiblePanels[i];
19646
19647                                                         if (panel.settings.autohide) {
19648                                                                 if (clickCtrl) {
19649                                                                         if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
19650                                                                                 continue;
19651                                                                         }
19652                                                                 }
19653
19654                                                                 e = panel.fire('autohide', {target: e.target});
19655                                                                 if (!e.isDefaultPrevented()) {
19656                                                                         panel.hide();
19657                                                                 }
19658                                                         }
19659                                                 }
19660                                         };
19661
19662                                         DomUtils.on(document, 'click', documentClickHandler);
19663                                 }
19664
19665                                 visiblePanels.push(self);
19666                         }
19667
19668                         if (settings.autofix) {
19669                                 if (!documentScrollHandler) {
19670                                         documentScrollHandler = function() {
19671                                                 var i;
19672
19673                                                 i = visiblePanels.length;
19674                                                 while (i--) {
19675                                                         repositionPanel(visiblePanels[i]);
19676                                                 }
19677                                         };
19678
19679                                         DomUtils.on(window, 'scroll', documentScrollHandler);
19680                                 }
19681
19682                                 self.on('move', function() {
19683                                         repositionPanel(this);
19684                                 });
19685                         }
19686
19687                         self.on('postrender show', function(e) {
19688                                 if (e.control == self) {
19689                                         var modalBlockEl, prefix = self.classPrefix;
19690
19691                                         if (self.modal && !hasModal) {
19692                                                 modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' +
19693                                                         prefix + 'reset ' + prefix + 'fade"></div>');
19694                                                 modalBlockEl = modalBlockEl.firstChild;
19695
19696                                                 self.getContainerElm().appendChild(modalBlockEl);
19697
19698                                                 setTimeout(function() {
19699                                                         DomUtils.addClass(modalBlockEl, prefix + 'in');
19700                                                         DomUtils.addClass(self.getEl(), prefix + 'in');
19701                                                 }, 0);
19702
19703                                                 hasModal = true;
19704                                         }
19705
19706                                         zOrder.push(self);
19707                                         reorder();
19708                                 }
19709                         });
19710
19711                         self.on('close hide', function(e) {
19712                                 if (e.control == self) {
19713                                         var i = zOrder.length;
19714
19715                                         while (i--) {
19716                                                 if (zOrder[i] === self) {
19717                                                         zOrder.splice(i, 1);
19718                                                 }
19719                                         }
19720
19721                                         reorder();
19722                                 }
19723                         });
19724
19725                         self.on('show', function() {
19726                                 self.parents().each(function(ctrl) {
19727                                         if (ctrl._fixed) {
19728                                                 self.fixed(true);
19729                                                 return false;
19730                                         }
19731                                 });
19732                         });
19733
19734                         if (settings.popover) {
19735                                 self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
19736                                 self.addClass('popover').addClass('bottom').addClass('start');
19737                         }
19738                 },
19739
19740                 fixed: function(state) {
19741                         var self = this;
19742
19743                         if (self._fixed != state) {
19744                                 if (self._rendered) {
19745                                         var viewport = DomUtils.getViewPort();
19746
19747                                         if (state) {
19748                                                 self.layoutRect().y -= viewport.y;
19749                                         } else {
19750                                                 self.layoutRect().y += viewport.y;
19751                                         }
19752                                 }
19753
19754                                 self.toggleClass('fixed', state);
19755                                 self._fixed = state;
19756                         }
19757
19758                         return self;
19759                 },
19760
19761                 /**
19762                  * Shows the current float panel.
19763                  *
19764                  * @method show
19765                  * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
19766                  */
19767                 show: function() {
19768                         var self = this, i, state = self._super();
19769
19770                         i = visiblePanels.length;
19771                         while (i--) {
19772                                 if (visiblePanels[i] === self) {
19773                                         break;
19774                                 }
19775                         }
19776
19777                         if (i === -1) {
19778                                 visiblePanels.push(self);
19779                         }
19780
19781                         return state;
19782                 },
19783
19784                 /**
19785                  * Hides the current float panel.
19786                  *
19787                  * @method hide
19788                  * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
19789                  */
19790                 hide: function() {
19791                         removeVisiblePanel(this);
19792                         return this._super();
19793                 },
19794
19795                 /**
19796                  * Hides all visible the float panels.
19797                  *
19798                  * @method hideAll
19799                  */
19800                 hideAll: function() {
19801                         FloatPanel.hideAll();
19802                 },
19803
19804                 /**
19805                  * Closes the float panel. This will remove the float panel from page and fire the close event.
19806                  *
19807                  * @method close
19808                  */
19809                 close: function() {
19810                         var self = this;
19811
19812                         self.fire('close');
19813
19814                         return self.remove();
19815                 },
19816
19817                 /**
19818                  * Removes the float panel from page.
19819                  *
19820                  * @method remove
19821                  */
19822                 remove: function() {
19823                         removeVisiblePanel(this);
19824                         this._super();
19825                 }
19826         });
19827
19828         /**
19829          * Hides all visible the float panels.
19830          *
19831          * @static
19832          * @method hideAll
19833          */
19834         FloatPanel.hideAll = function() {
19835                 var i = visiblePanels.length;
19836
19837                 while (i--) {
19838                         var panel = visiblePanels[i];
19839
19840                         if (panel.settings.autohide) {
19841                                 panel.fire('cancel', {}, false);
19842                                 panel.hide();
19843                                 visiblePanels.splice(i, 1);
19844                         }
19845                 }
19846         };
19847
19848         function removeVisiblePanel(panel) {
19849                 var i;
19850
19851                 i = visiblePanels.length;
19852                 while (i--) {
19853                         if (visiblePanels[i] === panel) {
19854                                 visiblePanels.splice(i, 1);
19855                         }
19856                 }
19857         }
19858
19859         return FloatPanel;
19860 });
19861
19862 // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
19863
19864 /**
19865  * KeyboardNavigation.js
19866  *
19867  * Copyright, Moxiecode Systems AB
19868  * Released under LGPL License.
19869  *
19870  * License: http://www.tinymce.com/license
19871  * Contributing: http://www.tinymce.com/contributing
19872  */
19873
19874 /**
19875  * This class handles keyboard navigation of controls and elements.
19876  *
19877  * @class tinymce.ui.KeyboardNavigation
19878  */
19879 define("tinymce/ui/KeyboardNavigation", [
19880         "tinymce/ui/DomUtils"
19881 ], function(DomUtils) {
19882         "use strict";
19883
19884         /**
19885          * Create a new KeyboardNavigation instance to handle the focus for a specific element.
19886          *
19887          * @constructor
19888          * @param {Object} settings the settings object to define how keyboard navigation works.
19889          *
19890          * @setting {tinymce.ui.Control} root the root control navigation focus movement is scoped to this root.
19891          * @setting {Array} items an array containing the items to move focus between. Every object in this array must have an
19892          *                        id attribute which maps to the actual DOM element and it must be able to have focus i.e. tabIndex=-1.
19893          * @setting {Function} onCancel the callback for when the user presses escape or otherwise indicates canceling.
19894          * @setting {Function} onAction (optional) the action handler to call when the user activates an item.
19895          * @setting {Boolean} enableLeftRight (optional, default) when true, the up/down arrows move through items.
19896          * @setting {Boolean} enableUpDown (optional) when true, the up/down arrows move through items.
19897          * Note for both up/down and left/right explicitly set both enableLeftRight and enableUpDown to true.
19898          */
19899         return function(settings) {
19900                 var root = settings.root, enableUpDown = settings.enableUpDown !== false;
19901                 var enableLeftRight = settings.enableLeftRight !== false;
19902                 var items = settings.items, focussedId;
19903
19904                 /**
19905                  * Initializes the items array if needed. This will collect items/elements
19906                  * from the specified root control.
19907                  */
19908                 function initItems() {
19909                         if (!items) {
19910                                 items = [];
19911
19912                                 if (root.find) {
19913                                         // Root is a container then get child elements using the UI API
19914                                         root.find('*').each(function(ctrl) {
19915                                                 if (ctrl.canFocus) {
19916                                                         items.push(ctrl.getEl());
19917                                                 }
19918                                         });
19919                                 } else {
19920                                         // Root is a control/widget then get the child elements of that control
19921                                         var elements = root.getEl().getElementsByTagName('*');
19922                                         for (var i = 0; i < elements.length; i++) {
19923                                                 if (elements[i].id && elements[i]) {
19924                                                         items.push(elements[i]);
19925                                                 }
19926                                         }
19927                                 }
19928                         }
19929                 }
19930
19931                 /**
19932                  * Returns the currently focused element.
19933                  *
19934                  * @private
19935                  * @return {Element} Currently focused element.
19936                  */
19937                 function getFocusElement() {
19938                         return document.getElementById(focussedId);
19939                 }
19940
19941                 /**
19942                  * Returns the currently focused elements wai aria role.
19943                  *
19944                  * @private
19945                  * @param {Element} elm Optional element to get role from.
19946                  * @return {String} Role of specified element.
19947                  */
19948                 function getRole(elm) {
19949                         elm = elm || getFocusElement();
19950
19951                         return elm && elm.getAttribute('role');
19952                 }
19953
19954                 /**
19955                  * Returns the role of the parent element.
19956                  *
19957                  * @private
19958                  * @param {Element} elm Optional element to get parent role from.
19959                  * @return {String} Role of the first parent that has a role.
19960                  */
19961                 function getParentRole(elm) {
19962                         var role, parent = elm || getFocusElement();
19963
19964                         while ((parent = parent.parentNode)) {
19965                                 if ((role = getRole(parent))) {
19966                                         return role;
19967                                 }
19968                         }
19969                 }
19970
19971                 /**
19972                  * Returns an wai aria property by name.
19973                  *
19974                  * @private
19975                  * @param {String} name Name of the aria property to get for example "disabled".
19976                  * @return {String} Aria property value.
19977                  */
19978                 function getAriaProp(name) {
19979                         var elm = document.getElementById(focussedId);
19980
19981                         if (elm) {
19982                                 return elm.getAttribute('aria-' + name);
19983                         }
19984                 }
19985
19986                 /**
19987                  * Executes the onAction event callback. This is when the user presses enter/space.
19988                  *
19989                  * @private
19990                  */
19991                 function action() {
19992                         var focusElm = getFocusElement();
19993
19994                         if (focusElm && (focusElm.nodeName == "TEXTAREA" || focusElm.type == "text")) {
19995                                 return;
19996                         }
19997
19998                         if (settings.onAction) {
19999                                 settings.onAction(focussedId);
20000                         } else {
20001                                 DomUtils.fire(getFocusElement(), 'click', {keyboard: true});
20002                         }
20003
20004                         return true;
20005                 }
20006
20007                 /**
20008                  * Cancels the current navigation. The same as pressing the Esc key.
20009                  *
20010                  * @method cancel
20011                  */
20012                 function cancel() {
20013                         var focusElm;
20014
20015                         if (settings.onCancel) {
20016                                 if ((focusElm = getFocusElement())) {
20017                                         focusElm.blur();
20018                                 }
20019
20020                                 settings.onCancel();
20021                         } else {
20022                                 settings.root.fire('cancel');
20023                         }
20024                 }
20025
20026                 /**
20027                  * Moves the focus to the next or previous item. It will wrap to start/end if it can't move.
20028                  *
20029                  * @method moveFocus
20030                  * @param {Number} dir Direction for move -1 or 1.
20031                  */
20032                 function moveFocus(dir) {
20033                         var idx = -1, focusElm, i;
20034                         var visibleItems = [];
20035
20036                         function isVisible(elm) {
20037                                 var rootElm = root ? root.getEl() : document.body;
20038
20039                                 while (elm && elm != rootElm) {
20040                                         if (elm.style.display == 'none') {
20041                                                 return false;
20042                                         }
20043
20044                                         elm = elm.parentNode;
20045                                 }
20046
20047                                 return true;
20048                         }
20049
20050                         initItems();
20051
20052                         // TODO: Optimize this, will be slow on lots of items
20053                         i = visibleItems.length;
20054                         for (i = 0; i < items.length; i++) {
20055                                 if (isVisible(items[i])) {
20056                                         visibleItems.push(items[i]);
20057                                 }
20058                         }
20059
20060                         i = visibleItems.length;
20061                         while (i--) {
20062                                 if (visibleItems[i].id === focussedId) {
20063                                         idx = i;
20064                                         break;
20065                                 }
20066                         }
20067
20068                         idx += dir;
20069                         if (idx < 0) {
20070                                 idx = visibleItems.length - 1;
20071                         } else if (idx >= visibleItems.length) {
20072                                 idx = 0;
20073                         }
20074
20075                         focusElm = visibleItems[idx];
20076                         focusElm.focus();
20077                         focussedId = focusElm.id;
20078
20079                         if (settings.actOnFocus) {
20080                                 action();
20081                         }
20082                 }
20083
20084                 /**
20085                  * Moves focus to the first item or the last focused item if root is a toolbar.
20086                  *
20087                  * @method focusFirst
20088                  * @return {[type]} [description]
20089                  */
20090                 function focusFirst() {
20091                         var i, rootRole;
20092
20093                         rootRole = getRole(settings.root.getEl());
20094                         initItems();
20095
20096                         i = items.length;
20097                         while (i--) {
20098                                 if (rootRole == 'toolbar' && items[i].id === focussedId) {
20099                                         items[i].focus();
20100                                         return;
20101                                 }
20102                         }
20103
20104                         items[0].focus();
20105                 }
20106
20107                 // Handle accessible keys
20108                 root.on('keydown', function(e) {
20109                         var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
20110                         var DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32, DOM_VK_TAB = 9;
20111                         var preventDefault;
20112
20113                         switch (e.keyCode) {
20114                                 case DOM_VK_LEFT:
20115                                         if (enableLeftRight) {
20116                                                 if (settings.leftAction) {
20117                                                         settings.leftAction();
20118                                                 } else {
20119                                                         moveFocus(-1);
20120                                                 }
20121
20122                                                 preventDefault = true;
20123                                         }
20124                                         break;
20125
20126                                 case DOM_VK_RIGHT:
20127                                         if (enableLeftRight) {
20128                                                 if (getRole() == 'menuitem' && getParentRole() == 'menu') {
20129                                                         if (getAriaProp('haspopup')) {
20130                                                                 action();
20131                                                         }
20132                                                 } else {
20133                                                         moveFocus(1);
20134                                                 }
20135
20136                                                 preventDefault = true;
20137                                         }
20138                                         break;
20139
20140                                 case DOM_VK_UP:
20141                                         if (enableUpDown) {
20142                                                 moveFocus(-1);
20143                                                 preventDefault = true;
20144                                         }
20145                                         break;
20146
20147                                 case DOM_VK_DOWN:
20148                                         if (enableUpDown) {
20149                                                 if (getRole() == 'menuitem' && getParentRole() == 'menubar') {
20150                                                         action();
20151                                                 } else if (getRole() == 'button' && getAriaProp('haspopup')) {
20152                                                         action();
20153                                                 } else {
20154                                                         moveFocus(1);
20155                                                 }
20156
20157                                                 preventDefault = true;
20158                                         }
20159                                         break;
20160
20161                                 case DOM_VK_TAB:
20162                                         preventDefault = true;
20163
20164                                         if (e.shiftKey) {
20165                                                 moveFocus(-1);
20166                                         } else {
20167                                                 moveFocus(1);
20168                                         }
20169                                         break;
20170
20171                                 case DOM_VK_ESCAPE:
20172                                         preventDefault = true;
20173                                         cancel();
20174                                         break;
20175
20176                                 case DOM_VK_ENTER:
20177                                 case DOM_VK_RETURN:
20178                                 case DOM_VK_SPACE:
20179                                         preventDefault = action();
20180                                         break;
20181                         }
20182
20183                         if (preventDefault) {
20184                                 e.stopPropagation();
20185                                 e.preventDefault();
20186                         }
20187                 });
20188
20189                 // Init on focus in
20190                 root.on('focusin', function(e) {
20191                         initItems();
20192                         focussedId = e.target.id;
20193                 });
20194
20195                 return {
20196                         moveFocus: moveFocus,
20197                         focusFirst: focusFirst,
20198                         cancel: cancel
20199                 };
20200         };
20201 });
20202
20203 // Included from: js/tinymce/classes/ui/Window.js
20204
20205 /**
20206  * Window.js
20207  *
20208  * Copyright, Moxiecode Systems AB
20209  * Released under LGPL License.
20210  *
20211  * License: http://www.tinymce.com/license
20212  * Contributing: http://www.tinymce.com/contributing
20213  */
20214
20215 /**
20216  * Creates a new window.
20217  *
20218  * @-x-less Window.less
20219  * @class tinymce.ui.Window
20220  * @extends tinymce.ui.FloatPanel
20221  */
20222 define("tinymce/ui/Window", [
20223         "tinymce/ui/FloatPanel",
20224         "tinymce/ui/Panel",
20225         "tinymce/ui/DomUtils",
20226         "tinymce/ui/KeyboardNavigation",
20227         "tinymce/ui/DragHelper"
20228 ], function(FloatPanel, Panel, DomUtils, KeyboardNavigation, DragHelper) {
20229         "use strict";
20230
20231         var Window = FloatPanel.extend({
20232                 modal: true,
20233
20234                 Defaults: {
20235                         border: 1,
20236                         layout: 'flex',
20237                         containerCls: 'panel',
20238                         role: 'dialog',
20239                         callbacks: {
20240                                 submit: function() {
20241                                         this.fire('submit', {data: this.toJSON()});
20242                                 },
20243
20244                                 close: function() {
20245                                         this.close();
20246                                 }
20247                         }
20248                 },
20249
20250                 /**
20251                  * Constructs a instance with the specified settings.
20252                  *
20253                  * @constructor
20254                  * @param {Object} settings Name/value object with settings.
20255                  */
20256                 init: function(settings) {
20257                         var self = this;
20258
20259                         self._super(settings);
20260
20261                         self.addClass('window');
20262                         self._fixed = true;
20263
20264                         // Create statusbar
20265                         if (settings.buttons) {
20266                                 self.statusbar = new Panel({
20267                                         layout: 'flex',
20268                                         border: '1 0 0 0',
20269                                         spacing: 3,
20270                                         padding: 10,
20271                                         align: 'center',
20272                                         pack: 'end',
20273                                         defaults: {
20274                                                 type: 'button'
20275                                         },
20276                                         items: settings.buttons
20277                                 });
20278
20279                                 self.statusbar.addClass('foot');
20280                                 self.statusbar.parent(self);
20281                         }
20282
20283                         self.on('click', function(e) {
20284                                 if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
20285                                         self.close();
20286                                 }
20287                         });
20288
20289                         self.aria('label', settings.title);
20290                         self._fullscreen = false;
20291                 },
20292
20293                 /**
20294                  * Recalculates the positions of the controls in the current container.
20295                  * This is invoked by the reflow method and shouldn't be called directly.
20296                  *
20297                  * @method recalc
20298                  */
20299                 recalc: function() {
20300                         var self = this, statusbar = self.statusbar, layoutRect, width, needsRecalc;
20301
20302                         if (self._fullscreen) {
20303                                 self.layoutRect(DomUtils.getWindowSize());
20304                                 self.layoutRect().contentH = self.layoutRect().innerH;
20305                         }
20306
20307                         self._super();
20308
20309                         layoutRect = self.layoutRect();
20310
20311                         // Resize window based on title width
20312                         if (self.settings.title && !self._fullscreen) {
20313                                 width = layoutRect.headerW;
20314                                 if (width > layoutRect.w) {
20315                                         self.layoutRect({w: width});
20316                                         needsRecalc = true;
20317                                 }
20318                         }
20319
20320                         // Resize window based on statusbar width
20321                         if (statusbar) {
20322                                 statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
20323
20324                                 width = statusbar.layoutRect().minW + layoutRect.deltaW;
20325                                 if (width > layoutRect.w) {
20326                                         self.layoutRect({w: width});
20327                                         needsRecalc = true;
20328                                 }
20329                         }
20330
20331                         // Recalc body and disable auto resize
20332                         if (needsRecalc) {
20333                                 self.recalc();
20334                         }
20335                 },
20336
20337                 /**
20338                  * Initializes the current controls layout rect.
20339                  * This will be executed by the layout managers to determine the
20340                  * default minWidth/minHeight etc.
20341                  *
20342                  * @method initLayoutRect
20343                  * @return {Object} Layout rect instance.
20344                  */
20345                 initLayoutRect: function() {
20346                         var self = this, layoutRect = self._super(), deltaH = 0, headEl;
20347
20348                         // Reserve vertical space for title
20349                         if (self.settings.title && !self._fullscreen) {
20350                                 headEl = self.getEl('head');
20351                                 layoutRect.headerW = headEl.offsetWidth;
20352                                 layoutRect.headerH = headEl.offsetHeight;
20353                                 deltaH += layoutRect.headerH;
20354                         }
20355
20356                         // Reserve vertical space for statusbar
20357                         if (self.statusbar) {
20358                                 deltaH += self.statusbar.layoutRect().h;
20359                         }
20360
20361                         layoutRect.deltaH += deltaH;
20362                         layoutRect.minH += deltaH;
20363                         //layoutRect.innerH -= deltaH;
20364                         layoutRect.h += deltaH;
20365
20366                         var rect = DomUtils.getWindowSize();
20367
20368                         layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2);
20369                         layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2);
20370
20371                         return layoutRect;
20372                 },
20373
20374                 /**
20375                  * Renders the control as a HTML string.
20376                  *
20377                  * @method renderHtml
20378                  * @return {String} HTML representing the control.
20379                  */
20380                 renderHtml: function() {
20381                         var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
20382                         var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
20383
20384                         self.preRender();
20385                         layout.preRender(self);
20386
20387                         if (settings.title) {
20388                                 headerHtml = (
20389                                         '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
20390                                                 '<div class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
20391                                                 '<button type="button" class="' + prefix + 'close" aria-hidden="true">&times;</button>' +
20392                                                 '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
20393                                         '</div>'
20394                                 );
20395                         }
20396
20397                         if (settings.url) {
20398                                 html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
20399                         }
20400
20401                         if (typeof(html) == "undefined") {
20402                                 html = layout.renderHtml(self);
20403                         }
20404
20405                         if (self.statusbar) {
20406                                 footerHtml = self.statusbar.renderHtml();
20407                         }
20408
20409                         return (
20410                                 '<div id="' + id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
20411                                         headerHtml +
20412                                         '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
20413                                                 html +
20414                                         '</div>' +
20415                                         footerHtml +
20416                                 '</div>'
20417                         );
20418                 },
20419
20420                 /**
20421                  * Switches the window fullscreen mode.
20422                  *
20423                  * @method fullscreen
20424                  * @param {Boolean} state True/false state.
20425                  * @return {tinymce.ui.Window} Current window instance.
20426                  */
20427                 fullscreen: function(state) {
20428                         var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
20429
20430                         if (state != self._fullscreen) {
20431                                 DomUtils.on(window, 'resize', function() {
20432                                         var time;
20433
20434                                         if (self._fullscreen) {
20435                                                 // Time the layout time if it's to slow use a timeout to not hog the CPU
20436                                                 if (!slowRendering) {
20437                                                         time = new Date().getTime();
20438
20439                                                         var rect = DomUtils.getWindowSize();
20440                                                         self.moveTo(0, 0).resizeTo(rect.w, rect.h);
20441
20442                                                         if ((new Date().getTime()) - time > 50) {
20443                                                                 slowRendering = true;
20444                                                         }
20445                                                 } else {
20446                                                         if (!self._timer) {
20447                                                                 self._timer = setTimeout(function() {
20448                                                                         var rect = DomUtils.getWindowSize();
20449                                                                         self.moveTo(0, 0).resizeTo(rect.w, rect.h);
20450
20451                                                                         self._timer = 0;
20452                                                                 }, 50);
20453                                                         }
20454                                                 }
20455                                         }
20456                                 });
20457
20458                                 layoutRect = self.layoutRect();
20459                                 self._fullscreen = state;
20460
20461                                 if (!state) {
20462                                         self._borderBox = self.parseBox(self.settings.border);
20463                                         self.getEl('head').style.display = '';
20464                                         layoutRect.deltaH += layoutRect.headerH;
20465                                         DomUtils.removeClass(documentElement, prefix + 'fullscreen');
20466                                         DomUtils.removeClass(document.body, prefix + 'fullscreen');
20467                                         self.removeClass('fullscreen');
20468                                         self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
20469                                 } else {
20470                                         self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
20471
20472                                         self._borderBox = self.parseBox('0');
20473                                         self.getEl('head').style.display = 'none';
20474                                         layoutRect.deltaH -= layoutRect.headerH + 2;
20475                                         DomUtils.addClass(documentElement, prefix + 'fullscreen');
20476                                         DomUtils.addClass(document.body, prefix + 'fullscreen');
20477                                         self.addClass('fullscreen');
20478
20479                                         var rect = DomUtils.getWindowSize();
20480                                         self.moveTo(0, 0).resizeTo(rect.w, rect.h);
20481                                 }
20482                         }
20483
20484                         return self.reflow();
20485                 },
20486
20487                 /**
20488                  * Called after the control has been rendered.
20489                  *
20490                  * @method postRender
20491                  */
20492                 postRender: function() {
20493                         var self = this, items = [], focusCtrl, autoFocusFound, startPos;
20494
20495                         setTimeout(function() {
20496                                 self.addClass('in');
20497                         }, 0);
20498
20499                         self.keyboardNavigation = new KeyboardNavigation({
20500                                 root: self,
20501                                 enableLeftRight: false,
20502                                 enableUpDown: false,
20503                                 items: items,
20504                                 onCancel: function() {
20505                                         self.close();
20506                                 }
20507                         });
20508
20509                         self.find('*').each(function(ctrl) {
20510                                 if (ctrl.canFocus) {
20511                                         autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
20512                                         focusCtrl = focusCtrl || ctrl;
20513
20514                                         // TODO: Figure out a better way
20515                                         if (ctrl.type == 'filepicker') {
20516                                                 items.push(ctrl.getEl('inp'));
20517
20518                                                 if (ctrl.getEl('open')) {
20519                                                         items.push(ctrl.getEl('open').firstChild);
20520                                                 }
20521                                         } else {
20522                                                 items.push(ctrl.getEl());
20523                                         }
20524                                 }
20525                         });
20526
20527                         if (self.statusbar) {
20528                                 self.statusbar.find('*').each(function(ctrl) {
20529                                         if (ctrl.canFocus) {
20530                                                 autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
20531                                                 focusCtrl = focusCtrl || ctrl;
20532                                                 items.push(ctrl.getEl());
20533                                         }
20534                                 });
20535                         }
20536
20537                         self._super();
20538
20539                         if (self.statusbar) {
20540                                 self.statusbar.postRender();
20541                         }
20542
20543                         if (!autoFocusFound && focusCtrl) {
20544                                 focusCtrl.focus();
20545                         }
20546
20547                         this.dragHelper = new DragHelper(self._id + '-dragh', {
20548                                 start: function() {
20549                                         startPos = {
20550                                                 x: self.layoutRect().x,
20551                                                 y: self.layoutRect().y
20552                                         };
20553                                 },
20554
20555                                 drag: function(e) {
20556                                         self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
20557                                 }
20558                         });
20559
20560                         self.on('submit', function(e) {
20561                                 if (!e.isDefaultPrevented()) {
20562                                         self.close();
20563                                 }
20564                         });
20565                 },
20566
20567                 /**
20568                  * Fires a submit event with the serialized form.
20569                  *
20570                  * @method submit
20571                  * @return {Object} Event arguments object.
20572                  */
20573                 submit: function() {
20574                         // Blur current control so a onchange is fired before submit
20575                         var ctrl = this.getParentCtrl(document.activeElement);
20576                         if (ctrl) {
20577                                 ctrl.blur();
20578                         }
20579
20580                         return this.fire('submit', {data: this.toJSON()});
20581                 },
20582
20583                 /**
20584                  * Removes the current control from DOM and from UI collections.
20585                  *
20586                  * @method remove
20587                  * @return {tinymce.ui.Control} Current control instance.
20588                  */
20589                 remove: function() {
20590                         var self = this;
20591
20592                         self._super();
20593                         self.dragHelper.destroy();
20594
20595                         if (self.statusbar) {
20596                                 this.statusbar.remove();
20597                         }
20598                 }
20599         });
20600
20601         return Window;
20602 });
20603
20604 // Included from: js/tinymce/classes/ui/MessageBox.js
20605
20606 /**
20607  * MessageBox.js
20608  *
20609  * Copyright, Moxiecode Systems AB
20610  * Released under LGPL License.
20611  *
20612  * License: http://www.tinymce.com/license
20613  * Contributing: http://www.tinymce.com/contributing
20614  */
20615
20616 /**
20617  * This class is used to create MessageBoxes like alerts/confirms etc.
20618  *
20619  * @class tinymce.ui.Window
20620  * @extends tinymce.ui.FloatPanel
20621  */
20622 define("tinymce/ui/MessageBox", [
20623         "tinymce/ui/Window"
20624 ], function(Window) {
20625         "use strict";
20626
20627         var MessageBox = Window.extend({
20628                 /**
20629                  * Constructs a instance with the specified settings.
20630                  *
20631                  * @constructor
20632                  * @param {Object} settings Name/value object with settings.
20633                  */
20634                 init: function(settings) {
20635                         settings = {
20636                                 border: 1,
20637                                 padding: 20,
20638                                 layout: 'flex',
20639                                 pack: "center",
20640                                 align: "center",
20641                                 containerCls: 'panel',
20642                                 autoScroll: true,
20643                                 buttons: {type: "button", text: "Ok", action: "ok"},
20644                                 items: {
20645                                         type: "label",
20646                                         multiline: true,
20647                                         maxWidth: 500,
20648                                         maxHeight: 200
20649                                 }
20650                         };
20651
20652                         this._super(settings);
20653                 },
20654
20655                 Statics: {
20656                         /**
20657                          * Ok buttons constant.
20658                          *
20659                          * @static
20660                          * @final
20661                          * @field {Number} OK
20662                          */
20663                         OK: 1,
20664
20665                         /**
20666                          * Ok/cancel buttons constant.
20667                          *
20668                          * @static
20669                          * @final
20670                          * @field {Number} OK_CANCEL
20671                          */
20672                         OK_CANCEL: 2,
20673
20674                         /**
20675                          * yes/no buttons constant.
20676                          *
20677                          * @static
20678                          * @final
20679                          * @field {Number} YES_NO
20680                          */
20681                         YES_NO: 3,
20682
20683                         /**
20684                          * yes/no/cancel buttons constant.
20685                          *
20686                          * @static
20687                          * @final
20688                          * @field {Number} YES_NO_CANCEL
20689                          */
20690                         YES_NO_CANCEL: 4,
20691
20692                         /**
20693                          * Constructs a new message box and renders it to the body element.
20694                          *
20695                          * @static
20696                          * @method msgBox
20697                          * @param {Object} settings Name/value object with settings.
20698                          */
20699                         msgBox: function(settings) {
20700                                 var buttons, callback = settings.callback || function() {};
20701
20702                                 switch (settings.buttons) {
20703                                         case MessageBox.OK_CANCEL:
20704                                                 buttons = [
20705                                                         {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
20706                                                                 e.control.parents()[1].close();
20707                                                                 callback(true);
20708                                                         }},
20709
20710                                                         {type: "button", text: "Cancel", onClick: function(e) {
20711                                                                 e.control.parents()[1].close();
20712                                                                 callback(false);
20713                                                         }}
20714                                                 ];
20715                                                 break;
20716
20717                                         case MessageBox.YES_NO:
20718                                                 buttons = [
20719                                                         {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
20720                                                                 e.control.parents()[1].close();
20721                                                                 callback(true);
20722                                                         }}
20723                                                 ];
20724                                                 break;
20725
20726                                         case MessageBox.YES_NO_CANCEL:
20727                                                 buttons = [
20728                                                         {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
20729                                                                 e.control.parents()[1].close();
20730                                                         }}
20731                                                 ];
20732                                                 break;
20733
20734                                         default:
20735                                                 buttons = [
20736                                                         {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
20737                                                                 e.control.parents()[1].close();
20738                                                         }}
20739                                                 ];
20740                                                 break;
20741                                 }
20742
20743                                 return new Window({
20744                                         padding: 20,
20745                                         x: settings.x,
20746                                         y: settings.y,
20747                                         minWidth: 300,
20748                                         minHeight: 100,
20749                                         layout: "flex",
20750                                         pack: "center",
20751                                         align: "center",
20752                                         buttons: buttons,
20753                                         title: settings.title,
20754                                         items: {
20755                                                 type: "label",
20756                                                 multiline: true,
20757                                                 maxWidth: 500,
20758                                                 maxHeight: 200,
20759                                                 text: settings.text
20760                                         },
20761                                         onClose: settings.onClose
20762                                 }).renderTo(document.body).reflow();
20763                         },
20764
20765                         /**
20766                          * Creates a new alert dialog.
20767                          *
20768                          * @method alert
20769                          * @param {Object} settings Settings for the alert dialog.
20770                          * @param {function} [callback] Callback to execute when the user makes a choice.
20771                          */
20772                         alert: function(settings, callback) {
20773                                 if (typeof(settings) == "string") {
20774                                         settings = {text: settings};
20775                                 }
20776
20777                                 settings.callback = callback;
20778                                 return MessageBox.msgBox(settings);
20779                         },
20780
20781                         /**
20782                          * Creates a new confirm dialog.
20783                          *
20784                          * @method confirm
20785                          * @param {Object} settings Settings for the confirm dialog.
20786                          * @param {function} [callback] Callback to execute when the user makes a choice.
20787                          */
20788                         confirm: function(settings, callback) {
20789                                 if (typeof(settings) == "string") {
20790                                         settings = {text: settings};
20791                                 }
20792
20793                                 settings.callback = callback;
20794                                 settings.buttons = MessageBox.OK_CANCEL;
20795
20796                                 return MessageBox.msgBox(settings);
20797                         }
20798                 }
20799         });
20800
20801         return MessageBox;
20802 });
20803
20804 // Included from: js/tinymce/classes/WindowManager.js
20805
20806 /**
20807  * WindowManager.js
20808  *
20809  * Copyright, Moxiecode Systems AB
20810  * Released under LGPL License.
20811  *
20812  * License: http://www.tinymce.com/license
20813  * Contributing: http://www.tinymce.com/contributing
20814  */
20815
20816 /**
20817  * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
20818  *
20819  * @class tinymce.WindowManager
20820  * @example
20821  * // Opens a new dialog with the file.htm file and the size 320x240
20822  * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
20823  * tinymce.activeEditor.windowManager.open({
20824  *    url: 'file.htm',
20825  *    width: 320,
20826  *    height: 240
20827  * }, {
20828  *    custom_param: 1
20829  * });
20830  *
20831  * // Displays an alert box using the active editors window manager instance
20832  * tinymce.activeEditor.windowManager.alert('Hello world!');
20833  *
20834  * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
20835  * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
20836  *    if (s)
20837  *       tinymce.activeEditor.windowManager.alert("Ok");
20838  *    else
20839  *       tinymce.activeEditor.windowManager.alert("Cancel");
20840  * });
20841  */
20842 define("tinymce/WindowManager", [
20843         "tinymce/ui/Window",
20844         "tinymce/ui/MessageBox"
20845 ], function(Window, MessageBox) {
20846         return function(editor) {
20847                 var self = this, windows = [];
20848
20849                 self.windows = windows;
20850
20851                 /**
20852                  * Opens a new window.
20853                  *
20854                  * @method open
20855                  * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
20856                  * @option {String} title Window title.
20857                  * @option {String} file URL of the file to open in the window.
20858                  * @option {Number} width Width in pixels.
20859                  * @option {Number} height Height in pixels.
20860                  * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
20861                  * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
20862                  * @option {String/bool} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
20863                  * larger than the popup size specified).
20864                  */
20865                 self.open = function(args, params) {
20866                         var win;
20867
20868                         // Handle URL
20869                         args.url = args.url || args.file; // Legacy
20870                         if (args.url) {
20871                                 args.width = parseInt(args.width || 320, 10);
20872                                 args.height = parseInt(args.height || 240, 10);
20873                         }
20874
20875                         // Handle body
20876                         if (args.body) {
20877                                 args.items = {
20878                                         defaults: args.defaults,
20879                                         type: args.bodyType || 'form',
20880                                         items: args.body
20881                                 };
20882                         }
20883
20884                         if (!args.url && !args.buttons) {
20885                                 args.buttons = [
20886                                         {text: 'Ok', subtype: 'primary', onclick: function() {
20887                                                 win.find('form')[0].submit();
20888                                                 win.close();
20889                                         }},
20890
20891                                         {text: 'Cancel', onclick: function() {
20892                                                 win.close();
20893                                         }}
20894                                 ];
20895                         }
20896
20897                         win = new Window(args);
20898                         windows.push(win);
20899
20900                         win.on('close', function() {
20901                                 var i = windows.length;
20902
20903                                 while (i--) {
20904                                         if (windows[i] === win) {
20905                                                 windows.splice(i, 1);
20906                                         }
20907                                 }
20908
20909                                 editor.focus();
20910                         });
20911
20912                         // Handle data
20913                         if (args.data) {
20914                                 win.on('postRender', function() {
20915                                         this.find('*').each(function(ctrl) {
20916                                                 var name = ctrl.name();
20917
20918                                                 if (name in args.data) {
20919                                                         ctrl.value(args.data[name]);
20920                                                 }
20921                                         });
20922                                 });
20923                         }
20924
20925                         // store parameters
20926                         win.params = params || {};
20927
20928                         // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
20929                         editor.nodeChanged();
20930
20931                         return win.renderTo(document.body).reflow();
20932                 };
20933
20934                 /**
20935                  * Creates a alert dialog. Please don't use the blocking behavior of this
20936                  * native version use the callback method instead then it can be extended.
20937                  *
20938                  * @method alert
20939                  * @param {String} message Text to display in the new alert dialog.
20940                  * @param {function} callback Callback function to be executed after the user has selected ok.
20941                  * @param {Object} scope Optional scope to execute the callback in.
20942                  * @example
20943                  * // Displays an alert box using the active editors window manager instance
20944                  * tinymce.activeEditor.windowManager.alert('Hello world!');
20945                  */
20946                 self.alert = function(message, callback, scope) {
20947                         MessageBox.alert(message, function() {
20948                                 callback.call(scope || this);
20949                         });
20950                 };
20951
20952                 /**
20953                  * Creates a confirm dialog. Please don't use the blocking behavior of this
20954                  * native version use the callback method instead then it can be extended.
20955                  *
20956                  * @method confirm
20957                  * @param {String} messageText to display in the new confirm dialog.
20958                  * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
20959                  * @param {Object} scope Optional scope to execute the callback in.
20960                  * @example
20961                  * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
20962                  * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
20963                  *    if (s)
20964                  *       tinymce.activeEditor.windowManager.alert("Ok");
20965                  *    else
20966                  *       tinymce.activeEditor.windowManager.alert("Cancel");
20967                  * });
20968                  */
20969                 self.confirm = function(message, callback, scope) {
20970                         MessageBox.confirm(message, function(state) {
20971                                 callback.call(scope || this, state);
20972                         });
20973                 };
20974
20975                 /**
20976                  * Closes the top most window.
20977                  *
20978                  * @method close
20979                  */
20980                 self.close = function() {
20981                         if (windows.length) {
20982                                 windows[windows.length - 1].close();
20983                         }
20984                 };
20985
20986                 /**
20987                  * Returns the params of the last window open call. This can be used in iframe based
20988                  * dialog to get params passed from the tinymce plugin.
20989                  *
20990                  * @example
20991                  * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
20992                  *
20993                  * @method getParams
20994                  * @return {Object} Name/value object with parameters passed from windowManager.open call.
20995                  */
20996                 self.getParams = function() {
20997                         if (windows.length) {
20998                                 return windows[windows.length - 1].params;
20999                         }
21000
21001                         return null;
21002                 };
21003
21004                 /**
21005                  * Sets the params of the last opened window.
21006                  *
21007                  * @method setParams
21008                  * @param {Object} params Params object to set for the last opened window.
21009                  */
21010                 self.setParams = function(params) {
21011                         if (windows.length) {
21012                                 windows[windows.length - 1].params = params;
21013                         }
21014                 };
21015         };
21016 });
21017
21018 // Included from: js/tinymce/classes/util/Quirks.js
21019
21020 /**
21021  * Quirks.js
21022  *
21023  * Copyright, Moxiecode Systems AB
21024  * Released under LGPL License.
21025  *
21026  * License: http://www.tinymce.com/license
21027  * Contributing: http://www.tinymce.com/contributing
21028  *
21029  * @ignore-file
21030  */
21031
21032 /**
21033  * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
21034  *
21035  * @class tinymce.util.Quirks
21036  */
21037 define("tinymce/util/Quirks", [
21038         "tinymce/util/VK",
21039         "tinymce/dom/RangeUtils",
21040         "tinymce/html/Node",
21041         "tinymce/html/Entities",
21042         "tinymce/Env",
21043         "tinymce/util/Tools"
21044 ], function(VK, RangeUtils, Node, Entities, Env, Tools) {
21045         return function(editor) {
21046                 var each = Tools.each;
21047                 var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
21048                         settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
21049                 var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
21050
21051                 /**
21052                  * Executes a command with a specific state this can be to enable/disable browser editing features.
21053                  */
21054                 function setEditorCommandState(cmd, state) {
21055                         try {
21056                                 editor.getDoc().execCommand(cmd, false, state);
21057                         } catch (ex) {
21058                                 // Ignore
21059                         }
21060                 }
21061
21062                 /**
21063                  * Returns current IE document mode.
21064                  */
21065                 function getDocumentMode() {
21066                         var documentMode = editor.getDoc().documentMode;
21067
21068                         return documentMode ? documentMode : 6;
21069                 }
21070
21071                 /**
21072                  * Returns true/false if the event is prevented or not.
21073                  *
21074                  * @private
21075                  * @param {Event} e Event object.
21076                  * @return {Boolean} true/false if the event is prevented or not.
21077                  */
21078                 function isDefaultPrevented(e) {
21079                         return e.isDefaultPrevented();
21080                 }
21081
21082                 /**
21083                  * Fixes a WebKit bug when deleting contents using backspace or delete key.
21084                  * WebKit will produce a span element if you delete across two block elements.
21085                  *
21086                  * Example:
21087                  * <h1>a</h1><p>|b</p>
21088                  *
21089                  * Will produce this on backspace:
21090                  * <h1>a<span class="Apple-style-span" style="<all runtime styles>">b</span></p>
21091                  *
21092                  * This fixes the backspace to produce:
21093                  * <h1>a|b</p>
21094                  *
21095                  * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
21096                  *
21097                  * This code is a bit of a hack and hopefully it will be fixed soon in WebKit.
21098                  */
21099                 function cleanupStylesWhenDeleting() {
21100                         function removeMergedFormatSpans(isDelete) {
21101                                 var rng, blockElm, wrapperElm, bookmark, container, offset, elm;
21102
21103                                 function isAtStartOrEndOfElm() {
21104                                         if (container.nodeType == 3) {
21105                                                 if (isDelete && offset == container.length) {
21106                                                         return true;
21107                                                 }
21108
21109                                                 if (!isDelete && offset === 0) {
21110                                                         return true;
21111                                                 }
21112                                         }
21113                                 }
21114
21115                                 rng = selection.getRng();
21116                                 var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
21117
21118                                 if (!rng.collapsed) {
21119                                         isDelete = true;
21120                                 }
21121
21122                                 container = rng[(isDelete ? 'start' : 'end') + 'Container'];
21123                                 offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];
21124
21125                                 if (container.nodeType == 3) {
21126                                         blockElm = dom.getParent(rng.startContainer, dom.isBlock);
21127
21128                                         // On delete clone the root span of the next block element
21129                                         if (isDelete) {
21130                                                 blockElm = dom.getNext(blockElm, dom.isBlock);
21131                                         }
21132
21133                                         if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) {
21134                                                 // Wrap children of block in a EM and let WebKit stick is
21135                                                 // runtime styles junk into that EM
21136                                                 wrapperElm = dom.create('em', {'id': '__mceDel'});
21137
21138                                                 each(Tools.grep(blockElm.childNodes), function(node) {
21139                                                         wrapperElm.appendChild(node);
21140                                                 });
21141
21142                                                 blockElm.appendChild(wrapperElm);
21143                                         }
21144                                 }
21145
21146                                 // Do the backspace/delete action
21147                                 rng = dom.createRng();
21148                                 rng.setStart(tmpRng[0], tmpRng[1]);
21149                                 rng.setEnd(tmpRng[2], tmpRng[3]);
21150                                 selection.setRng(rng);
21151                                 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
21152
21153                                 // Remove temp wrapper element
21154                                 if (wrapperElm) {
21155                                         bookmark = selection.getBookmark();
21156
21157                                         while ((elm = dom.get('__mceDel'))) {
21158                                                 dom.remove(elm, true);
21159                                         }
21160
21161                                         selection.moveToBookmark(bookmark);
21162                                 }
21163                         }
21164
21165                         editor.on('keydown', function(e) {
21166                                 var isDelete;
21167
21168                                 isDelete = e.keyCode == DELETE;
21169                                 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
21170                                         e.preventDefault();
21171                                         removeMergedFormatSpans(isDelete);
21172                                 }
21173                         });
21174
21175                         editor.addCommand('Delete', function() {removeMergedFormatSpans();});
21176                 }
21177
21178                 /**
21179                  * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
21180                  *
21181                  * For example:
21182                  * <p><b>|</b></p>
21183                  *
21184                  * Or:
21185                  * <h1>|</h1>
21186                  *
21187                  * Or:
21188                  * [<h1></h1>]
21189                  */
21190                 function emptyEditorWhenDeleting() {
21191                         function serializeRng(rng) {
21192                                 var body = dom.create("body");
21193                                 var contents = rng.cloneContents();
21194                                 body.appendChild(contents);
21195                                 return selection.serializer.serialize(body, {format: 'html'});
21196                         }
21197
21198                         function allContentsSelected(rng) {
21199                                 var selection = serializeRng(rng);
21200
21201                                 var allRng = dom.createRng();
21202                                 allRng.selectNode(editor.getBody());
21203
21204                                 var allSelection = serializeRng(allRng);
21205                                 return selection === allSelection;
21206                         }
21207
21208                         editor.on('keydown', function(e) {
21209                                 var keyCode = e.keyCode, isCollapsed;
21210
21211                                 // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
21212                                 if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
21213                                         isCollapsed = editor.selection.isCollapsed();
21214
21215                                         // Selection is collapsed but the editor isn't empty
21216                                         if (isCollapsed && !dom.isEmpty(editor.getBody())) {
21217                                                 return;
21218                                         }
21219
21220                                         // IE deletes all contents correctly when everything is selected
21221                                         if (isIE && !isCollapsed) {
21222                                                 return;
21223                                         }
21224
21225                                         // Selection isn't collapsed but not all the contents is selected
21226                                         if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
21227                                                 return;
21228                                         }
21229
21230                                         // Manually empty the editor
21231                                         e.preventDefault();
21232                                         editor.setContent('');
21233                                         editor.selection.setCursorLocation(editor.getBody(), 0);
21234                                         editor.nodeChanged();
21235                                 }
21236                         });
21237                 }
21238
21239                 /**
21240                  * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
21241                  * This selects the whole body so that backspace/delete logic will delete everything
21242                  */
21243                 function selectAll() {
21244                         editor.on('keydown', function(e) {
21245                                 if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
21246                                         e.preventDefault();
21247                                         editor.execCommand('SelectAll');
21248                                 }
21249                         });
21250                 }
21251
21252                 /**
21253                  * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
21254                  * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
21255                  *
21256                  * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
21257                  * you enter a character into the editor.
21258                  *
21259                  * It also happens when the first focus in made to the body.
21260                  *
21261                  * See: https://bugs.webkit.org/show_bug.cgi?id=83566
21262                  */
21263                 function inputMethodFocus() {
21264                         if (!editor.settings.content_editable) {
21265                                 // Case 1 IME doesn't initialize if you focus the document
21266                                 dom.bind(editor.getDoc(), 'focusin', function() {
21267                                         selection.setRng(selection.getRng());
21268                                 });
21269
21270                                 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
21271                                 dom.bind(editor.getDoc(), 'mousedown', function(e) {
21272                                         if (e.target == editor.getDoc().documentElement) {
21273                                                 editor.getWin().focus();
21274                                                 selection.setRng(selection.getRng());
21275                                         }
21276                                 });
21277                         }
21278                 }
21279
21280                 /**
21281                  * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
21282                  * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
21283                  * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
21284                  * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
21285                  * browsers
21286                  */
21287                 function removeHrOnBackspace() {
21288                         editor.on('keydown', function(e) {
21289                                 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
21290                                         if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
21291                                                 var node = selection.getNode();
21292                                                 var previousSibling = node.previousSibling;
21293
21294                                                 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
21295                                                         dom.remove(previousSibling);
21296                                                         e.preventDefault();
21297                                                 }
21298                                         }
21299                                 }
21300                         });
21301                 }
21302
21303                 /**
21304                  * Firefox 3.x has an issue where the body element won't get proper focus if you click out
21305                  * side it's rectangle.
21306                  */
21307                 function focusBody() {
21308                         // Fix for a focus bug in FF 3.x where the body element
21309                         // wouldn't get proper focus if the user clicked on the HTML element
21310                         if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
21311                                 editor.on('mousedown', function(e) {
21312                                         if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
21313                                                 var body = editor.getBody();
21314
21315                                                 // Blur the body it's focused but not correctly focused
21316                                                 body.blur();
21317
21318                                                 // Refocus the body after a little while
21319                                                 setTimeout(function() {
21320                                                         body.focus();
21321                                                 }, 0);
21322                                         }
21323                                 });
21324                         }
21325                 }
21326
21327                 /**
21328                  * WebKit has a bug where it isn't possible to select image, hr or anchor elements
21329                  * by clicking on them so we need to fake that.
21330                  */
21331                 function selectControlElements() {
21332                         editor.on('click', function(e) {
21333                                 e = e.target;
21334
21335                                 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
21336                                 // WebKit can't even do simple things like selecting an image
21337                                 // Needs tobe the setBaseAndExtend or it will fail to select floated images
21338                                 if (/^(IMG|HR)$/.test(e.nodeName)) {
21339                                         selection.getSel().setBaseAndExtent(e, 0, e, 1);
21340                                 }
21341
21342                                 if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
21343                                         selection.select(e);
21344                                 }
21345
21346                                 editor.nodeChanged();
21347                         });
21348                 }
21349
21350                 /**
21351                  * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
21352                  *
21353                  * Fixes do backspace/delete on this:
21354                  * <p>bla[ck</p><p style="color:red">r]ed</p>
21355                  *
21356                  * Would become:
21357                  * <p>bla|ed</p>
21358                  *
21359                  * Instead of:
21360                  * <p style="color:red">bla|ed</p>
21361                  */
21362                 function removeStylesWhenDeletingAcrossBlockElements() {
21363                         function getAttributeApplyFunction() {
21364                                 var template = dom.getAttribs(selection.getStart().cloneNode(false));
21365
21366                                 return function() {
21367                                         var target = selection.getStart();
21368
21369                                         if (target !== editor.getBody()) {
21370                                                 dom.setAttrib(target, "style", null);
21371
21372                                                 each(template, function(attr) {
21373                                                         target.setAttributeNode(attr.cloneNode(true));
21374                                                 });
21375                                         }
21376                                 };
21377                         }
21378
21379                         function isSelectionAcrossElements() {
21380                                 return !selection.isCollapsed() &&
21381                                         dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
21382                         }
21383
21384                         editor.on('keypress', function(e) {
21385                                 var applyAttributes;
21386
21387                                 if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
21388                                         applyAttributes = getAttributeApplyFunction();
21389                                         editor.getDoc().execCommand('delete', false, null);
21390                                         applyAttributes();
21391                                         e.preventDefault();
21392                                         return false;
21393                                 }
21394                         });
21395
21396                         dom.bind(editor.getDoc(), 'cut', function(e) {
21397                                 var applyAttributes;
21398
21399                                 if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
21400                                         applyAttributes = getAttributeApplyFunction();
21401
21402                                         setTimeout(function() {
21403                                                 applyAttributes();
21404                                         }, 0);
21405                                 }
21406                         });
21407                 }
21408
21409                 /**
21410                  * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange
21411                  * event every 50ms since it would other wise update the UI when you type and it hogs the CPU.
21412                  */
21413                 function selectionChangeNodeChanged() {
21414                         var lastRng, selectionTimer;
21415
21416                         editor.on('selectionchange', function() {
21417                                 if (selectionTimer) {
21418                                         clearTimeout(selectionTimer);
21419                                         selectionTimer = 0;
21420                                 }
21421
21422                                 selectionTimer = window.setTimeout(function() {
21423                                         var rng = selection.getRng();
21424
21425                                         // Compare the ranges to see if it was a real change or not
21426                                         if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) {
21427                                                 editor.nodeChanged();
21428                                                 lastRng = rng;
21429                                         }
21430                                 }, 50);
21431                         });
21432                 }
21433
21434                 /**
21435                  * Screen readers on IE needs to have the role application set on the body.
21436                  */
21437                 function ensureBodyHasRoleApplication() {
21438                         document.body.setAttribute("role", "application");
21439                 }
21440
21441                 /**
21442                  * Backspacing into a table behaves differently depending upon browser type.
21443                  * Therefore, disable Backspace when cursor immediately follows a table.
21444                  */
21445                 function disableBackspaceIntoATable() {
21446                         editor.on('keydown', function(e) {
21447                                 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
21448                                         if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
21449                                                 var previousSibling = selection.getNode().previousSibling;
21450                                                 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
21451                                                         e.preventDefault();
21452                                                         return false;
21453                                                 }
21454                                         }
21455                                 }
21456                         });
21457                 }
21458
21459                 /**
21460                  * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
21461                  * logic adds a \n before the BR so that it will get rendered.
21462                  */
21463                 function addNewLinesBeforeBrInPre() {
21464                         // IE8+ rendering mode does the right thing with BR in PRE
21465                         if (getDocumentMode() > 7) {
21466                                 return;
21467                         }
21468
21469                          // Enable display: none in area and add a specific class that hides all BR elements in PRE to
21470                          // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
21471                         setEditorCommandState('RespectVisibilityInDesign', true);
21472                         editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
21473                         dom.addClass(editor.getBody(), 'mceHideBrInPre');
21474
21475                         // Adds a \n before all BR elements in PRE to get them visual
21476                         parser.addNodeFilter('pre', function(nodes) {
21477                                 var i = nodes.length, brNodes, j, brElm, sibling;
21478
21479                                 while (i--) {
21480                                         brNodes = nodes[i].getAll('br');
21481                                         j = brNodes.length;
21482                                         while (j--) {
21483                                                 brElm = brNodes[j];
21484
21485                                                 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
21486                                                 sibling = brElm.prev;
21487                                                 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
21488                                                         sibling.value += '\n';
21489                                                 } else {
21490                                                         brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
21491                                                 }
21492                                         }
21493                                 }
21494                         });
21495
21496                         // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
21497                         serializer.addNodeFilter('pre', function(nodes) {
21498                                 var i = nodes.length, brNodes, j, brElm, sibling;
21499
21500                                 while (i--) {
21501                                         brNodes = nodes[i].getAll('br');
21502                                         j = brNodes.length;
21503                                         while (j--) {
21504                                                 brElm = brNodes[j];
21505                                                 sibling = brElm.prev;
21506                                                 if (sibling && sibling.type == 3) {
21507                                                         sibling.value = sibling.value.replace(/\r?\n$/, '');
21508                                                 }
21509                                         }
21510                                 }
21511                         });
21512                 }
21513
21514                 /**
21515                  * Moves style width/height to attribute width/height when the user resizes an image on IE.
21516                  */
21517                 function removePreSerializedStylesWhenSelectingControls() {
21518                         dom.bind(editor.getBody(), 'mouseup', function() {
21519                                 var value, node = selection.getNode();
21520
21521                                 // Moved styles to attributes on IMG eements
21522                                 if (node.nodeName == 'IMG') {
21523                                         // Convert style width to width attribute
21524                                         if ((value = dom.getStyle(node, 'width'))) {
21525                                                 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
21526                                                 dom.setStyle(node, 'width', '');
21527                                         }
21528
21529                                         // Convert style height to height attribute
21530                                         if ((value = dom.getStyle(node, 'height'))) {
21531                                                 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
21532                                                 dom.setStyle(node, 'height', '');
21533                                         }
21534                                 }
21535                         });
21536                 }
21537
21538                 /**
21539                  * Backspace or delete on WebKit will combine all visual styles in a span if the last character is deleted.
21540                  *
21541                  * For example backspace on:
21542                  * <p><b>x|</b></p>
21543                  *
21544                  * Will produce:
21545                  * <p><span style="font-weight: bold">|<br></span></p>
21546                  *
21547                  * When it should produce:
21548                  * <p><b>|<br></b></p>
21549                  *
21550                  * See: https://bugs.webkit.org/show_bug.cgi?id=81656
21551                  */
21552                 function keepInlineElementOnDeleteBackspace() {
21553                         editor.on('keydown', function(e) {
21554                                 var isDelete, rng, container, offset, brElm, sibling, collapsed, nonEmptyElements;
21555
21556                                 isDelete = e.keyCode == DELETE;
21557                                 if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
21558                                         rng = selection.getRng();
21559                                         container = rng.startContainer;
21560                                         offset = rng.startOffset;
21561                                         collapsed = rng.collapsed;
21562
21563                                         // Override delete if the start container is a text node and is at the beginning of text or
21564                                         // just before/after the last character to be deleted in collapsed mode
21565                                         if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) ||
21566                                                 (collapsed && offset === (isDelete ? 0 : 1)))) {
21567                                                 // Edge case when deleting <p><b><img> |x</b></p>
21568                                                 sibling = container.previousSibling;
21569                                                 if (sibling && sibling.nodeName == "IMG") {
21570                                                         return;
21571                                                 }
21572
21573                                                 nonEmptyElements = editor.schema.getNonEmptyElements();
21574
21575                                                 // Prevent default logic since it's broken
21576                                                 e.preventDefault();
21577
21578                                                 // Insert a BR before the text node this will prevent the containing element from being deleted/converted
21579                                                 brElm = dom.create('br', {id: '__tmp'});
21580                                                 container.parentNode.insertBefore(brElm, container);
21581
21582                                                 // Do the browser delete
21583                                                 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
21584
21585                                                 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
21586                                                 container = selection.getRng().startContainer;
21587                                                 sibling = container.previousSibling;
21588                                                 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) &&
21589                                                         !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
21590                                                         dom.remove(sibling);
21591                                                 }
21592
21593                                                 // Remove the temp element we inserted
21594                                                 dom.remove('__tmp');
21595                                         }
21596                                 }
21597                         });
21598                 }
21599
21600                 /**
21601                  * Removes a blockquote when backspace is pressed at the beginning of it.
21602                  *
21603                  * For example:
21604                  * <blockquote><p>|x</p></blockquote>
21605                  *
21606                  * Becomes:
21607                  * <p>|x</p>
21608                  */
21609                 function removeBlockQuoteOnBackSpace() {
21610                         // Add block quote deletion handler
21611                         editor.on('keydown', function(e) {
21612                                 var rng, container, offset, root, parent;
21613
21614                                 if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
21615                                         return;
21616                                 }
21617
21618                                 rng = selection.getRng();
21619                                 container = rng.startContainer;
21620                                 offset = rng.startOffset;
21621                                 root = dom.getRoot();
21622                                 parent = container;
21623
21624                                 if (!rng.collapsed || offset !== 0) {
21625                                         return;
21626                                 }
21627
21628                                 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
21629                                         parent = parent.parentNode;
21630                                 }
21631
21632                                 // Is the cursor at the beginning of a blockquote?
21633                                 if (parent.tagName === 'BLOCKQUOTE') {
21634                                         // Remove the blockquote
21635                                         editor.formatter.toggle('blockquote', null, parent);
21636
21637                                         // Move the caret to the beginning of container
21638                                         rng = dom.createRng();
21639                                         rng.setStart(container, 0);
21640                                         rng.setEnd(container, 0);
21641                                         selection.setRng(rng);
21642                                 }
21643                         });
21644                 }
21645
21646                 /**
21647                  * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
21648                  */
21649                 function setGeckoEditingOptions() {
21650                         function setOpts() {
21651                                 editor._refreshContentEditable();
21652
21653                                 setEditorCommandState("StyleWithCSS", false);
21654                                 setEditorCommandState("enableInlineTableEditing", false);
21655
21656                                 if (!settings.object_resizing) {
21657                                         setEditorCommandState("enableObjectResizing", false);
21658                                 }
21659                         }
21660
21661                         if (!settings.readonly) {
21662                                 editor.on('BeforeExecCommand MouseDown', setOpts);
21663                         }
21664                 }
21665
21666                 /**
21667                  * Fixes a gecko link bug, when a link is placed at the end of block elements there is
21668                  * no way to move the caret behind the link. This fix adds a bogus br element after the link.
21669                  *
21670                  * For example this:
21671                  * <p><b><a href="#">x</a></b></p>
21672                  *
21673                  * Becomes this:
21674                  * <p><b><a href="#">x</a></b><br></p>
21675                  */
21676                 function addBrAfterLastLinks() {
21677                         function fixLinks() {
21678                                 each(dom.select('a'), function(node) {
21679                                         var parentNode = node.parentNode, root = dom.getRoot();
21680
21681                                         if (parentNode.lastChild === node) {
21682                                                 while (parentNode && !dom.isBlock(parentNode)) {
21683                                                         if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
21684                                                                 return;
21685                                                         }
21686
21687                                                         parentNode = parentNode.parentNode;
21688                                                 }
21689
21690                                                 dom.add(parentNode, 'br', {'data-mce-bogus': 1});
21691                                         }
21692                                 });
21693                         }
21694
21695                         editor.on('SetContent ExecCommand', function(e) {
21696                                 if (e.type == "setcontent" || e.command === 'mceInsertLink') {
21697                                         fixLinks();
21698                                 }
21699                         });
21700                 }
21701
21702                 /**
21703                  * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
21704                  * default we want to change that behavior.
21705                  */
21706                 function setDefaultBlockType() {
21707                         if (settings.forced_root_block) {
21708                                 editor.on('init', function() {
21709                                         setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
21710                                 });
21711                         }
21712                 }
21713
21714                 /**
21715                  * Removes ghost selections from images/tables on Gecko.
21716                  */
21717                 function removeGhostSelection() {
21718                         editor.on('Undo Redo SetContent', function(e) {
21719                                 if (!e.initial) {
21720                                         editor.execCommand('mceRepaint');
21721                                 }
21722                         });
21723                 }
21724
21725                 /**
21726                  * Deletes the selected image on IE instead of navigating to previous page.
21727                  */
21728                 function deleteControlItemOnBackSpace() {
21729                         editor.on('keydown', function(e) {
21730                                 var rng;
21731
21732                                 if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
21733                                         rng = editor.getDoc().selection.createRange();
21734                                         if (rng && rng.item) {
21735                                                 e.preventDefault();
21736                                                 editor.undoManager.beforeChange();
21737                                                 dom.remove(rng.item(0));
21738                                                 editor.undoManager.add();
21739                                         }
21740                                 }
21741                         });
21742                 }
21743
21744                 /**
21745                  * IE10 doesn't properly render block elements with the right height until you add contents to them.
21746                  * This fixes that by adding a padding-right to all empty text block elements.
21747                  * See: https://connect.microsoft.com/IE/feedback/details/743881
21748                  */
21749                 function renderEmptyBlocksFix() {
21750                         var emptyBlocksCSS;
21751
21752                         // IE10+
21753                         if (getDocumentMode() >= 10) {
21754                                 emptyBlocksCSS = '';
21755                                 each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
21756                                         emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
21757                                 });
21758
21759                                 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
21760                         }
21761                 }
21762
21763                 /**
21764                  * Old IE versions can't retain contents within noscript elements so this logic will store the contents
21765                  * as a attribute and the insert that value as it's raw text when the DOM is serialized.
21766                  */
21767                 function keepNoScriptContents() {
21768                         if (getDocumentMode() < 9) {
21769                                 parser.addNodeFilter('noscript', function(nodes) {
21770                                         var i = nodes.length, node, textNode;
21771
21772                                         while (i--) {
21773                                                 node = nodes[i];
21774                                                 textNode = node.firstChild;
21775
21776                                                 if (textNode) {
21777                                                         node.attr('data-mce-innertext', textNode.value);
21778                                                 }
21779                                         }
21780                                 });
21781
21782                                 serializer.addNodeFilter('noscript', function(nodes) {
21783                                         var i = nodes.length, node, textNode, value;
21784
21785                                         while (i--) {
21786                                                 node = nodes[i];
21787                                                 textNode = nodes[i].firstChild;
21788
21789                                                 if (textNode) {
21790                                                         textNode.value = Entities.decode(textNode.value);
21791                                                 } else {
21792                                                         // Old IE can't retain noscript value so an attribute is used to store it
21793                                                         value = node.attributes.map['data-mce-innertext'];
21794                                                         if (value) {
21795                                                                 node.attr('data-mce-innertext', null);
21796                                                                 textNode = new Node('#text', 3);
21797                                                                 textNode.value = value;
21798                                                                 textNode.raw = true;
21799                                                                 node.append(textNode);
21800                                                         }
21801                                                 }
21802                                         }
21803                                 });
21804                         }
21805                 }
21806
21807                 /**
21808                  * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
21809                  */
21810                 function fixCaretSelectionOfDocumentElementOnIe() {
21811                         var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
21812
21813                         // Return range from point or null if it failed
21814                         function rngFromPoint(x, y) {
21815                                 var rng = body.createTextRange();
21816
21817                                 try {
21818                                         rng.moveToPoint(x, y);
21819                                 } catch (ex) {
21820                                         // IE sometimes throws and exception, so lets just ignore it
21821                                         rng = null;
21822                                 }
21823
21824                                 return rng;
21825                         }
21826
21827                         // Fires while the selection is changing
21828                         function selectionChange(e) {
21829                                 var pointRng;
21830
21831                                 // Check if the button is down or not
21832                                 if (e.button) {
21833                                         // Create range from mouse position
21834                                         pointRng = rngFromPoint(e.x, e.y);
21835
21836                                         if (pointRng) {
21837                                                 // Check if pointRange is before/after selection then change the endPoint
21838                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
21839                                                         pointRng.setEndPoint('StartToStart', startRng);
21840                                                 } else {
21841                                                         pointRng.setEndPoint('EndToEnd', startRng);
21842                                                 }
21843
21844                                                 pointRng.select();
21845                                         }
21846                                 } else {
21847                                         endSelection();
21848                                 }
21849                         }
21850
21851                         // Removes listeners
21852                         function endSelection() {
21853                                 var rng = doc.selection.createRange();
21854
21855                                 // If the range is collapsed then use the last start range
21856                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
21857                                         startRng.select();
21858                                 }
21859
21860                                 dom.unbind(doc, 'mouseup', endSelection);
21861                                 dom.unbind(doc, 'mousemove', selectionChange);
21862                                 startRng = started = 0;
21863                         }
21864
21865                         // Make HTML element unselectable since we are going to handle selection by hand
21866                         doc.documentElement.unselectable = true;
21867
21868                         // Detect when user selects outside BODY
21869                         dom.bind(doc, 'mousedown contextmenu', function(e) {
21870                                 if (e.target.nodeName === 'HTML') {
21871                                         if (started) {
21872                                                 endSelection();
21873                                         }
21874
21875                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
21876                                         htmlElm = doc.documentElement;
21877                                         if (htmlElm.scrollHeight > htmlElm.clientHeight) {
21878                                                 return;
21879                                         }
21880
21881                                         started = 1;
21882                                         // Setup start position
21883                                         startRng = rngFromPoint(e.x, e.y);
21884                                         if (startRng) {
21885                                                 // Listen for selection change events
21886                                                 dom.bind(doc, 'mouseup', endSelection);
21887                                                 dom.bind(doc, 'mousemove', selectionChange);
21888
21889                                                 dom.win.focus();
21890                                                 startRng.select();
21891                                         }
21892                                 }
21893                         });
21894                 }
21895
21896                 /**
21897                  * Fixes selection issues on Gecko where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
21898                  * this fix will lean the caret right into the closest inline element.
21899                  */
21900                 function normalizeSelection() {
21901                         // 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
21902                         editor.on('keyup focusin', function(e) {
21903                                 if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
21904                                         selection.normalize();
21905                                 }
21906                         });
21907                 }
21908
21909                 /**
21910                  * Forces Gecko to render a broken image icon if it fails to load an image.
21911                  */
21912                 function showBrokenImageIcon() {
21913                         editor.contentStyles.push(
21914                                 'img:-moz-broken {' +
21915                                         '-moz-force-broken-image-icon:1;' +
21916                                         'min-width:24px;' +
21917                                         'min-height:24px' +
21918                                 '}'
21919                         );
21920                 }
21921
21922                 /**
21923                  * iOS has a bug where it's impossible to type if the document has a touchstart event
21924                  * bound and the user touches the document while having the on screen keyboard visible.
21925                  *
21926                  * The touch event moves the focus to the parent document while having the caret inside the iframe
21927                  * this fix moves the focus back into the iframe document.
21928                  */
21929                 function restoreFocusOnKeyDown() {
21930                         if (!editor.inline) {
21931                                 editor.on('keydown', function() {
21932                                         if (document.activeElement == document.body) {
21933                                                 editor.getWin().focus();
21934                                         }
21935                                 });
21936                         }
21937                 }
21938
21939                 // All browsers
21940                 disableBackspaceIntoATable();
21941                 removeBlockQuoteOnBackSpace();
21942                 emptyEditorWhenDeleting();
21943                 normalizeSelection();
21944
21945                 // WebKit
21946                 if (isWebKit) {
21947                         keepInlineElementOnDeleteBackspace();
21948                         cleanupStylesWhenDeleting();
21949                         inputMethodFocus();
21950                         selectControlElements();
21951                         setDefaultBlockType();
21952
21953                         // iOS
21954                         if (Env.iOS) {
21955                                 selectionChangeNodeChanged();
21956                                 restoreFocusOnKeyDown();
21957                         } else {
21958                                 selectAll();
21959                         }
21960                 }
21961
21962                 // IE
21963                 if (isIE && Env.ie < 11) {
21964                         removeHrOnBackspace();
21965                         ensureBodyHasRoleApplication();
21966                         addNewLinesBeforeBrInPre();
21967                         removePreSerializedStylesWhenSelectingControls();
21968                         deleteControlItemOnBackSpace();
21969                         renderEmptyBlocksFix();
21970                         keepNoScriptContents();
21971                         fixCaretSelectionOfDocumentElementOnIe();
21972                 }
21973
21974                 // Gecko
21975                 if (isGecko) {
21976                         removeHrOnBackspace();
21977                         focusBody();
21978                         removeStylesWhenDeletingAcrossBlockElements();
21979                         setGeckoEditingOptions();
21980                         addBrAfterLastLinks();
21981                         removeGhostSelection();
21982                         showBrokenImageIcon();
21983                 }
21984         };
21985 });
21986
21987 // Included from: js/tinymce/classes/util/Observable.js
21988
21989 /**
21990  * Observable.js
21991  *
21992  * Copyright, Moxiecode Systems AB
21993  * Released under LGPL License.
21994  *
21995  * License: http://www.tinymce.com/license
21996  * Contributing: http://www.tinymce.com/contributing
21997  */
21998
21999 /**
22000  * This mixin will add event binding logic to classes.
22001  *
22002  * @mixin tinymce.util.Observable
22003  */
22004 define("tinymce/util/Observable", [
22005         "tinymce/util/Tools"
22006 ], function(Tools) {
22007         var bindingsName = "__bindings";
22008         var nativeEvents = Tools.makeMap(
22009                 "focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange" +
22010                 " mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragend dragover draggesture dragdrop drop drag", ' '
22011         );
22012
22013         function returnFalse() {
22014                 return false;
22015         }
22016
22017         function returnTrue() {
22018                 return true;
22019         }
22020
22021         return {
22022                 /**
22023                  * Fires the specified event by name.
22024                  *
22025                  * @method fire
22026                  * @param {String} name Name of the event to fire.
22027                  * @param {tinymce.Event/Object?} args Event arguments.
22028                  * @param {Boolean?} bubble True/false if the event is to be bubbled.
22029                  * @return {tinymce.Event} Event instance passed in converted into tinymce.Event instance.
22030                  * @example
22031                  * instance.fire('event', {...});
22032                  */
22033                 fire: function(name, args, bubble) {
22034                         var self = this, handlers, i, l, callback, parent;
22035
22036                         name = name.toLowerCase();
22037                         args = args || {};
22038                         args.type = name;
22039
22040                         // Setup target is there isn't one
22041                         if (!args.target) {
22042                                 args.target = self;
22043                         }
22044
22045                         // Add event delegation methods if they are missing
22046                         if (!args.preventDefault) {
22047                                 // Add preventDefault method
22048                                 args.preventDefault = function() {
22049                                         args.isDefaultPrevented = returnTrue;
22050                                 };
22051
22052                                 // Add stopPropagation
22053                                 args.stopPropagation = function() {
22054                                         args.isPropagationStopped = returnTrue;
22055                                 };
22056
22057                                 // Add stopImmediatePropagation
22058                                 args.stopImmediatePropagation = function() {
22059                                         args.isImmediatePropagationStopped = returnTrue;
22060                                 };
22061
22062                                 // Add event delegation states
22063                                 args.isDefaultPrevented = returnFalse;
22064                                 args.isPropagationStopped = returnFalse;
22065                                 args.isImmediatePropagationStopped = returnFalse;
22066                         }
22067
22068                         //console.log(name, args);
22069
22070                         if (self[bindingsName]) {
22071                                 handlers = self[bindingsName][name];
22072
22073                                 if (handlers) {
22074                                         for (i = 0, l = handlers.length; i < l; i++) {
22075                                                 handlers[i] = callback = handlers[i];
22076
22077                                                 // Stop immediate propagation if needed
22078                                                 if (args.isImmediatePropagationStopped()) {
22079                                                         break;
22080                                                 }
22081
22082                                                 // If callback returns false then prevent default and stop all propagation
22083                                                 if (callback.call(self, args) === false) {
22084                                                         args.preventDefault();
22085                                                         return args;
22086                                                 }
22087                                         }
22088                                 }
22089                         }
22090
22091                         // Bubble event up to parents
22092                         if (bubble !== false && self.parent) {
22093                                 parent = self.parent();
22094                                 while (parent && !args.isPropagationStopped()) {
22095                                         parent.fire(name, args, false);
22096                                         parent = parent.parent();
22097                                 }
22098                         }
22099
22100                         return args;
22101                 },
22102
22103                 /**
22104                  * Binds an event listener to a specific event by name.
22105                  *
22106                  * @method on
22107                  * @param {String} name Event name or space separated list of events to bind.
22108                  * @param {callback} callback Callback to be executed when the event occurs.
22109                  * @return {Object} Current class instance.
22110                  * @example
22111                  * instance.on('event', function(e) {
22112                  *     // Callback logic
22113                  * });
22114                  */
22115                 on: function(name, callback) {
22116                         var self = this, bindings, handlers, names, i;
22117
22118                         if (callback === false) {
22119                                 callback = function() {
22120                                         return false;
22121                                 };
22122                         }
22123
22124                         if (callback) {
22125                                 names = name.toLowerCase().split(' ');
22126                                 i = names.length;
22127                                 while (i--) {
22128                                         name = names[i];
22129
22130                                         bindings = self[bindingsName];
22131                                         if (!bindings) {
22132                                                 bindings = self[bindingsName] = {};
22133                                         }
22134
22135                                         handlers = bindings[name];
22136                                         if (!handlers) {
22137                                                 handlers = bindings[name] = [];
22138                                                 if (self.bindNative && nativeEvents[name]) {
22139                                                         self.bindNative(name);
22140                                                 }
22141                                         }
22142
22143                                         handlers.push(callback);
22144                                 }
22145                         }
22146
22147                         return self;
22148                 },
22149
22150                 /**
22151                  * Unbinds an event listener to a specific event by name.
22152                  *
22153                  * @method off
22154                  * @param {String?} name Name of the event to unbind.
22155                  * @param {callback?} callback Callback to unbind.
22156                  * @return {Object} Current class instance.
22157                  * @example
22158                  * // Unbind specific callback
22159                  * instance.off('event', handler);
22160                  *
22161                  * // Unbind all listeners by name
22162                  * instance.off('event');
22163                  *
22164                  * // Unbind all events
22165                  * instance.off();
22166                  */
22167                 off: function(name, callback) {
22168                         var self = this, i, bindings = self[bindingsName], handlers, bindingName, names, hi;
22169
22170                         if (bindings) {
22171                                 if (name) {
22172                                         names = name.toLowerCase().split(' ');
22173                                         i = names.length;
22174                                         while (i--) {
22175                                                 name = names[i];
22176                                                 handlers = bindings[name];
22177
22178                                                 // Unbind all handlers
22179                                                 if (!name) {
22180                                                         for (bindingName in bindings) {
22181                                                                 bindings[name].length = 0;
22182                                                         }
22183
22184                                                         return self;
22185                                                 }
22186
22187                                                 if (handlers) {
22188                                                         // Unbind all by name
22189                                                         if (!callback) {
22190                                                                 handlers.length = 0;
22191                                                         } else {
22192                                                                 // Unbind specific ones
22193                                                                 hi = handlers.length;
22194                                                                 while (hi--) {
22195                                                                         if (handlers[hi] === callback) {
22196                                                                                 handlers.splice(hi, 1);
22197                                                                         }
22198                                                                 }
22199                                                         }
22200
22201                                                         if (!handlers.length && self.unbindNative && nativeEvents[name]) {
22202                                                                 self.unbindNative(name);
22203                                                                 delete bindings[name];
22204                                                         }
22205                                                 }
22206                                         }
22207                                 } else {
22208                                         if (self.unbindNative) {
22209                                                 for (name in bindings) {
22210                                                         self.unbindNative(name);
22211                                                 }
22212                                         }
22213
22214                                         self[bindingsName] = [];
22215                                 }
22216                         }
22217
22218                         return self;
22219                 }
22220         };
22221 });
22222
22223 // Included from: js/tinymce/classes/Shortcuts.js
22224
22225 /**
22226  * Shortcuts.js
22227  *
22228  * Copyright, Moxiecode Systems AB
22229  * Released under LGPL License.
22230  *
22231  * License: http://www.tinymce.com/license
22232  * Contributing: http://www.tinymce.com/contributing
22233  */
22234
22235 /**
22236  * Contains all logic for handling of keyboard shortcuts.
22237  */
22238 define("tinymce/Shortcuts", [
22239         "tinymce/util/Tools",
22240         "tinymce/Env"
22241 ], function(Tools, Env) {
22242         var each = Tools.each, explode = Tools.explode;
22243
22244         var keyCodeLookup = {
22245                 "f9": 120,
22246                 "f10": 121,
22247                 "f11": 122
22248         };
22249
22250         return function(editor) {
22251                 var self = this, shortcuts = {};
22252
22253                 editor.on('keyup keypress keydown', function(e) {
22254                         if (e.altKey || e.ctrlKey || e.metaKey) {
22255                                 each(shortcuts, function(shortcut) {
22256                                         var ctrlKey = Env.mac ? (e.ctrlKey || e.metaKey) : e.ctrlKey;
22257
22258                                         if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
22259                                                 return;
22260                                         }
22261
22262                                         if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
22263                                                 e.preventDefault();
22264
22265                                                 if (e.type == "keydown") {
22266                                                         shortcut.func.call(shortcut.scope);
22267                                                 }
22268
22269                                                 return true;
22270                                         }
22271                                 });
22272                         }
22273                 });
22274
22275                 /**
22276                  * Adds a keyboard shortcut for some command or function.
22277                  *
22278                  * @method addShortcut
22279                  * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
22280                  * @param {String} desc Text description for the command.
22281                  * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
22282                  * @param {Object} sc Optional scope to execute the function in.
22283                  * @return {Boolean} true/false state if the shortcut was added or not.
22284                  */
22285                 self.add = function(pattern, desc, cmdFunc, scope) {
22286                         var cmd;
22287
22288                         cmd = cmdFunc;
22289
22290                         if (typeof(cmdFunc) === 'string') {
22291                                 cmdFunc = function() {
22292                                         editor.execCommand(cmd, false, null);
22293                                 };
22294                         } else if (Tools.isArray(cmd)) {
22295                                 cmdFunc = function() {
22296                                         editor.execCommand(cmd[0], cmd[1], cmd[2]);
22297                                 };
22298                         }
22299
22300                         each(explode(pattern.toLowerCase()), function(pattern) {
22301                                 var shortcut = {
22302                                         func: cmdFunc,
22303                                         scope: scope || editor,
22304                                         desc: editor.translate(desc),
22305                                         alt: false,
22306                                         ctrl: false,
22307                                         shift: false
22308                                 };
22309
22310                                 each(explode(pattern, '+'), function(value) {
22311                                         switch (value) {
22312                                                 case 'alt':
22313                                                 case 'ctrl':
22314                                                 case 'shift':
22315                                                         shortcut[value] = true;
22316                                                         break;
22317
22318                                                 default:
22319                                                         shortcut.charCode = value.charCodeAt(0);
22320                                                         shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
22321                                         }
22322                                 });
22323
22324                                 shortcuts[
22325                                         (shortcut.ctrl ? 'ctrl' : '') + ',' +
22326                                         (shortcut.alt ? 'alt' : '') + ',' +
22327                                         (shortcut.shift ? 'shift' : '') + ',' +
22328                                         shortcut.keyCode
22329                                 ] = shortcut;
22330                         });
22331
22332                         return true;
22333                 };
22334         };
22335 });
22336
22337 // Included from: js/tinymce/classes/Editor.js
22338
22339 /**
22340  * Editor.js
22341  *
22342  * Copyright, Moxiecode Systems AB
22343  * Released under LGPL License.
22344  *
22345  * License: http://www.tinymce.com/license
22346  * Contributing: http://www.tinymce.com/contributing
22347  */
22348
22349 /*jshint scripturl:true */
22350
22351 /**
22352  * Include the base event class documentation.
22353  *
22354  * @include ../../../tools/docs/tinymce.Event.js
22355  */
22356
22357 /**
22358  * This class contains the core logic for a TinyMCE editor.
22359  *
22360  * @class tinymce.Editor
22361  * @mixes tinymce.util.Observable
22362  * @example
22363  * // Add a class to all paragraphs in the editor.
22364  * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
22365  *
22366  * // Gets the current editors selection as text
22367  * tinymce.activeEditor.selection.getContent({format: 'text'});
22368  *
22369  * // Creates a new editor instance
22370  * var ed = new tinymce.Editor('textareaid', {
22371  *     some_setting: 1
22372  * }, tinymce.EditorManager);
22373  *
22374  * // Select each item the user clicks on
22375  * ed.on('click', function(e) {
22376  *     ed.selection.select(e.target);
22377  * });
22378  *
22379  * ed.render();
22380  */
22381 define("tinymce/Editor", [
22382         "tinymce/dom/DOMUtils",
22383         "tinymce/AddOnManager",
22384         "tinymce/html/Node",
22385         "tinymce/dom/Serializer",
22386         "tinymce/html/Serializer",
22387         "tinymce/dom/Selection",
22388         "tinymce/Formatter",
22389         "tinymce/UndoManager",
22390         "tinymce/EnterKey",
22391         "tinymce/ForceBlocks",
22392         "tinymce/EditorCommands",
22393         "tinymce/util/URI",
22394         "tinymce/dom/ScriptLoader",
22395         "tinymce/dom/EventUtils",
22396         "tinymce/WindowManager",
22397         "tinymce/html/Schema",
22398         "tinymce/html/DomParser",
22399         "tinymce/util/Quirks",
22400         "tinymce/Env",
22401         "tinymce/util/Tools",
22402         "tinymce/util/Observable",
22403         "tinymce/Shortcuts"
22404 ], function(
22405         DOMUtils, AddOnManager, Node, DomSerializer, Serializer,
22406         Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
22407         URI, ScriptLoader, EventUtils, WindowManager,
22408         Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts
22409 ) {
22410         // Shorten these names
22411         var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
22412         var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
22413         var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
22414         var Event = EventUtils.Event;
22415         var isGecko = Env.gecko, ie = Env.ie, isOpera = Env.opera;
22416
22417         function getEventTarget(editor, eventName) {
22418                 if (eventName == 'selectionchange' || eventName == 'drop') {
22419                         return editor.getDoc();
22420                 }
22421
22422                 // Need to bind mousedown/mouseup etc to document not body in iframe mode
22423                 // Since the user might click on the HTML element not the BODY
22424                 if (!editor.inline && /^mouse|click|contextmenu/.test(eventName)) {
22425                         return editor.getDoc();
22426                 }
22427
22428                 return editor.getBody();
22429         }
22430
22431         /**
22432          * Include documentation for all the events.
22433          *
22434          * @include ../../../tools/docs/tinymce.Editor.js
22435          */
22436
22437         /**
22438          * Constructs a editor instance by id.
22439          *
22440          * @constructor
22441          * @method Editor
22442          * @param {String} id Unique id for the editor.
22443          * @param {Object} settings Settings for the editor.
22444          * @param {tinymce.EditorManager} editorManager EditorManager instance.
22445          * @author Moxiecode
22446          */
22447         function Editor(id, settings, editorManager) {
22448                 var self = this, documentBaseUrl, baseUri;
22449
22450                 documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
22451                 baseUri = editorManager.baseURI;
22452
22453                 /**
22454                  * Name/value collection with editor settings.
22455                  *
22456                  * @property settings
22457                  * @type Object
22458                  * @example
22459                  * // Get the value of the theme setting
22460                  * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
22461                  */
22462                 self.settings = settings = extend({
22463                         id: id,
22464                         theme: 'modern',
22465                         delta_width: 0,
22466                         delta_height: 0,
22467                         popup_css: '',
22468                         plugins: '',
22469                         document_base_url: documentBaseUrl,
22470                         add_form_submit_trigger: true,
22471                         submit_patch: true,
22472                         add_unload_trigger: true,
22473                         convert_urls: true,
22474                         relative_urls: true,
22475                         remove_script_host: true,
22476                         object_resizing: true,
22477                         doctype: '<!DOCTYPE html>',
22478                         visual: true,
22479                         font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
22480
22481                         // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
22482                         font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
22483                         forced_root_block: 'p',
22484                         hidden_input: true,
22485                         padd_empty_editor: true,
22486                         render_ui: true,
22487                         indentation: '30px',
22488                         inline_styles: true,
22489                         convert_fonts_to_spans: true,
22490                         indent: 'simple',
22491                         indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
22492                                 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
22493                         indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
22494                                 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
22495                         validate: true,
22496                         entity_encoding: 'named',
22497                         url_converter: self.convertURL,
22498                         url_converter_scope: self,
22499                         ie7_compat: true
22500                 }, settings);
22501
22502                 // TODO: Fix this
22503                 AddOnManager.settings = settings;
22504                 AddOnManager.baseURL = editorManager.baseURL;
22505
22506                 /**
22507                  * Editor instance id, normally the same as the div/textarea that was replaced.
22508                  *
22509                  * @property id
22510                  * @type String
22511                  */
22512                 self.id = settings.id = id;
22513
22514                 /**
22515                  * State to force the editor to return false on a isDirty call.
22516                  *
22517                  * @property isNotDirty
22518                  * @type Boolean
22519                  * @example
22520                  * function ajaxSave() {
22521                  *     var ed = tinymce.get('elm1');
22522                  *
22523                  *     // Save contents using some XHR call
22524                  *     alert(ed.getContent());
22525                  *
22526                  *     ed.isNotDirty = true; // Force not dirty state
22527                  * }
22528                  */
22529                 self.isNotDirty = true;
22530
22531                 /**
22532                  * Name/Value object containting plugin instances.
22533                  *
22534                  * @property plugins
22535                  * @type Object
22536                  * @example
22537                  * // Execute a method inside a plugin directly
22538                  * tinymce.activeEditor.plugins.someplugin.someMethod();
22539                  */
22540                 self.plugins = {};
22541
22542                 /**
22543                  * URI object to document configured for the TinyMCE instance.
22544                  *
22545                  * @property documentBaseURI
22546                  * @type tinymce.util.URI
22547                  * @example
22548                  * // Get relative URL from the location of document_base_url
22549                  * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
22550                  *
22551                  * // Get absolute URL from the location of document_base_url
22552                  * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
22553                  */
22554                 self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
22555                         base_uri: baseUri
22556                 });
22557
22558                 /**
22559                  * URI object to current document that holds the TinyMCE editor instance.
22560                  *
22561                  * @property baseURI
22562                  * @type tinymce.util.URI
22563                  * @example
22564                  * // Get relative URL from the location of the API
22565                  * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
22566                  *
22567                  * // Get absolute URL from the location of the API
22568                  * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
22569                  */
22570                 self.baseURI = baseUri;
22571
22572                 /**
22573                  * Array with CSS files to load into the iframe.
22574                  *
22575                  * @property contentCSS
22576                  * @type Array
22577                  */
22578                 self.contentCSS = [];
22579
22580                 /**
22581                  * Array of CSS styles to add to head of document when the editor loads.
22582                  *
22583                  * @property contentStyles
22584                  * @type Array
22585                  */
22586                 self.contentStyles = [];
22587
22588                 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
22589                 self.shortcuts = new Shortcuts(self);
22590
22591                 // Internal command handler objects
22592                 self.execCommands = {};
22593                 self.queryStateCommands = {};
22594                 self.queryValueCommands = {};
22595                 self.loadedCSS = {};
22596
22597                 self.suffix = editorManager.suffix;
22598                 self.editorManager = editorManager;
22599                 self.inline = settings.inline;
22600
22601                 // Call setup
22602                 self.execCallback('setup', self);
22603         }
22604
22605         Editor.prototype = {
22606                 /**
22607                  * Renderes the editor/adds it to the page.
22608                  *
22609                  * @method render
22610                  */
22611                 render: function() {
22612                         var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
22613
22614                         // Page is not loaded yet, wait for it
22615                         if (!Event.domLoaded) {
22616                                 DOM.bind(window, 'ready', function() {
22617                                         self.render();
22618                                 });
22619                                 return;
22620                         }
22621
22622                         self.editorManager.settings = settings;
22623
22624                         // Element not found, then skip initialization
22625                         if (!self.getElement()) {
22626                                 return;
22627                         }
22628
22629                         // No editable support old iOS versions etc
22630                         if (!Env.contentEditable) {
22631                                 return;
22632                         }
22633
22634                         // Hide target element early to prevent content flashing
22635                         if (!settings.inline) {
22636                                 self.orgVisibility = self.getElement().style.visibility;
22637                                 self.getElement().style.visibility = 'hidden';
22638                         } else {
22639                                 self.inline = true;
22640                         }
22641
22642                         var form = self.getElement().form || DOM.getParent(id, 'form');
22643                         if (form) {
22644                                 self.formElement = form;
22645
22646                                 // Add hidden input for non input elements inside form elements
22647                                 if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
22648                                         DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
22649                                 }
22650
22651                                 // Pass submit/reset from form to editor instance
22652                                 self.formEventDelegate = function(e) {
22653                                         self.fire(e.type, e);
22654                                 };
22655
22656                                 DOM.bind(form, 'submit reset', self.formEventDelegate);
22657
22658                                 // Reset contents in editor when the form is reset
22659                                 self.on('reset', function() {
22660                                         self.setContent(self.startContent, {format: 'raw'});
22661                                 });
22662
22663                                 // Check page uses id="submit" or name="submit" for it's submit button
22664                                 if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
22665                                         form._mceOldSubmit = form.submit;
22666                                         form.submit = function() {
22667                                                 self.editorManager.triggerSave();
22668                                                 self.isNotDirty = true;
22669
22670                                                 return form._mceOldSubmit(form);
22671                                         };
22672                                 }
22673                         }
22674
22675                         /**
22676                          * Window manager reference, use this to open new windows and dialogs.
22677                          *
22678                          * @property windowManager
22679                          * @type tinymce.WindowManager
22680                          * @example
22681                          * // Shows an alert message
22682                          * tinymce.activeEditor.windowManager.alert('Hello world!');
22683                          *
22684                          * // Opens a new dialog with the file.htm file and the size 320x240
22685                          * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
22686                          * tinymce.activeEditor.windowManager.open({
22687                          *    url: 'file.htm',
22688                          *    width: 320,
22689                          *    height: 240
22690                          * }, {
22691                          *    custom_param: 1
22692                          * });
22693                          */
22694                         self.windowManager = new WindowManager(self);
22695
22696                         if (settings.encoding == 'xml') {
22697                                 self.on('GetContent', function(e) {
22698                                         if (e.save) {
22699                                                 e.content = DOM.encode(e.content);
22700                                         }
22701                                 });
22702                         }
22703
22704                         if (settings.add_form_submit_trigger) {
22705                                 self.on('submit', function() {
22706                                         if (self.initialized) {
22707                                                 self.save();
22708                                         }
22709                                 });
22710                         }
22711
22712                         if (settings.add_unload_trigger) {
22713                                 self._beforeUnload = function() {
22714                                         if (self.initialized && !self.destroyed && !self.isHidden()) {
22715                                                 self.save({format: 'raw', no_events: true, set_dirty: false});
22716                                         }
22717                                 };
22718
22719                                 self.editorManager.on('BeforeUnload', self._beforeUnload);
22720                         }
22721
22722                         // Load scripts
22723                         function loadScripts() {
22724                                 var scriptLoader = ScriptLoader.ScriptLoader;
22725
22726                                 if (settings.language && settings.language != 'en') {
22727                                         settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
22728                                 }
22729
22730                                 if (settings.language_url) {
22731                                         scriptLoader.add(settings.language_url);
22732                                 }
22733
22734                                 if (settings.theme && typeof settings.theme != "function" &&
22735                                         settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
22736                                         ThemeManager.load(settings.theme, 'themes/' + settings.theme + '/theme' + suffix + '.js');
22737                                 }
22738
22739                                 if (Tools.isArray(settings.plugins)) {
22740                                         settings.plugins = settings.plugins.join(' ');
22741                                 }
22742
22743                                 each(settings.external_plugins, function(url, name) {
22744                                         PluginManager.load(name, url);
22745                                         settings.plugins += ' ' + name;
22746                                 });
22747
22748                                 each(settings.plugins.split(/[ ,]/), function(plugin) {
22749                                         plugin = trim(plugin);
22750
22751                                         if (plugin && !PluginManager.urls[plugin]) {
22752                                                 if (plugin.charAt(0) == '-') {
22753                                                         plugin = plugin.substr(1, plugin.length);
22754
22755                                                         var dependencies = PluginManager.dependencies(plugin);
22756
22757                                                         each(dependencies, function(dep) {
22758                                                                 var defaultSettings = {
22759                                                                         prefix:'plugins/',
22760                                                                         resource: dep,
22761                                                                         suffix:'/plugin' + suffix + '.js'
22762                                                                 };
22763
22764                                                                 dep = PluginManager.createUrl(defaultSettings, dep);
22765                                                                 PluginManager.load(dep.resource, dep);
22766                                                         });
22767                                                 } else {
22768                                                         PluginManager.load(plugin, {
22769                                                                 prefix: 'plugins/',
22770                                                                 resource: plugin,
22771                                                                 suffix: '/plugin' + suffix + '.js'
22772                                                         });
22773                                                 }
22774                                         }
22775                                 });
22776
22777                                 scriptLoader.loadQueue(function() {
22778                                         if (!self.removed) {
22779                                                 self.init();
22780                                         }
22781                                 });
22782                         }
22783
22784                         loadScripts();
22785                 },
22786
22787                 /**
22788                  * Initializes the editor this will be called automatically when
22789                  * all plugins/themes and language packs are loaded by the rendered method.
22790                  * This method will setup the iframe and create the theme and plugin instances.
22791                  *
22792                  * @method init
22793                  */
22794                 init: function() {
22795                         var self = this, settings = self.settings, elm = self.getElement();
22796                         var w, h, minHeight, n, o, url, bodyId, bodyClass, re, i, initializedPlugins = [];
22797
22798                         self.editorManager.add(self);
22799
22800                         settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
22801
22802                         /**
22803                          * Reference to the theme instance that was used to generate the UI.
22804                          *
22805                          * @property theme
22806                          * @type tinymce.Theme
22807                          * @example
22808                          * // Executes a method on the theme directly
22809                          * tinymce.activeEditor.theme.someMethod();
22810                          */
22811                         if (settings.theme) {
22812                                 if (typeof settings.theme != "function") {
22813                                         settings.theme = settings.theme.replace(/-/, '');
22814                                         o = ThemeManager.get(settings.theme);
22815                                         self.theme = new o(self, ThemeManager.urls[settings.theme]);
22816
22817                                         if (self.theme.init) {
22818                                                 self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''));
22819                                         }
22820                                 } else {
22821                                         self.theme = settings.theme;
22822                                 }
22823                         }
22824
22825                         function initPlugin(plugin) {
22826                                 var constr = PluginManager.get(plugin), url, pluginInstance;
22827
22828                                 url = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
22829                                 plugin = trim(plugin);
22830                                 if (constr && inArray(initializedPlugins, plugin) === -1) {
22831                                         each(PluginManager.dependencies(plugin), function(dep){
22832                                                 initPlugin(dep);
22833                                         });
22834
22835                                         pluginInstance = new constr(self, url);
22836
22837                                         self.plugins[plugin] = pluginInstance;
22838
22839                                         if (pluginInstance.init) {
22840                                                 pluginInstance.init(self, url);
22841                                                 initializedPlugins.push(plugin);
22842                                         }
22843                                 }
22844                         }
22845
22846                         // Create all plugins
22847                         each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
22848
22849                         // Enables users to override the control factory
22850                         self.fire('BeforeRenderUI');
22851
22852                         // Measure box
22853                         if (settings.render_ui && self.theme) {
22854                                 self.orgDisplay = elm.style.display;
22855
22856                                 if (typeof settings.theme != "function") {
22857                                         w = settings.width || elm.style.width || elm.offsetWidth;
22858                                         h = settings.height || elm.style.height || elm.offsetHeight;
22859                                         minHeight = settings.min_height || 100;
22860                                         re = /^[0-9\.]+(|px)$/i;
22861
22862                                         if (re.test('' + w)) {
22863                                                 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
22864                                         }
22865
22866                                         if (re.test('' + h)) {
22867                                                 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), minHeight);
22868                                         }
22869
22870                                         // Render UI
22871                                         o = self.theme.renderUI({
22872                                                 targetNode: elm,
22873                                                 width: w,
22874                                                 height: h,
22875                                                 deltaWidth: settings.delta_width,
22876                                                 deltaHeight: settings.delta_height
22877                                         });
22878
22879                                         // Resize editor
22880                                         if (!settings.content_editable) {
22881                                                 DOM.setStyles(o.sizeContainer || o.editorContainer, {
22882                                                         wi2dth: w,
22883                                                         // TODO: Fix this
22884                                                         h2eight: h
22885                                                 });
22886
22887                                                 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
22888                                                 if (h < minHeight) {
22889                                                         h = minHeight;
22890                                                 }
22891                                         }
22892                                 } else {
22893                                         o = settings.theme(self, elm);
22894
22895                                         // Convert element type to id:s
22896                                         if (o.editorContainer.nodeType) {
22897                                                 o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
22898                                         }
22899
22900                                         // Convert element type to id:s
22901                                         if (o.iframeContainer.nodeType) {
22902                                                 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
22903                                         }
22904
22905                                         // Use specified iframe height or the targets offsetHeight
22906                                         h = o.iframeHeight || elm.offsetHeight;
22907                                 }
22908
22909                                 self.editorContainer = o.editorContainer;
22910                         }
22911
22912                         // Load specified content CSS last
22913                         if (settings.content_css) {
22914                                 each(explode(settings.content_css), function(u) {
22915                                         self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
22916                                 });
22917                         }
22918
22919                         // Load specified content CSS last
22920                         if (settings.content_style) {
22921                                 self.contentStyles.push(settings.content_style);
22922                         }
22923
22924                         // Content editable mode ends here
22925                         if (settings.content_editable) {
22926                                 elm = n = o = null; // Fix IE leak
22927                                 return self.initContentBody();
22928                         }
22929
22930                         // User specified a document.domain value
22931                         if (document.domain && location.hostname != document.domain) {
22932                                 self.editorManager.relaxedDomain = document.domain;
22933                         }
22934
22935                         self.iframeHTML = settings.doctype + '<html><head>';
22936
22937                         // We only need to override paths if we have to
22938                         // IE has a bug where it remove site absolute urls to relative ones if this is specified
22939                         if (settings.document_base_url != self.documentBaseUrl) {
22940                                 self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
22941                         }
22942
22943                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
22944                         if (!Env.caretAfter && settings.ie7_compat) {
22945                                 self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
22946                         }
22947
22948                         self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
22949
22950                         // Load the CSS by injecting them into the HTML this will reduce "flicker"
22951                         for (i = 0; i < self.contentCSS.length; i++) {
22952                                 var cssUrl = self.contentCSS[i];
22953                                 self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />';
22954                                 self.loadedCSS[cssUrl] = true;
22955                         }
22956
22957                         bodyId = settings.body_id || 'tinymce';
22958                         if (bodyId.indexOf('=') != -1) {
22959                                 bodyId = self.getParam('body_id', '', 'hash');
22960                                 bodyId = bodyId[self.id] || bodyId;
22961                         }
22962
22963                         bodyClass = settings.body_class || '';
22964                         if (bodyClass.indexOf('=') != -1) {
22965                                 bodyClass = self.getParam('body_class', '', 'hash');
22966                                 bodyClass = bodyClass[self.id] || '';
22967                         }
22968
22969                         self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" ' +
22970                                 'onload="window.parent.tinymce.get(\'' + self.id + '\').fire(\'load\');"><br></body></html>';
22971
22972                         // Domain relaxing enabled, then set document domain
22973                         // TODO: Fix this old stuff
22974                         if (self.editorManager.relaxedDomain && (ie || (isOpera && parseFloat(window.opera.version()) < 11))) {
22975                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
22976                                 url = 'javascript:(function(){document.open();document.domain="' + document.domain + '";' +
22977                                         'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
22978                                         'document.close();ed.initContentBody();})()';
22979                         }
22980
22981                         // Create iframe
22982                         // TODO: ACC add the appropriate description on this.
22983                         n = DOM.add(o.iframeContainer, 'iframe', {
22984                                 id: self.id + "_ifr",
22985                                 src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
22986                                 frameBorder: '0',
22987                                 allowTransparency: "true",
22988                                 title: self.editorManager.translate(
22989                                         "Rich Text Area. Press ALT-F9 for menu. " +
22990                                         "Press ALT-F10 for toolbar. Press ALT-0 for help"
22991                                 ),
22992                                 style: {
22993                                         width: '100%',
22994                                         height: h,
22995                                         display: 'block' // Important for Gecko to render the iframe correctly
22996                                 }
22997                         });
22998
22999                         self.contentAreaContainer = o.iframeContainer;
23000
23001                         if (o.editorContainer) {
23002                                 DOM.get(o.editorContainer).style.display = self.orgDisplay;
23003                         }
23004
23005                         DOM.get(self.id).style.display = 'none';
23006                         DOM.setAttrib(self.id, 'aria-hidden', true);
23007
23008                         if (!self.editorManager.relaxedDomain || !url) {
23009                                 self.initContentBody();
23010                         }
23011
23012                         elm = n = o = null; // Cleanup
23013                 },
23014
23015                 /**
23016                  * This method get called by the init method ones the iframe is loaded.
23017                  * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
23018                  *
23019                  * @method initContentBody
23020                  * @private
23021                  */
23022                 initContentBody: function() {
23023                         var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText;
23024
23025                         // Restore visibility on target element
23026                         if (!settings.inline) {
23027                                 self.getElement().style.visibility = self.orgVisibility;
23028                         }
23029
23030                         // Setup iframe body
23031                         if ((!ie || !self.editorManager.relaxedDomain) && !settings.content_editable) {
23032                                 doc.open();
23033                                 doc.write(self.iframeHTML);
23034                                 doc.close();
23035
23036                                 if (self.editorManager.relaxedDomain) {
23037                                         doc.domain = self.editorManager.relaxedDomain;
23038                                 }
23039                         }
23040
23041                         if (settings.content_editable) {
23042                                 self.on('remove', function() {
23043                                         var body = this.getBody();
23044
23045                                         DOM.removeClass(body, 'mce-content-body');
23046                                         DOM.removeClass(body, 'mce-edit-focus');
23047                                         DOM.setAttrib(body, 'tabIndex', null);
23048                                         DOM.setAttrib(body, 'contentEditable', null);
23049                                 });
23050
23051                                 DOM.addClass(targetElm, 'mce-content-body');
23052                                 targetElm.tabIndex = -1;
23053                                 self.contentDocument = doc = settings.content_document || document;
23054                                 self.contentWindow = settings.content_window || window;
23055                                 self.bodyElement = targetElm;
23056
23057                                 // Prevent leak in IE
23058                                 settings.content_document = settings.content_window = null;
23059
23060                                 // TODO: Fix this
23061                                 settings.root_name = targetElm.nodeName.toLowerCase();
23062                         }
23063
23064                         // It will not steal focus while setting contentEditable
23065                         body = self.getBody();
23066                         body.disabled = true;
23067
23068                         if (!settings.readonly) {
23069                                 body.contentEditable = self.getParam('content_editable_state', true);
23070                         }
23071
23072                         body.disabled = false;
23073
23074                         /**
23075                          * Schema instance, enables you to validate elements and it's children.
23076                          *
23077                          * @property schema
23078                          * @type tinymce.html.Schema
23079                          */
23080                         self.schema = new Schema(settings);
23081
23082                         /**
23083                          * DOM instance for the editor.
23084                          *
23085                          * @property dom
23086                          * @type tinymce.dom.DOMUtils
23087                          * @example
23088                          * // Adds a class to all paragraphs within the editor
23089                          * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
23090                          */
23091                         self.dom = new DOMUtils(doc, {
23092                                 keep_values: true,
23093                                 url_converter: self.convertURL,
23094                                 url_converter_scope: self,
23095                                 hex_colors: settings.force_hex_style_colors,
23096                                 class_filter: settings.class_filter,
23097                                 update_styles: true,
23098                                 root_element: settings.content_editable ? self.id : null,
23099                                 schema: self.schema,
23100                                 onSetAttrib: function(e) {
23101                                         self.fire('SetAttrib', e);
23102                                 }
23103                         });
23104
23105                         /**
23106                          * HTML parser will be used when contents is inserted into the editor.
23107                          *
23108                          * @property parser
23109                          * @type tinymce.html.DomParser
23110                          */
23111                         self.parser = new DomParser(settings, self.schema);
23112
23113                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style
23114                         self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
23115                                 var i = nodes.length, node, dom = self.dom, value, internalName;
23116
23117                                 while (i--) {
23118                                         node = nodes[i];
23119                                         value = node.attr(name);
23120                                         internalName = 'data-mce-' + name;
23121
23122                                         // Add internal attribute if we need to we don't on a refresh of the document
23123                                         if (!node.attributes.map[internalName]) {
23124                                                 if (name === "style") {
23125                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
23126                                                 } else {
23127                                                         node.attr(internalName, self.convertURL(value, name, node.name));
23128                                                 }
23129                                         }
23130                                 }
23131                         });
23132
23133                         // Keep scripts from executing
23134                         self.parser.addNodeFilter('script', function(nodes) {
23135                                 var i = nodes.length, node;
23136
23137                                 while (i--) {
23138                                         node = nodes[i];
23139                                         node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
23140                                 }
23141                         });
23142
23143                         self.parser.addNodeFilter('#cdata', function(nodes) {
23144                                 var i = nodes.length, node;
23145
23146                                 while (i--) {
23147                                         node = nodes[i];
23148                                         node.type = 8;
23149                                         node.name = '#comment';
23150                                         node.value = '[CDATA[' + node.value + ']]';
23151                                 }
23152                         });
23153
23154                         self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
23155                                 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
23156
23157                                 while (i--) {
23158                                         node = nodes[i];
23159
23160                                         if (node.isEmpty(nonEmptyElements)) {
23161                                                 node.empty().append(new Node('br', 1)).shortEnded = true;
23162                                         }
23163                                 }
23164                         });
23165
23166                         /**
23167                          * DOM serializer for the editor. Will be used when contents is extracted from the editor.
23168                          *
23169                          * @property serializer
23170                          * @type tinymce.dom.Serializer
23171                          * @example
23172                          * // Serializes the first paragraph in the editor into a string
23173                          * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
23174                          */
23175                         self.serializer = new DomSerializer(settings, self);
23176
23177                         /**
23178                          * Selection instance for the editor.
23179                          *
23180                          * @property selection
23181                          * @type tinymce.dom.Selection
23182                          * @example
23183                          * // Sets some contents to the current selection in the editor
23184                          * tinymce.activeEditor.selection.setContent('Some contents');
23185                          *
23186                          * // Gets the current selection
23187                          * alert(tinymce.activeEditor.selection.getContent());
23188                          *
23189                          * // Selects the first paragraph found
23190                          * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
23191                          */
23192                         self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
23193
23194                         /**
23195                          * Formatter instance.
23196                          *
23197                          * @property formatter
23198                          * @type tinymce.Formatter
23199                          */
23200                         self.formatter = new Formatter(self);
23201
23202                         /**
23203                          * Undo manager instance, responsible for handling undo levels.
23204                          *
23205                          * @property undoManager
23206                          * @type tinymce.UndoManager
23207                          * @example
23208                          * // Undoes the last modification to the editor
23209                          * tinymce.activeEditor.undoManager.undo();
23210                          */
23211                         self.undoManager = new UndoManager(self);
23212
23213                         self.forceBlocks = new ForceBlocks(self);
23214                         self.enterKey = new EnterKey(self);
23215                         self.editorCommands = new EditorCommands(self);
23216
23217                         self.fire('PreInit');
23218
23219                         if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
23220                                 doc.body.spellcheck = false; // Gecko
23221                                 DOM.setAttrib(body, "spellcheck", "false");
23222                         }
23223
23224                         self.fire('PostRender');
23225
23226                         self.quirks = Quirks(self);
23227
23228                         if (settings.directionality) {
23229                                 body.dir = settings.directionality;
23230                         }
23231
23232                         if (settings.nowrap) {
23233                                 body.style.whiteSpace = "nowrap";
23234                         }
23235
23236                         if (settings.protect) {
23237                                 self.on('BeforeSetContent', function(e) {
23238                                         each(settings.protect, function(pattern) {
23239                                                 e.content = e.content.replace(pattern, function(str) {
23240                                                         return '<!--mce:protected ' + escape(str) + '-->';
23241                                                 });
23242                                         });
23243                                 });
23244                         }
23245
23246                         self.on('SetContent', function() {
23247                                 self.addVisual(self.getBody());
23248                         });
23249
23250                         // Remove empty contents
23251                         if (settings.padd_empty_editor) {
23252                                 self.on('PostProcess', function(e) {
23253                                         e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
23254                                 });
23255                         }
23256
23257                         self.load({initial: true, format: 'html'});
23258                         self.startContent = self.getContent({format: 'raw'});
23259
23260                         /**
23261                          * Is set to true after the editor instance has been initialized
23262                          *
23263                          * @property initialized
23264                          * @type Boolean
23265                          * @example
23266                          * function isEditorInitialized(editor) {
23267                          *     return editor && editor.initialized;
23268                          * }
23269                          */
23270                         self.initialized = true;
23271
23272                         each(self._pendingNativeEvents, function(name) {
23273                                 self.dom.bind(getEventTarget(self, name), name, function(e) {
23274                                         self.fire(e.type, e);
23275                                 });
23276                         });
23277
23278                         self.fire('init');
23279                         self.focus(true);
23280                         self.nodeChanged({initial: true});
23281                         self.execCallback('init_instance_callback', self);
23282
23283                         // Add editor specific CSS styles
23284                         if (self.contentStyles.length > 0) {
23285                                 contentCssText = '';
23286
23287                                 each(self.contentStyles, function(style) {
23288                                         contentCssText += style + "\r\n";
23289                                 });
23290
23291                                 self.dom.addStyle(contentCssText);
23292                         }
23293
23294                         // Load specified content CSS last
23295                         each(self.contentCSS, function(cssUrl) {
23296                                 if (!self.loadedCSS[cssUrl]) {
23297                                         self.dom.loadCSS(cssUrl);
23298                                         self.loadedCSS[cssUrl] = true;
23299                                 }
23300                         });
23301
23302                         // Handle auto focus
23303                         if (settings.auto_focus) {
23304                                 setTimeout(function () {
23305                                         var ed = self.editorManager.get(settings.auto_focus);
23306
23307                                         ed.selection.select(ed.getBody(), 1);
23308                                         ed.selection.collapse(1);
23309                                         ed.getBody().focus();
23310                                         ed.getWin().focus();
23311                                 }, 100);
23312                         }
23313
23314                         // Clean up references for IE
23315                         targetElm = doc = body = null;
23316                 },
23317
23318                 /**
23319                  * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
23320                  * it will also place DOM focus inside the editor.
23321                  *
23322                  * @method focus
23323                  * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
23324                  */
23325                 focus: function(skip_focus) {
23326                         var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
23327                         var controlElm, doc = self.getDoc(), body;
23328
23329                         if (!skip_focus) {
23330                                 // Get selected control element
23331                                 rng = selection.getRng();
23332                                 if (rng.item) {
23333                                         controlElm = rng.item(0);
23334                                 }
23335
23336                                 self._refreshContentEditable();
23337
23338                                 // Focus the window iframe
23339                                 if (!contentEditable) {
23340                                         // WebKit needs this call to fire focusin event properly see #5948
23341                                         // But Opera pre Blink engine will produce an empty selection so skip Opera
23342                                         if (!Env.opera) {
23343                                                 self.getBody().focus();
23344                                         }
23345
23346                                         self.getWin().focus();
23347                                 }
23348
23349                                 // Focus the body as well since it's contentEditable
23350                                 if (isGecko || contentEditable) {
23351                                         body = self.getBody();
23352
23353                                         // Check for setActive since it doesn't scroll to the element
23354                                         if (body.setActive) {
23355                                                 body.setActive();
23356                                         } else {
23357                                                 body.focus();
23358                                         }
23359
23360                                         if (contentEditable) {
23361                                                 selection.normalize();
23362                                         }
23363                                 }
23364
23365                                 // Restore selected control element
23366                                 // This is needed when for example an image is selected within a
23367                                 // layer a call to focus will then remove the control selection
23368                                 if (controlElm && controlElm.ownerDocument == doc) {
23369                                         rng = doc.body.createControlRange();
23370                                         rng.addElement(controlElm);
23371                                         rng.select();
23372                                 }
23373                         }
23374
23375                         if (self.editorManager.activeEditor != self) {
23376                                 if ((oed = self.editorManager.activeEditor)) {
23377                                         oed.fire('deactivate', {relatedTarget: self});
23378                                 }
23379
23380                                 self.fire('activate', {relatedTarget: oed});
23381                         }
23382
23383                         self.editorManager.activeEditor = self;
23384                 },
23385
23386                 /**
23387                  * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
23388                  * There new event model is a better way to add callback so this method might be removed in the future.
23389                  *
23390                  * @method execCallback
23391                  * @param {String} name Name of the callback to execute.
23392                  * @return {Object} Return value passed from callback function.
23393                  */
23394                 execCallback: function(name) {
23395                         var self = this, callback = self.settings[name], scope;
23396
23397                         if (!callback) {
23398                                 return;
23399                         }
23400
23401                         // Look through lookup
23402                         if (self.callbackLookup && (scope = self.callbackLookup[name])) {
23403                                 callback = scope.func;
23404                                 scope = scope.scope;
23405                         }
23406
23407                         if (typeof(callback) === 'string') {
23408                                 scope = callback.replace(/\.\w+$/, '');
23409                                 scope = scope ? resolve(scope) : 0;
23410                                 callback = resolve(callback);
23411                                 self.callbackLookup = self.callbackLookup || {};
23412                                 self.callbackLookup[name] = {func: callback, scope: scope};
23413                         }
23414
23415                         return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
23416                 },
23417
23418                 /**
23419                  * Translates the specified string by replacing variables with language pack items it will also check if there is
23420                  * a key mathcin the input.
23421                  *
23422                  * @method translate
23423                  * @param {String} text String to translate by the language pack data.
23424                  * @return {String} Translated string.
23425                  */
23426                 translate: function(text) {
23427                         var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
23428
23429                         if (!text) {
23430                                 return '';
23431                         }
23432
23433                         return i18n[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
23434                                 return i18n[lang + '.' + b] || '{#' + b + '}';
23435                         });
23436                 },
23437
23438                 /**
23439                  * Returns a language pack item by name/key.
23440                  *
23441                  * @method getLang
23442                  * @param {String} name Name/key to get from the language pack.
23443                  * @param {String} defaultVal Optional default value to retrive.
23444                  */
23445                 getLang: function(name, defaultVal) {
23446                         return (
23447                                 this.editorManager.i18n[(this.settings.language || 'en') + '.' + name] ||
23448                                 (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
23449                         );
23450                 },
23451
23452                 /**
23453                  * Returns a configuration parameter by name.
23454                  *
23455                  * @method getParam
23456                  * @param {String} name Configruation parameter to retrive.
23457                  * @param {String} defaultVal Optional default value to return.
23458                  * @param {String} type Optional type parameter.
23459                  * @return {String} Configuration parameter value or default value.
23460                  * @example
23461                  * // Returns a specific config value from the currently active editor
23462                  * var someval = tinymce.activeEditor.getParam('myvalue');
23463                  *
23464                  * // Returns a specific config value from a specific editor instance by id
23465                  * var someval2 = tinymce.get('my_editor').getParam('myvalue');
23466                  */
23467                 getParam: function(name, defaultVal, type) {
23468                         var value = name in this.settings ? this.settings[name] : defaultVal, output;
23469
23470                         if (type === 'hash') {
23471                                 output = {};
23472
23473                                 if (typeof(value) === 'string') {
23474                                         each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
23475                                                 value = value.split('=');
23476
23477                                                 if (value.length > 1) {
23478                                                         output[trim(value[0])] = trim(value[1]);
23479                                                 } else {
23480                                                         output[trim(value[0])] = trim(value);
23481                                                 }
23482                                         });
23483                                 } else {
23484                                         output = value;
23485                                 }
23486
23487                                 return output;
23488                         }
23489
23490                         return value;
23491                 },
23492
23493                 /**
23494                  * Distpaches out a onNodeChange event to all observers. This method should be called when you
23495                  * need to update the UI states or element path etc.
23496                  *
23497                  * @method nodeChanged
23498                  */
23499                 nodeChanged: function() {
23500                         var self = this, selection = self.selection, node, parents, root;
23501
23502                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
23503                         if (self.initialized && !self.settings.disable_nodechange) {
23504                                 // Get start node
23505                                 root = self.getBody();
23506                                 node = selection.getStart() || root;
23507                                 node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
23508
23509                                 // Edge case for <p>|<img></p>
23510                                 if (node.nodeName == 'IMG' && selection.isCollapsed()) {
23511                                         node = node.parentNode;
23512                                 }
23513
23514                                 // Get parents and add them to object
23515                                 parents = [];
23516                                 self.dom.getParent(node, function(node) {
23517                                         if (node === root) {
23518                                                 return true;
23519                                         }
23520
23521                                         parents.push(node);
23522                                 });
23523
23524                                 self.fire('NodeChange', {element: node, parents: parents});
23525                         }
23526                 },
23527
23528                 /**
23529                  * Adds a button that later gets created by the ControlManager. This is a shorter and easier method
23530                  * of adding buttons without the need to deal with the ControlManager directly. But it's also less
23531                  * powerfull if you need more control use the ControlManagers factory methods instead.
23532                  *
23533                  * @method addButton
23534                  * @param {String} name Button name to add.
23535                  * @param {Object} settings Settings object with title, cmd etc.
23536                  * @example
23537                  * // Adds a custom button to the editor that inserts contents when clicked
23538                  * tinymce.init({
23539                  *    ...
23540                  *
23541                  *    toolbar: 'example'
23542                  *
23543                  *    setup: function(ed) {
23544                  *       ed.addButton('example', {
23545                  *          title: 'My title',
23546                  *          image: '../js/tinymce/plugins/example/img/example.gif',
23547                  *          onclick: function() {
23548                  *             ed.insertContent('Hello world!!');
23549                  *          }
23550                  *       });
23551                  *    }
23552                  * });
23553                  */
23554                 addButton: function(name, settings) {
23555                         var self = this;
23556
23557                         if (settings.cmd) {
23558                                 settings.onclick = function() {
23559                                         self.execCommand(settings.cmd);
23560                                 };
23561                         }
23562
23563                         if (!settings.text && !settings.icon) {
23564                                 settings.icon = name;
23565                         }
23566
23567                         self.buttons = self.buttons || {};
23568                         settings.tooltip = settings.tooltip || settings.title;
23569                         self.buttons[name] = settings;
23570                 },
23571
23572                 /**
23573                  * Adds a menu item to be used in the menus of the modern theme.
23574                  *
23575                  * @method addMenuItem
23576                  * @param {String} name Menu item name to add.
23577                  * @param {Object} settings Settings object with title, cmd etc.
23578                  * @example
23579                  * // Adds a custom menu item to the editor that inserts contents when clicked
23580                  * // The context option allows you to add the menu item to an existing default menu
23581                  * tinymce.init({
23582                  *    ...
23583                  *
23584                  *    setup: function(ed) {
23585                  *       ed.addMenuItem('example', {
23586                  *          title: 'My menu item',
23587                  *          context: 'tools',
23588                  *          onclick: function() {
23589                  *             ed.insertContent('Hello world!!');
23590                  *          }
23591                  *       });
23592                  *    }
23593                  * });
23594                  */
23595                 addMenuItem: function(name, settings) {
23596                         var self = this;
23597
23598                         if (settings.cmd) {
23599                                 settings.onclick = function() {
23600                                         self.execCommand(settings.cmd);
23601                                 };
23602                         }
23603
23604                         self.menuItems = self.menuItems || {};
23605                         self.menuItems[name] = settings;
23606                 },
23607
23608                 /**
23609                  * Adds a custom command to the editor, you can also override existing commands with this method.
23610                  * The command that you add can be executed with execCommand.
23611                  *
23612                  * @method addCommand
23613                  * @param {String} name Command name to add/override.
23614                  * @param {addCommandCallback} callback Function to execute when the command occurs.
23615                  * @param {Object} scope Optional scope to execute the function in.
23616                  * @example
23617                  * // Adds a custom command that later can be executed using execCommand
23618                  * tinymce.init({
23619                  *    ...
23620                  *
23621                  *    setup: function(ed) {
23622                  *       // Register example command
23623                  *       ed.addCommand('mycommand', function(ui, v) {
23624                  *          ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
23625                  *       });
23626                  *    }
23627                  * });
23628                  */
23629                 addCommand: function(name, callback, scope) {
23630                         /**
23631                          * Callback function that gets called when a command is executed.
23632                          *
23633                          * @callback addCommandCallback
23634                          * @param {Boolean} ui Display UI state true/false.
23635                          * @param {Object} value Optional value for command.
23636                          * @return {Boolean} True/false state if the command was handled or not.
23637                          */
23638                         this.execCommands[name] = {func: callback, scope: scope || this};
23639                 },
23640
23641                 /**
23642                  * Adds a custom query state command to the editor, you can also override existing commands with this method.
23643                  * The command that you add can be executed with queryCommandState function.
23644                  *
23645                  * @method addQueryStateHandler
23646                  * @param {String} name Command name to add/override.
23647                  * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
23648                  * @param {Object} scope Optional scope to execute the function in.
23649                  */
23650                 addQueryStateHandler: function(name, callback, scope) {
23651                         /**
23652                          * Callback function that gets called when a queryCommandState is executed.
23653                          *
23654                          * @callback addQueryStateHandlerCallback
23655                          * @return {Boolean} True/false state if the command is enabled or not like is it bold.
23656                          */
23657                         this.queryStateCommands[name] = {func: callback, scope: scope || this};
23658                 },
23659
23660                 /**
23661                  * Adds a custom query value command to the editor, you can also override existing commands with this method.
23662                  * The command that you add can be executed with queryCommandValue function.
23663                  *
23664                  * @method addQueryValueHandler
23665                  * @param {String} name Command name to add/override.
23666                  * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
23667                  * @param {Object} scope Optional scope to execute the function in.
23668                  */
23669                 addQueryValueHandler: function(name, callback, scope) {
23670                         /**
23671                          * Callback function that gets called when a queryCommandValue is executed.
23672                          *
23673                          * @callback addQueryValueHandlerCallback
23674                          * @return {Object} Value of the command or undefined.
23675                          */
23676                         this.queryValueCommands[name] = {func: callback, scope: scope || this};
23677                 },
23678
23679                 /**
23680                  * Adds a keyboard shortcut for some command or function.
23681                  *
23682                  * @method addShortcut
23683                  * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
23684                  * @param {String} desc Text description for the command.
23685                  * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
23686                  * @param {Object} sc Optional scope to execute the function in.
23687                  * @return {Boolean} true/false state if the shortcut was added or not.
23688                  */
23689                 addShortcut: function(pattern, desc, cmdFunc, scope) {
23690                         this.shortcuts.add(pattern, desc, cmdFunc, scope);
23691                 },
23692
23693                 /**
23694                  * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
23695                  * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
23696                  * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
23697                  * return true it will handle the command as a internal browser command.
23698                  *
23699                  * @method execCommand
23700                  * @param {String} cmd Command name to execute, for example mceLink or Bold.
23701                  * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
23702                  * @param {mixed} value Optional command value, this can be anything.
23703                  * @param {Object} a Optional arguments object.
23704                  */
23705                 execCommand: function(cmd, ui, value, args) {
23706                         var self = this, state = 0, cmdItem;
23707
23708                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
23709                                 self.focus();
23710                         }
23711
23712                         args = extend({}, args);
23713                         args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
23714                         if (args.isDefaultPrevented()) {
23715                                 return false;
23716                         }
23717
23718                         // Registred commands
23719                         if ((cmdItem = self.execCommands[cmd])) {
23720                                 // Fall through on true
23721                                 if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
23722                                         self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
23723                                         return true;
23724                                 }
23725                         }
23726
23727                         // Plugin commands
23728                         each(self.plugins, function(p) {
23729                                 if (p.execCommand && p.execCommand(cmd, ui, value)) {
23730                                         self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
23731                                         state = true;
23732                                         return false;
23733                                 }
23734                         });
23735
23736                         if (state) {
23737                                 return state;
23738                         }
23739
23740                         // Theme commands
23741                         if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
23742                                 self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
23743                                 return true;
23744                         }
23745
23746                         // Editor commands
23747                         if (self.editorCommands.execCommand(cmd, ui, value)) {
23748                                 self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
23749                                 return true;
23750                         }
23751
23752                         // Browser commands
23753                         self.getDoc().execCommand(cmd, ui, value);
23754                         self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
23755                 },
23756
23757                 /**
23758                  * Returns a command specific state, for example if bold is enabled or not.
23759                  *
23760                  * @method queryCommandState
23761                  * @param {string} cmd Command to query state from.
23762                  * @return {Boolean} Command specific state, for example if bold is enabled or not.
23763                  */
23764                 queryCommandState: function(cmd) {
23765                         var self = this, queryItem, returnVal;
23766
23767                         // Is hidden then return undefined
23768                         if (self._isHidden()) {
23769                                 return;
23770                         }
23771
23772                         // Registred commands
23773                         if ((queryItem = self.queryStateCommands[cmd])) {
23774                                 returnVal = queryItem.func.call(queryItem.scope);
23775
23776                                 // Fall though on true
23777                                 if (returnVal !== true) {
23778                                         return returnVal;
23779                                 }
23780                         }
23781
23782                         // Editor commands
23783                         returnVal = self.editorCommands.queryCommandState(cmd);
23784                         if (returnVal !== -1) {
23785                                 return returnVal;
23786                         }
23787
23788                         // Browser commands
23789                         try {
23790                                 return self.getDoc().queryCommandState(cmd);
23791                         } catch (ex) {
23792                                 // Fails sometimes see bug: 1896577
23793                         }
23794                 },
23795
23796                 /**
23797                  * Returns a command specific value, for example the current font size.
23798                  *
23799                  * @method queryCommandValue
23800                  * @param {string} cmd Command to query value from.
23801                  * @return {Object} Command specific value, for example the current font size.
23802                  */
23803                 queryCommandValue: function(cmd) {
23804                         var self = this, queryItem, returnVal;
23805
23806                         // Is hidden then return undefined
23807                         if (self._isHidden()) {
23808                                 return;
23809                         }
23810
23811                         // Registred commands
23812                         if ((queryItem = self.queryValueCommands[cmd])) {
23813                                 returnVal = queryItem.func.call(queryItem.scope);
23814
23815                                 // Fall though on true
23816                                 if (returnVal !== true) {
23817                                         return returnVal;
23818                                 }
23819                         }
23820
23821                         // Editor commands
23822                         returnVal = self.editorCommands.queryCommandValue(cmd);
23823                         if (returnVal !== undefined) {
23824                                 return returnVal;
23825                         }
23826
23827                         // Browser commands
23828                         try {
23829                                 return self.getDoc().queryCommandValue(cmd);
23830                         } catch (ex) {
23831                                 // Fails sometimes see bug: 1896577
23832                         }
23833                 },
23834
23835                 /**
23836                  * Shows the editor and hides any textarea/div that the editor is supposed to replace.
23837                  *
23838                  * @method show
23839                  */
23840                 show: function() {
23841                         var self = this;
23842
23843                         DOM.show(self.getContainer());
23844                         DOM.hide(self.id);
23845                         self.load();
23846                         self.fire('show');
23847                 },
23848
23849                 /**
23850                  * Hides the editor and shows any textarea/div that the editor is supposed to replace.
23851                  *
23852                  * @method hide
23853                  */
23854                 hide: function() {
23855                         var self = this, doc = self.getDoc();
23856
23857                         // Fixed bug where IE has a blinking cursor left from the editor
23858                         if (ie && doc) {
23859                                 doc.execCommand('SelectAll');
23860                         }
23861
23862                         // We must save before we hide so Safari doesn't crash
23863                         self.save();
23864
23865                         // defer the call to hide to prevent an IE9 crash #4921
23866                         DOM.hide(self.getContainer());
23867                         DOM.setStyle(self.id, 'display', self.orgDisplay);
23868                         self.fire('hide');
23869                 },
23870
23871                 /**
23872                  * Returns true/false if the editor is hidden or not.
23873                  *
23874                  * @method isHidden
23875                  * @return {Boolean} True/false if the editor is hidden or not.
23876                  */
23877                 isHidden: function() {
23878                         return !DOM.isHidden(this.id);
23879                 },
23880
23881                 /**
23882                  * Sets the progress state, this will display a throbber/progess for the editor.
23883                  * This is ideal for asycronous operations like an AJAX save call.
23884                  *
23885                  * @method setProgressState
23886                  * @param {Boolean} state Boolean state if the progress should be shown or hidden.
23887                  * @param {Number} time Optional time to wait before the progress gets shown.
23888                  * @return {Boolean} Same as the input state.
23889                  * @example
23890                  * // Show progress for the active editor
23891                  * tinymce.activeEditor.setProgressState(true);
23892                  * 
23893                  * // Hide progress for the active editor
23894                  * tinymce.activeEditor.setProgressState(false);
23895                  * 
23896                  * // Show progress after 3 seconds
23897                  * tinymce.activeEditor.setProgressState(true, 3000);
23898                  */
23899                 setProgressState: function(state, time) {
23900                         this.fire('ProgressState', {state: state, time: time});
23901                 },
23902
23903                 /**
23904                  * Loads contents from the textarea or div element that got converted into an editor instance.
23905                  * This method will move the contents from that textarea or div into the editor by using setContent
23906                  * so all events etc that method has will get dispatched as well.
23907                  *
23908                  * @method load
23909                  * @param {Object} args Optional content object, this gets passed around through the whole load process.
23910                  * @return {String} HTML string that got set into the editor.
23911                  */
23912                 load: function(args) {
23913                         var self = this, elm = self.getElement(), html;
23914
23915                         if (elm) {
23916                                 args = args || {};
23917                                 args.load = true;
23918
23919                                 html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
23920                                 args.element = elm;
23921
23922                                 if (!args.no_events) {
23923                                         self.fire('LoadContent', args);
23924                                 }
23925
23926                                 args.element = elm = null;
23927
23928                                 return html;
23929                         }
23930                 },
23931
23932                 /**
23933                  * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
23934                  * This method will move the HTML contents from the editor into that textarea or div by getContent
23935                  * so all events etc that method has will get dispatched as well.
23936                  *
23937                  * @method save
23938                  * @param {Object} args Optional content object, this gets passed around through the whole save process.
23939                  * @return {String} HTML string that got set into the textarea/div.
23940                  */
23941                 save: function(args) {
23942                         var self = this, elm = self.getElement(), html, form;
23943
23944                         if (!elm || !self.initialized) {
23945                                 return;
23946                         }
23947
23948                         args = args || {};
23949                         args.save = true;
23950
23951                         args.element = elm;
23952                         html = args.content = self.getContent(args);
23953
23954                         if (!args.no_events) {
23955                                 self.fire('SaveContent', args);
23956                         }
23957
23958                         html = args.content;
23959
23960                         if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
23961                                 elm.innerHTML = html;
23962
23963                                 // Update hidden form element
23964                                 if ((form = DOM.getParent(self.id, 'form'))) {
23965                                         each(form.elements, function(elm) {
23966                                                 if (elm.name == self.id) {
23967                                                         elm.value = html;
23968                                                         return false;
23969                                                 }
23970                                         });
23971                                 }
23972                         } else {
23973                                 elm.value = html;
23974                         }
23975
23976                         args.element = elm = null;
23977
23978                         if (args.set_dirty !== false) {
23979                                 self.isNotDirty = true;
23980                         }
23981
23982                         return html;
23983                 },
23984
23985                 /**
23986                  * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
23987                  * the different cleanup rules options.
23988                  *
23989                  * @method setContent
23990                  * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
23991                  * @param {Object} args Optional content object, this gets passed around through the whole set process.
23992                  * @return {String} HTML string that got set into the editor.
23993                  * @example
23994                  * // Sets the HTML contents of the activeEditor editor
23995                  * tinymce.activeEditor.setContent('<span>some</span> html');
23996                  *
23997                  * // Sets the raw contents of the activeEditor editor
23998                  * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
23999                  *
24000                  * // Sets the content of a specific editor (my_editor in this example)
24001                  * tinymce.get('my_editor').setContent(data);
24002                  *
24003                  * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
24004                  * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
24005                  */
24006                 setContent: function(content, args) {
24007                         var self = this, body = self.getBody(), forcedRootBlockName;
24008
24009                         // Setup args object
24010                         args = args || {};
24011                         args.format = args.format || 'html';
24012                         args.set = true;
24013                         args.content = content;
24014
24015                         // Do preprocessing
24016                         if (!args.no_events) {
24017                                 self.fire('BeforeSetContent', args);
24018                         }
24019
24020                         content = args.content;
24021
24022                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
24023                         // It will also be impossible to place the caret in the editor unless there is a BR element present
24024                         if (content.length === 0 || /^\s+$/.test(content)) {
24025                                 forcedRootBlockName = self.settings.forced_root_block;
24026
24027                                 // Check if forcedRootBlock is configured and that the block is a valid child of the body
24028                                 if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
24029                                         if (ie && ie < 11) {
24030                                                 // IE renders BR elements in blocks so lets just add an empty block
24031                                                 content = '<' + forcedRootBlockName + '></' + forcedRootBlockName + '>';
24032                                         } else {
24033                                                 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
24034                                         }
24035                                 } else if (!ie) {
24036                                         // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
24037                                         content = '<br data-mce-bogus="1">';
24038                                 }
24039
24040                                 body.innerHTML = content;
24041
24042                                 self.fire('SetContent', args);
24043                         } else {
24044                                 // Parse and serialize the html
24045                                 if (args.format !== 'raw') {
24046                                         content = new Serializer({}, self.schema).serialize(
24047                                                 self.parser.parse(content, {isRootContent: true})
24048                                         );
24049                                 }
24050
24051                                 // Set the new cleaned contents to the editor
24052                                 args.content = trim(content);
24053                                 self.dom.setHTML(body, args.content);
24054
24055                                 // Do post processing
24056                                 if (!args.no_events) {
24057                                         self.fire('SetContent', args);
24058                                 }
24059
24060                                 // Don't normalize selection if the focused element isn't the body in
24061                                 // content editable mode since it will steal focus otherwise
24062                                 /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
24063                                         self.selection.normalize();
24064                                 }*/
24065                         }
24066
24067                         // Move selection to start of body if it's a after init setContent call
24068                         // This prevents IE 7/8 from moving focus to empty editors
24069                         if (!args.initial) {
24070                                 self.selection.select(body, true);
24071                                 self.selection.collapse(true);
24072                         }
24073
24074                         return args.content;
24075                 },
24076
24077                 /**
24078                  * Gets the content from the editor instance, this will cleanup the content before it gets returned using
24079                  * the different cleanup rules options.
24080                  *
24081                  * @method getContent
24082                  * @param {Object} args Optional content object, this gets passed around through the whole get process.
24083                  * @return {String} Cleaned content string, normally HTML contents.
24084                  * @example
24085                  * // Get the HTML contents of the currently active editor
24086                  * console.debug(tinymce.activeEditor.getContent());
24087                  *
24088                  * // Get the raw contents of the currently active editor
24089                  * tinymce.activeEditor.getContent({format: 'raw'});
24090                  *
24091                  * // Get content of a specific editor:
24092                  * tinymce.get('content id').getContent()
24093                  */
24094                 getContent: function(args) {
24095                         var self = this, content, body = self.getBody();
24096
24097                         // Setup args object
24098                         args = args || {};
24099                         args.format = args.format || 'html';
24100                         args.get = true;
24101                         args.getInner = true;
24102
24103                         // Do preprocessing
24104                         if (!args.no_events) {
24105                                 self.fire('BeforeGetContent', args);
24106                         }
24107
24108                         // Get raw contents or by default the cleaned contents
24109                         if (args.format == 'raw') {
24110                                 content = body.innerHTML;
24111                         } else if (args.format == 'text') {
24112                                 content = body.innerText || body.textContent;
24113                         } else {
24114                                 content = self.serializer.serialize(body, args);
24115                         }
24116
24117                         // Trim whitespace in beginning/end of HTML
24118                         if (args.format != 'text') {
24119                                 args.content = trim(content);
24120                         } else {
24121                                 args.content = content;
24122                         }
24123
24124                         // Do post processing
24125                         if (!args.no_events) {
24126                                 self.fire('GetContent', args);
24127                         }
24128
24129                         return args.content;
24130                 },
24131
24132                 /**
24133                  * Inserts content at caret position.
24134                  *
24135                  * @method insertContent
24136                  * @param {String} content Content to insert.
24137                  */
24138                 insertContent: function(content) {
24139                         this.execCommand('mceInsertContent', false, content);
24140                 },
24141
24142                 /**
24143                  * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
24144                  *
24145                  * @method isDirty
24146                  * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
24147                  * @example
24148                  * if (tinymce.activeEditor.isDirty())
24149                  *     alert("You must save your contents.");
24150                  */
24151                 isDirty: function() {
24152                         return !this.isNotDirty;
24153                 },
24154
24155                 /**
24156                  * Returns the editors container element. The container element wrappes in
24157                  * all the elements added to the page for the editor. Such as UI, iframe etc.
24158                  *
24159                  * @method getContainer
24160                  * @return {Element} HTML DOM element for the editor container.
24161                  */
24162                 getContainer: function() {
24163                         var self = this;
24164
24165                         if (!self.container) {
24166                                 self.container = DOM.get(self.editorContainer || self.id + '_parent');
24167                         }
24168
24169                         return self.container;
24170                 },
24171
24172                 /**
24173                  * Returns the editors content area container element. The this element is the one who
24174                  * holds the iframe or the editable element.
24175                  *
24176                  * @method getContentAreaContainer
24177                  * @return {Element} HTML DOM element for the editor area container.
24178                  */
24179                 getContentAreaContainer: function() {
24180                         return this.contentAreaContainer;
24181                 },
24182
24183                 /**
24184                  * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
24185                  *
24186                  * @method getElement
24187                  * @return {Element} HTML DOM element for the replaced element.
24188                  */
24189                 getElement: function() {
24190                         return DOM.get(this.settings.content_element || this.id);
24191                 },
24192
24193                 /**
24194                  * Returns the iframes window object.
24195                  *
24196                  * @method getWin
24197                  * @return {Window} Iframe DOM window object.
24198                  */
24199                 getWin: function() {
24200                         var self = this, elm;
24201
24202                         if (!self.contentWindow) {
24203                                 elm = DOM.get(self.id + "_ifr");
24204
24205                                 if (elm) {
24206                                         self.contentWindow = elm.contentWindow;
24207                                 }
24208                         }
24209
24210                         return self.contentWindow;
24211                 },
24212
24213                 /**
24214                  * Returns the iframes document object.
24215                  *
24216                  * @method getDoc
24217                  * @return {Document} Iframe DOM document object.
24218                  */
24219                 getDoc: function() {
24220                         var self = this, win;
24221
24222                         if (!self.contentDocument) {
24223                                 win = self.getWin();
24224
24225                                 if (win) {
24226                                         self.contentDocument = win.document;
24227                                 }
24228                         }
24229
24230                         return self.contentDocument;
24231                 },
24232
24233                 /**
24234                  * Returns the iframes body element.
24235                  *
24236                  * @method getBody
24237                  * @return {Element} Iframe body element.
24238                  */
24239                 getBody: function() {
24240                         return this.bodyElement || this.getDoc().body;
24241                 },
24242
24243                 /**
24244                  * URL converter function this gets executed each time a user adds an img, a or
24245                  * any other element that has a URL in it. This will be called both by the DOM and HTML
24246                  * manipulation functions.
24247                  *
24248                  * @method convertURL
24249                  * @param {string} url URL to convert.
24250                  * @param {string} name Attribute name src, href etc.
24251                  * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
24252                  * @return {string} Converted URL string.
24253                  */
24254                 convertURL: function(url, name, elm) {
24255                         var self = this, settings = self.settings;
24256
24257                         // Use callback instead
24258                         if (settings.urlconverter_callback) {
24259                                 return self.execCallback('urlconverter_callback', url, elm, true, name);
24260                         }
24261
24262                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
24263                         if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
24264                                 return url;
24265                         }
24266
24267                         // Convert to relative
24268                         if (settings.relative_urls) {
24269                                 return self.documentBaseURI.toRelative(url);
24270                         }
24271
24272                         // Convert to absolute
24273                         url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
24274
24275                         return url;
24276                 },
24277
24278                 /**
24279                  * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
24280                  *
24281                  * @method addVisual
24282                  * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
24283                  */
24284                 addVisual: function(elm) {
24285                         var self = this, settings = self.settings, dom = self.dom, cls;
24286
24287                         elm = elm || self.getBody();
24288
24289                         if (self.hasVisual === undefined) {
24290                                 self.hasVisual = settings.visual;
24291                         }
24292
24293                         each(dom.select('table,a', elm), function(elm) {
24294                                 var value;
24295
24296                                 switch (elm.nodeName) {
24297                                         case 'TABLE':
24298                                                 cls = settings.visual_table_class || 'mce-item-table';
24299                                                 value = dom.getAttrib(elm, 'border');
24300
24301                                                 if (!value || value == '0') {
24302                                                         if (self.hasVisual) {
24303                                                                 dom.addClass(elm, cls);
24304                                                         } else {
24305                                                                 dom.removeClass(elm, cls);
24306                                                         }
24307                                                 }
24308
24309                                                 return;
24310
24311                                         case 'A':
24312                                                 if (!dom.getAttrib(elm, 'href', false)) {
24313                                                         value = dom.getAttrib(elm, 'name') || elm.id;
24314                                                         cls = 'mce-item-anchor';
24315
24316                                                         if (value) {
24317                                                                 if (self.hasVisual) {
24318                                                                         dom.addClass(elm, cls);
24319                                                                 } else {
24320                                                                         dom.removeClass(elm, cls);
24321                                                                 }
24322                                                         }
24323                                                 }
24324
24325                                                 return;
24326                                 }
24327                         });
24328
24329                         self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
24330                 },
24331
24332                 /**
24333                  * Removes the editor from the dom and tinymce collection.
24334                  *
24335                  * @method remove
24336                  */
24337                 remove: function() {
24338                         var self = this, elm = self.getContainer(), doc = self.getDoc();
24339
24340                         if (!self.removed) {
24341                                 self.removed = 1; // Cancels post remove event execution
24342
24343                                 // Fixed bug where IE has a blinking cursor left from the editor
24344                                 if (ie && doc) {
24345                                         doc.execCommand('SelectAll');
24346                                 }
24347
24348                                 // We must save before we hide so Safari doesn't crash
24349                                 self.save();
24350
24351                                 DOM.setStyle(self.id, 'display', self.orgDisplay);
24352
24353                                 // Don't clear the window or document if content editable
24354                                 // is enabled since other instances might still be present
24355                                 if (!self.settings.content_editable) {
24356                                         Event.unbind(self.getWin());
24357                                         Event.unbind(self.getDoc());
24358                                 }
24359
24360                                 Event.unbind(self.getBody());
24361                                 Event.unbind(elm);
24362
24363                                 self.fire('remove');
24364
24365                                 self.editorManager.remove(self);
24366                                 DOM.remove(elm);
24367                         }
24368                 },
24369
24370                 bindNative: function(name) {
24371                         var self = this;
24372
24373                         if (self.initialized) {
24374                                 self.dom.bind(getEventTarget(self, name), name, function(e) {
24375                                         self.fire(name, e);
24376                                 });
24377                         } else {
24378                                 if (!self._pendingNativeEvents) {
24379                                         self._pendingNativeEvents = [name];
24380                                 } else {
24381                                         self._pendingNativeEvents.push(name);
24382                                 }
24383                         }
24384                 },
24385
24386                 unbindNative: function(name) {
24387                         var self = this;
24388
24389                         if (self.initialized) {
24390                                 self.dom.unbind(name);
24391                         }
24392                 },
24393
24394                 /**
24395                  * Destroys the editor instance by removing all events, element references or other resources
24396                  * that could leak memory. This method will be called automatically when the page is unloaded
24397                  * but you can also call it directly if you know what you are doing.
24398                  *
24399                  * @method destroy
24400                  * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
24401                  */
24402                 destroy: function(automatic) {
24403                         var self = this, form;
24404
24405                         // One time is enough
24406                         if (self.destroyed) {
24407                                 return;
24408                         }
24409
24410                         // We must unbind on Gecko since it would otherwise produce the pesky "attempt
24411                         // to run compile-and-go script on a cleared scope" message
24412                         if (isGecko) {
24413                                 Event.unbind(self.getDoc());
24414                                 Event.unbind(self.getWin());
24415                                 Event.unbind(self.getBody());
24416                         }
24417
24418                         if (!automatic) {
24419                                 self.editorManager.off('beforeunload', self._beforeUnload);
24420
24421                                 // Manual destroy
24422                                 if (self.theme && self.theme.destroy) {
24423                                         self.theme.destroy();
24424                                 }
24425
24426                                 // Destroy controls, selection and dom
24427                                 self.selection.destroy();
24428                                 self.dom.destroy();
24429                         }
24430
24431                         form = self.formElement;
24432                         if (form) {
24433                                 form.submit = form._mceOldSubmit;
24434                                 form._mceOldSubmit = null;
24435                                 DOM.unbind(form, 'submit reset', self.formEventDelegate);
24436                         }
24437
24438                         self.contentAreaContainer = self.formElement = self.container = null;
24439                         self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null;
24440
24441                         if (self.selection) {
24442                                 self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
24443                         }
24444
24445                         self.destroyed = 1;
24446                 },
24447
24448                 // Internal functions
24449
24450                 _refreshContentEditable: function() {
24451                         var self = this, body, parent;
24452
24453                         // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
24454                         if (self._isHidden()) {
24455                                 body = self.getBody();
24456                                 parent = body.parentNode;
24457
24458                                 parent.removeChild(body);
24459                                 parent.appendChild(body);
24460
24461                                 body.focus();
24462                         }
24463                 },
24464
24465                 _isHidden: function() {
24466                         var sel;
24467
24468                         if (!isGecko) {
24469                                 return 0;
24470                         }
24471
24472                         // Weird, wheres that cursor selection?
24473                         sel = this.selection.getSel();
24474                         return (!sel || !sel.rangeCount || sel.rangeCount === 0);
24475                 }
24476         };
24477
24478         extend(Editor.prototype, Observable);
24479
24480         return Editor;
24481 });
24482
24483 // Included from: js/tinymce/classes/util/I18n.js
24484
24485 /**
24486  * I18n.js
24487  *
24488  * Copyright, Moxiecode Systems AB
24489  * Released under LGPL License.
24490  *
24491  * License: http://www.tinymce.com/license
24492  * Contributing: http://www.tinymce.com/contributing
24493  */
24494
24495 /**
24496  * I18n class that handles translation of TinyMCE UI.
24497  * Uses po style with csharp style parameters.
24498  *
24499  * @class tinymce.util.I18n
24500  */
24501 define("tinymce/util/I18n", [], function() {
24502         "use strict";
24503
24504         var data = {};
24505
24506         return {
24507                 /**
24508                  * Adds translations for a specific language code.
24509                  *
24510                  * @method add
24511                  * @param {String} code Language code like sv_SE.
24512                  * @param {Array} items Name/value array with English en_US to sv_SE.
24513                  */
24514                 add: function(code, items) {
24515                         for (var name in items) {
24516                                 data[name] = items[name];
24517                         }
24518                 },
24519
24520                 /**
24521                  * Translates the specified text.
24522                  *
24523                  * It has a few formats:
24524                  * I18n.translate("Text");
24525                  * I18n.translate(["Text {0}/{1}", 0, 1]);
24526                  * I18n.translate({raw: "Raw string"});
24527                  *
24528                  * @method translate
24529                  * @param {String/Object/Array} text Text to translate.
24530                  * @return {String} String that got translated.
24531                  */
24532                 translate: function(text) {
24533                         if (typeof(text) == "undefined") {
24534                                 return text;
24535                         }
24536
24537                         if (typeof(text) != "string" && text.raw) {
24538                                 return text.raw;
24539                         }
24540
24541                         if (text.push) {
24542                                 var values = text.slice(1);
24543
24544                                 text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) {
24545                                         return values[match2];
24546                                 });
24547                         }
24548
24549                         return data[text] || text;
24550                 },
24551
24552                 data: data
24553         };
24554 });
24555
24556 // Included from: js/tinymce/classes/FocusManager.js
24557
24558 /**
24559  * FocusManager.js
24560  *
24561  * Copyright, Moxiecode Systems AB
24562  * Released under LGPL License.
24563  *
24564  * License: http://www.tinymce.com/license
24565  * Contributing: http://www.tinymce.com/contributing
24566  */
24567
24568 /**
24569  * This class manages the focus/blur state of the editor. This class is needed since some
24570  * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
24571  *
24572  * This class will fire two events focus and blur on the editor instances that got affected.
24573  * It will also handle the restore of selection when the focus is lost and returned.
24574  *
24575  * @class tinymce.FocusManager
24576  */
24577 define("tinymce/FocusManager", [
24578         "tinymce/dom/DOMUtils",
24579         "tinymce/Env"
24580 ], function(DOMUtils, Env) {
24581         /**
24582          * Constructs a new focus manager instance.
24583          *
24584          * @constructor FocusManager
24585          * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
24586          */
24587         function FocusManager(editorManager) {
24588                 function getActiveElement() {
24589                         try {
24590                                 return document.activeElement;
24591                         } catch (ex) {
24592                                 // IE sometimes fails to get the activeElement when resizing table
24593                                 // TODO: Investigate this
24594                                 return document.body;
24595                         }
24596                 }
24597
24598                 // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
24599                 // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
24600                 function createBookmark(rng) {
24601                         if (rng && rng.startContainer) {
24602                                 return {
24603                                         startContainer: rng.startContainer,
24604                                         startOffset: rng.startOffset,
24605                                         endContainer: rng.endContainer,
24606                                         endOffset: rng.endOffset
24607                                 };
24608                         }
24609
24610                         return rng;
24611                 }
24612
24613                 function bookmarkToRng(editor, bookmark) {
24614                         var rng;
24615
24616                         if (bookmark.startContainer) {
24617                                 rng = editor.getDoc().createRange();
24618                                 rng.setStart(bookmark.startContainer, bookmark.startOffset);
24619                                 rng.setEnd(bookmark.endContainer, bookmark.endOffset);
24620                         } else {
24621                                 rng = bookmark;
24622                         }
24623
24624                         return rng;
24625                 }
24626
24627                 function registerEvents(e) {
24628                         var editor = e.editor, lastRng, selectionChangeHandler;
24629
24630                         function isUIElement(elm) {
24631                                 return !!DOMUtils.DOM.getParent(elm, FocusManager.isEditorUIElement);
24632                         }
24633
24634                         editor.on('init', function() {
24635                                 // On IE take selection snapshot onbeforedeactivate
24636                                 if ("onbeforedeactivate" in document && Env.ie < 11) {
24637                                         editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
24638                                                 var ieSelection = editor.getDoc().selection;
24639
24640                                                 try {
24641                                                         lastRng = ieSelection && ieSelection.createRange ? ieSelection.createRange() : editor.selection.getRng();
24642                                                 } catch (ex) {
24643                                                         // IE throws "Unexcpected call to method or property access" some times so lets ignore it
24644                                                 }
24645                                         });
24646                                 } else if (editor.inline || Env.ie > 10) {
24647                                         // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
24648                                         editor.on('nodechange keyup', function() {
24649                                                 var isInBody, node = document.activeElement;
24650
24651                                                 // IE 11 reports active element as iframe not body of iframe
24652                                                 if (node && node.id == editor.id + '_ifr') {
24653                                                         node = editor.getBody();
24654                                                 }
24655
24656                                                 // Check if selection is within editor body
24657                                                 while (node) {
24658                                                         if (node == editor.getBody()) {
24659                                                                 isInBody = true;
24660                                                                 break;
24661                                                         }
24662
24663                                                         node = node.parentNode;
24664                                                 }
24665
24666                                                 if (isInBody) {
24667                                                         lastRng = editor.selection.getRng();
24668                                                 }
24669                                         });
24670
24671                                         // Handles the issue with WebKit not retaining selection within inline document
24672                                         // If the user releases the mouse out side the body while selecting a nodeChange won't
24673                                         // fire and there for the selection snapshot won't be stored
24674                                         // TODO: Optimize this since we only need to bind these on the active editor
24675                                         if (Env.webkit) {
24676                                                 selectionChangeHandler = function() {
24677                                                         var rng = editor.selection.getRng();
24678
24679                                                         // Store when it's non collapsed
24680                                                         if (!rng.collapsed) {
24681                                                                 lastRng = rng;
24682                                                         }
24683                                                 };
24684
24685                                                 // Bind selection handler
24686                                                 DOMUtils.DOM.bind(document, 'selectionchange', selectionChangeHandler);
24687
24688                                                 editor.on('remove', function() {
24689                                                         DOMUtils.DOM.unbind(document, 'selectionchange', selectionChangeHandler);
24690                                                 });
24691                                         }
24692                                 }
24693                         });
24694
24695                         editor.on('focusin', function() {
24696                                 var focusedEditor = editorManager.focusedEditor;
24697
24698                                 if (editor.selection.lastFocusBookmark) {
24699                                         editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
24700                                         editor.selection.lastFocusBookmark = null;
24701                                 }
24702
24703                                 if (focusedEditor != editor) {
24704                                         if (focusedEditor) {
24705                                                 focusedEditor.fire('blur', {focusedEditor: editor});
24706                                         }
24707
24708                                         editorManager.activeEditor = editor;
24709                                         editor.fire('focus', {blurredEditor: focusedEditor});
24710                                         editor.focus(false);
24711                                         editorManager.focusedEditor = editor;
24712                                 }
24713                         });
24714
24715                         editor.on('focusout', function() {
24716                                 editor.selection.lastFocusBookmark = createBookmark(lastRng);
24717
24718                                 window.setTimeout(function() {
24719                                         var focusedEditor = editorManager.focusedEditor;
24720
24721                                         // Focus from editorA into editorB then don't restore selection
24722                                         if (focusedEditor != editor) {
24723                                                 editor.selection.lastFocusBookmark = null;
24724                                         }
24725
24726                                         // Still the same editor the the blur was outside any editor UI
24727                                         if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
24728                                                 editor.fire('blur', {focusedEditor: null});
24729                                                 editorManager.focusedEditor = null;
24730                                                 editor.selection.lastFocusBookmark = null;
24731                                         }
24732                                 }, 0);
24733                         });
24734                 }
24735
24736                 editorManager.on('AddEditor', registerEvents);
24737         }
24738
24739         /**
24740          * Returns true if the specified element is part of the UI for example an button or text input.
24741          *
24742          * @method isEditorUIElement
24743          * @param  {Element} elm Element to check if it's part of the UI or not.
24744          * @return {Boolean} True/false state if the element is part of the UI or not.
24745          */
24746         FocusManager.isEditorUIElement = function(elm) {
24747                 return elm.className.indexOf('mce-') !== -1;
24748         };
24749
24750         return FocusManager;
24751 });
24752
24753 // Included from: js/tinymce/classes/EditorManager.js
24754
24755 /**
24756  * EditorManager.js
24757  *
24758  * Copyright, Moxiecode Systems AB
24759  * Released under LGPL License.
24760  *
24761  * License: http://www.tinymce.com/license
24762  * Contributing: http://www.tinymce.com/contributing
24763  */
24764
24765 /**
24766  * This class used as a factory for manager for tinymce.Editor instances.
24767  *
24768  * @example
24769  * tinymce.EditorManager.init({});
24770  *
24771  * @class tinymce.EditorManager
24772  * @mixes tinymce.util.Observable
24773  * @static
24774  */
24775 define("tinymce/EditorManager", [
24776         "tinymce/Editor",
24777         "tinymce/dom/DOMUtils",
24778         "tinymce/util/URI",
24779         "tinymce/Env",
24780         "tinymce/util/Tools",
24781         "tinymce/util/Observable",
24782         "tinymce/util/I18n",
24783         "tinymce/FocusManager"
24784 ], function(Editor, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
24785         var DOM = DOMUtils.DOM;
24786         var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
24787         var instanceCounter = 0, beforeUnloadDelegate;
24788
24789         var EditorManager = {
24790                 /**
24791                  * Major version of TinyMCE build.
24792                  *
24793                  * @property majorVersion
24794                  * @type String
24795                  */
24796                 majorVersion : '4',
24797
24798                 /**
24799                  * Minor version of TinyMCE build.
24800                  *
24801                  * @property minorVersion
24802                  * @type String
24803                  */
24804                 minorVersion : '0.5',
24805
24806                 /**
24807                  * Release date of TinyMCE build.
24808                  *
24809                  * @property releaseDate
24810                  * @type String
24811                  */
24812                 releaseDate: '2013-08-27',
24813
24814                 /**
24815                  * Collection of editor instances.
24816                  *
24817                  * @property editors
24818                  * @type Object
24819                  * @example
24820                  * for (edId in tinymce.editors)
24821                  *     tinymce.editors[edId].save();
24822                  */
24823                 editors: [],
24824
24825                 /**
24826                  * Collection of language pack data.
24827                  *
24828                  * @property i18n
24829                  * @type Object
24830                  */
24831                 i18n: I18n,
24832
24833                 /**
24834                  * Currently active editor instance.
24835                  *
24836                  * @property activeEditor
24837                  * @type tinymce.Editor
24838                  * @example
24839                  * tinyMCE.activeEditor.selection.getContent();
24840                  * tinymce.EditorManager.activeEditor.selection.getContent();
24841                  */
24842                 activeEditor: null,
24843
24844                 setup: function() {
24845                         var self = this, baseURL, documentBaseURL, suffix = "", preInit;
24846
24847                         // Get base URL for the current document
24848                         documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
24849                         if (!/[\/\\]$/.test(documentBaseURL)) {
24850                                 documentBaseURL += '/';
24851                         }
24852
24853                         // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
24854                         preInit = window.tinymce || window.tinyMCEPreInit;
24855                         if (preInit) {
24856                                 baseURL = preInit.base || preInit.baseURL;
24857                                 suffix = preInit.suffix;
24858                         } else {
24859                                 // Get base where the tinymce script is located
24860                                 var scripts = document.getElementsByTagName('script');
24861                                 for (var i = 0; i < scripts.length; i++) {
24862                                         var src = scripts[i].src;
24863
24864                                         if (/tinymce(\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
24865                                                 if (src.indexOf('.min') != -1) {
24866                                                         suffix = '.min';
24867                                                 }
24868
24869                                                 baseURL = src.substring(0, src.lastIndexOf('/'));
24870                                                 break;
24871                                         }
24872                                 }
24873                         }
24874
24875                         /**
24876                          * Base URL where the root directory if TinyMCE is located.
24877                          *
24878                          * @property baseURL
24879                          * @type String
24880                          */
24881                         self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
24882
24883                         /**
24884                          * Document base URL where the current document is located.
24885                          *
24886                          * @property documentBaseURL
24887                          * @type String
24888                          */
24889                         self.documentBaseURL = documentBaseURL;
24890
24891                         /**
24892                          * Absolute baseURI for the installation path of TinyMCE.
24893                          *
24894                          * @property baseURI
24895                          * @type tinymce.util.URI
24896                          */
24897                         self.baseURI = new URI(self.baseURL);
24898
24899                         /**
24900                          * Current suffix to add to each plugin/theme that gets loaded for example ".min".
24901                          *
24902                          * @property suffix
24903                          * @type String
24904                          */
24905                         self.suffix = suffix;
24906
24907                         self.focusManager = new FocusManager(self);
24908                 },
24909
24910                 /**
24911                  * Initializes a set of editors. This method will create editors based on various settings.
24912                  *
24913                  * @method init
24914                  * @param {Object} settings Settings object to be passed to each editor instance.
24915                  * @example
24916                  * // Initializes a editor using the longer method
24917                  * tinymce.EditorManager.init({
24918                  *    some_settings : 'some value'
24919                  * });
24920                  *
24921                  * // Initializes a editor instance using the shorter version
24922                  * tinyMCE.init({
24923                  *    some_settings : 'some value'
24924                  * });
24925                  */
24926                 init: function(settings) {
24927                         var self = this, editors = [], editor;
24928
24929                         function createId(elm) {
24930                                 var id = elm.id;
24931
24932                                 // Use element id, or unique name or generate a unique id
24933                                 if (!id) {
24934                                         id = elm.name;
24935
24936                                         if (id && !DOM.get(id)) {
24937                                                 id = elm.name;
24938                                         } else {
24939                                                 // Generate unique name
24940                                                 id = DOM.uniqueId();
24941                                         }
24942
24943                                         elm.setAttribute('id', id);
24944                                 }
24945
24946                                 return id;
24947                         }
24948
24949                         function execCallback(se, n, s) {
24950                                 var f = se[n];
24951
24952                                 if (!f) {
24953                                         return;
24954                                 }
24955
24956                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
24957                         }
24958
24959                         function hasClass(n, c) {
24960                                 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
24961                         }
24962
24963                         self.settings = settings;
24964
24965                         DOM.bind(window, 'ready', function() {
24966                                 var l, co;
24967
24968                                 execCallback(settings, 'onpageload');
24969
24970                                 if (settings.types) {
24971                                         // Process type specific selector
24972                                         each(settings.types, function(type) {
24973                                                 each(DOM.select(type.selector), function(elm) {
24974                                                         var editor = new Editor(createId(elm), extend({}, settings, type), self);
24975                                                         editors.push(editor);
24976                                                         editor.render(1);
24977                                                 });
24978                                         });
24979
24980                                         return;
24981                                 } else if (settings.selector) {
24982                                         // Process global selector
24983                                         each(DOM.select(settings.selector), function(elm) {
24984                                                 var editor = new Editor(createId(elm), settings, self);
24985                                                 editors.push(editor);
24986                                                 editor.render(1);
24987                                         });
24988
24989                                         return;
24990                                 }
24991
24992                                 // Fallback to old setting
24993                                 switch (settings.mode) {
24994                                         case "exact":
24995                                                 l = settings.elements || '';
24996
24997                                                 if(l.length > 0) {
24998                                                         each(explode(l), function(v) {
24999                                                                 if (DOM.get(v)) {
25000                                                                         editor = new Editor(v, settings, self);
25001                                                                         editors.push(editor);
25002                                                                         editor.render(true);
25003                                                                 } else {
25004                                                                         each(document.forms, function(f) {
25005                                                                                 each(f.elements, function(e) {
25006                                                                                         if (e.name === v) {
25007                                                                                                 v = 'mce_editor_' + instanceCounter++;
25008                                                                                                 DOM.setAttrib(e, 'id', v);
25009
25010                                                                                                 editor = new Editor(v, settings, self);
25011                                                                                                 editors.push(editor);
25012                                                                                                 editor.render(1);
25013                                                                                         }
25014                                                                                 });
25015                                                                         });
25016                                                                 }
25017                                                         });
25018                                                 }
25019                                                 break;
25020
25021                                         case "textareas":
25022                                         case "specific_textareas":
25023                                                 each(DOM.select('textarea'), function(elm) {
25024                                                         if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
25025                                                                 return;
25026                                                         }
25027
25028                                                         if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
25029                                                                 editor = new Editor(createId(elm), settings, self);
25030                                                                 editors.push(editor);
25031                                                                 editor.render(true);
25032                                                         }
25033                                                 });
25034                                                 break;
25035                                 }
25036
25037                                 // Call onInit when all editors are initialized
25038                                 if (settings.oninit) {
25039                                         l = co = 0;
25040
25041                                         each(editors, function(ed) {
25042                                                 co++;
25043
25044                                                 if (!ed.initialized) {
25045                                                         // Wait for it
25046                                                         ed.on('init', function() {
25047                                                                 l++;
25048
25049                                                                 // All done
25050                                                                 if (l == co) {
25051                                                                         execCallback(settings, 'oninit');
25052                                                                 }
25053                                                         });
25054                                                 } else {
25055                                                         l++;
25056                                                 }
25057
25058                                                 // All done
25059                                                 if (l == co) {
25060                                                         execCallback(settings, 'oninit');
25061                                                 }
25062                                         });
25063                                 }
25064                         });
25065                 },
25066
25067                 /**
25068                  * Returns a editor instance by id.
25069                  *
25070                  * @method get
25071                  * @param {String/Number} id Editor instance id or index to return.
25072                  * @return {tinymce.Editor} Editor instance to return.
25073                  * @example
25074                  * // Adds an onclick event to an editor by id (shorter version)
25075                  * tinymce.get('mytextbox').on('click', function(e) {
25076                  *    ed.windowManager.alert('Hello world!');
25077                  * });
25078                  *
25079                  * // Adds an onclick event to an editor by id (longer version)
25080                  * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
25081                  *    ed.windowManager.alert('Hello world!');
25082                  * });
25083                  */
25084                 get: function(id) {
25085                         if (id === undefined) {
25086                                 return this.editors;
25087                         }
25088
25089                         return this.editors[id];
25090                 },
25091
25092                 /**
25093                  * Adds an editor instance to the editor collection. This will also set it as the active editor.
25094                  *
25095                  * @method add
25096                  * @param {tinymce.Editor} editor Editor instance to add to the collection.
25097                  * @return {tinymce.Editor} The same instance that got passed in.
25098                  */
25099                 add: function(editor) {
25100                         var self = this, editors = self.editors;
25101
25102                         // Add named and index editor instance
25103                         editors[editor.id] = editor;
25104                         editors.push(editor);
25105
25106                         self.activeEditor = editor;
25107
25108                         /**
25109                          * Fires when an editor is added to the EditorManager collection.
25110                          *
25111                          * @event AddEditor
25112                          * @param {Object} e Event arguments.
25113                          */
25114                         self.fire('AddEditor', {editor: editor});
25115
25116                         if (!beforeUnloadDelegate) {
25117                                 beforeUnloadDelegate = function() {
25118                                         self.fire('BeforeUnload');
25119                                 };
25120
25121                                 DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
25122                         }
25123
25124                         return editor;
25125                 },
25126
25127                 /**
25128                  * Creates an editor instance and adds it to the EditorManager collection.
25129                  *
25130                  * @method createEditor
25131                  * @param {String} id Instance id to use for editor.
25132                  * @param {Object} settings Editor instance settings.
25133                  * @return {tinymce.Editor} Editor instance that got created.
25134                  */
25135                 createEditor: function(id, settings) {
25136                         return this.add(new Editor(id, settings, this));
25137                 },
25138
25139                 /**
25140                  * Removes a editor or editors form page.
25141                  *
25142                  * @example
25143                  * // Remove all editors bound to divs
25144                  * tinymce.remove('div');
25145                  *
25146                  * // Remove all editors bound to textareas
25147                  * tinymce.remove('textarea');
25148                  *
25149                  * // Remove all editors
25150                  * tinymce.remove();
25151                  *
25152                  * // Remove specific instance by id
25153                  * tinymce.remove('#id');
25154                  *
25155                  * @method remove
25156                  * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
25157                  * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
25158                  */
25159                 remove: function(selector) {
25160                         var self = this, i, editors = self.editors, editor;
25161
25162                         // Remove all editors
25163                         if (!selector) {
25164                                 for (i = editors.length - 1; i >= 0; i--) {
25165                                         self.remove(editors[i]);
25166                                 }
25167
25168                                 return;
25169                         }
25170
25171                         // Remove editors by selector
25172                         if (typeof(selector) == "string") {
25173                                 selector = selector.selector || selector;
25174
25175                                 each(DOM.select(selector), function(elm) {
25176                                         self.remove(editors[elm.id]);
25177                                 });
25178
25179                                 return;
25180                         }
25181
25182                         // Remove specific editor
25183                         editor = selector;
25184
25185                         // Not in the collection
25186                         if (!editors[editor.id]) {
25187                                 return null;
25188                         }
25189
25190                         delete editors[editor.id];
25191
25192                         for (i = 0; i < editors.length; i++) {
25193                                 if (editors[i] == editor) {
25194                                         editors.splice(i, 1);
25195                                         break;
25196                                 }
25197                         }
25198
25199                         // Select another editor since the active one was removed
25200                         if (self.activeEditor == editor) {
25201                                 self.activeEditor = editors[0];
25202                         }
25203
25204                         // Don't remove missing editor or removed instances
25205                         if (!editor || editor.removed) {
25206                                 return;
25207                         }
25208
25209                         editor.remove();
25210                         editor.destroy();
25211
25212                         /**
25213                          * Fires when an editor is removed from EditorManager collection.
25214                          *
25215                          * @event RemoveEditor
25216                          * @param {Object} e Event arguments.
25217                          */
25218                         self.fire('RemoveEditor', {editor: editor});
25219
25220                         if (!editors.length) {
25221                                 DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
25222                         }
25223
25224                         return editor;
25225                 },
25226
25227                 /**
25228                  * Executes a specific command on the currently active editor.
25229                  *
25230                  * @method execCommand
25231                  * @param {String} c Command to perform for example Bold.
25232                  * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
25233                  * @param {String} v Optional value parameter like for example an URL to a link.
25234                  * @return {Boolean} true/false if the command was executed or not.
25235                  */
25236                 execCommand: function(cmd, ui, value) {
25237                         var self = this, editor = self.get(value);
25238
25239                         // Manager commands
25240                         switch (cmd) {
25241                                 case "mceAddEditor":
25242                                         if (!self.get(value)) {
25243                                                 new Editor(value, self.settings, self).render();
25244                                         }
25245
25246                                         return true;
25247
25248                                 case "mceRemoveEditor":
25249                                         if (editor) {
25250                                                 editor.remove();
25251                                         }
25252
25253                                         return true;
25254
25255                                 case 'mceToggleEditor':
25256                                         if (!editor) {
25257                                                 self.execCommand('mceAddEditor', 0, value);
25258                                                 return true;
25259                                         }
25260
25261                                         if (editor.isHidden()) {
25262                                                 editor.show();
25263                                         } else {
25264                                                 editor.hide();
25265                                         }
25266
25267                                         return true;
25268                         }
25269
25270                         // Run command on active editor
25271                         if (self.activeEditor) {
25272                                 return self.activeEditor.execCommand(cmd, ui, value);
25273                         }
25274
25275                         return false;
25276                 },
25277
25278                 /**
25279                  * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
25280                  *
25281                  * @method triggerSave
25282                  * @example
25283                  * // Saves all contents
25284                  * tinyMCE.triggerSave();
25285                  */
25286                 triggerSave: function() {
25287                         each(this.editors, function(editor) {
25288                                 editor.save();
25289                         });
25290                 },
25291
25292                 /**
25293                  * Adds a language pack, this gets called by the loaded language files like en.js.
25294                  *
25295                  * @method addI18n
25296                  * @param {String} code Optional language code.
25297                  * @param {Object} items Name/value object with translations.
25298                  */
25299                 addI18n: function(code, items) {
25300                         I18n.add(code, items);
25301                 },
25302
25303                 /**
25304                  * Translates the specified string using the language pack items.
25305                  *
25306                  * @method translate
25307                  * @param {String/Array/Object} text String to translate
25308                  * @return {String} Translated string.
25309                  */
25310                 translate: function(text) {
25311                         return I18n.translate(text);
25312                 }
25313         };
25314
25315         extend(EditorManager, Observable);
25316
25317         EditorManager.setup();
25318
25319         // Export EditorManager as tinymce/tinymce in global namespace
25320         window.tinymce = window.tinyMCE = EditorManager;
25321
25322         return EditorManager;
25323 });
25324
25325 // Included from: js/tinymce/classes/LegacyInput.js
25326
25327 /**
25328  * LegacyInput.js
25329  *
25330  * Copyright, Moxiecode Systems AB
25331  * Released under LGPL License.
25332  *
25333  * License: http://www.tinymce.com/license
25334  * Contributing: http://www.tinymce.com/contributing
25335  */
25336
25337 define("tinymce/LegacyInput", [
25338         "tinymce/EditorManager",
25339         "tinymce/util/Tools"
25340 ], function(EditorManager, Tools) {
25341         var each = Tools.each, explode = Tools.explode;
25342
25343         EditorManager.on('AddEditor', function(e) {
25344                 var editor = e.editor;
25345
25346                 editor.on('preInit', function() {
25347                         var filters, fontSizes, dom, settings = editor.settings;
25348
25349                         function replaceWithSpan(node, styles) {
25350                                 each(styles, function(value, name) {
25351                                         if (value) {
25352                                                 dom.setStyle(node, name, value);
25353                                         }
25354                                 });
25355
25356                                 dom.rename(node, 'span');
25357                         }
25358
25359                         function convert(e) {
25360                                 dom = editor.dom;
25361
25362                                 if (settings.convert_fonts_to_spans) {
25363                                         each(dom.select('font,u,strike', e.node), function(node) {
25364                                                 filters[node.nodeName.toLowerCase()](dom, node);
25365                                         });
25366                                 }
25367                         }
25368
25369                         if (settings.inline_styles) {
25370                                 fontSizes = explode(settings.font_size_legacy_values);
25371
25372                                 filters = {
25373                                         font: function(dom, node) {
25374                                                 replaceWithSpan(node, {
25375                                                         backgroundColor: node.style.backgroundColor,
25376                                                         color: node.color,
25377                                                         fontFamily: node.face,
25378                                                         fontSize: fontSizes[parseInt(node.size, 10) - 1]
25379                                                 });
25380                                         },
25381
25382                                         u: function(dom, node) {
25383                                                 replaceWithSpan(node, {
25384                                                         textDecoration: 'underline'
25385                                                 });
25386                                         },
25387
25388                                         strike: function(dom, node) {
25389                                                 replaceWithSpan(node, {
25390                                                         textDecoration: 'line-through'
25391                                                 });
25392                                         }
25393                                 };
25394
25395                                 editor.on('PreProcess SetContent', convert);
25396                         }
25397                 });
25398         });
25399 });
25400
25401 // Included from: js/tinymce/classes/util/XHR.js
25402
25403 /**
25404  * XHR.js
25405  *
25406  * Copyright, Moxiecode Systems AB
25407  * Released under LGPL License.
25408  *
25409  * License: http://www.tinymce.com/license
25410  * Contributing: http://www.tinymce.com/contributing
25411  */
25412
25413 /**
25414  * This class enables you to send XMLHTTPRequests cross browser.
25415  * @class tinymce.util.XHR
25416  * @static
25417  * @example
25418  * // Sends a low level Ajax request
25419  * tinymce.util.XHR.send({
25420  *    url: 'someurl',
25421  *    success: function(text) {
25422  *       console.debug(text);
25423  *    }
25424  * });
25425  */
25426 define("tinymce/util/XHR", [], function() {
25427         return {
25428                 /**
25429                  * Sends a XMLHTTPRequest.
25430                  * Consult the Wiki for details on what settings this method takes.
25431                  *
25432                  * @method send
25433                  * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
25434                  */
25435                 send: function(settings) {
25436                         var xhr, count = 0;
25437
25438                         function ready() {
25439                                 if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
25440                                         if (settings.success && count < 10000 && xhr.status == 200) {
25441                                                 settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
25442                                         } else if (settings.error) {
25443                                                 settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
25444                                         }
25445
25446                                         xhr = null;
25447                                 } else {
25448                                         setTimeout(ready, 10);
25449                                 }
25450                         }
25451
25452                         // Default settings
25453                         settings.scope = settings.scope || this;
25454                         settings.success_scope = settings.success_scope || settings.scope;
25455                         settings.error_scope = settings.error_scope || settings.scope;
25456                         settings.async = settings.async === false ? false : true;
25457                         settings.data = settings.data || '';
25458
25459                         xhr = new XMLHttpRequest();
25460
25461                         if (xhr) {
25462                                 if (xhr.overrideMimeType) {
25463                                         xhr.overrideMimeType(settings.content_type);
25464                                 }
25465
25466                                 xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
25467
25468                                 if (settings.content_type) {
25469                                         xhr.setRequestHeader('Content-Type', settings.content_type);
25470                                 }
25471
25472                                 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
25473
25474                                 xhr.send(settings.data);
25475
25476                                 // Syncronous request
25477                                 if (!settings.async) {
25478                                         return ready();
25479                                 }
25480
25481                                 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
25482                                 setTimeout(ready, 10);
25483                         }
25484                 }
25485         };
25486 });
25487
25488 // Included from: js/tinymce/classes/util/JSON.js
25489
25490 /**
25491  * JSON.js
25492  *
25493  * Copyright, Moxiecode Systems AB
25494  * Released under LGPL License.
25495  *
25496  * License: http://www.tinymce.com/license
25497  * Contributing: http://www.tinymce.com/contributing
25498  */
25499
25500 /**
25501  * JSON parser and serializer class.
25502  *
25503  * @class tinymce.util.JSON
25504  * @static
25505  * @example
25506  * // JSON parse a string into an object
25507  * var obj = tinymce.util.JSON.parse(somestring);
25508  *
25509  * // JSON serialize a object into an string
25510  * var str = tinymce.util.JSON.serialize(obj);
25511  */
25512 define("tinymce/util/JSON", [], function() {
25513         function serialize(o, quote) {
25514                 var i, v, t, name;
25515
25516                 quote = quote || '"';
25517
25518                 if (o === null) {
25519                         return 'null';
25520                 }
25521
25522                 t = typeof o;
25523
25524                 if (t == 'string') {
25525                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
25526
25527                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
25528                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
25529                                 if (quote === '"' && a === "'") {
25530                                         return a;
25531                                 }
25532
25533                                 i = v.indexOf(b);
25534
25535                                 if (i + 1) {
25536                                         return '\\' + v.charAt(i + 1);
25537                                 }
25538
25539                                 a = b.charCodeAt().toString(16);
25540
25541                                 return '\\u' + '0000'.substring(a.length) + a;
25542                         }) + quote;
25543                 }
25544
25545                 if (t == 'object') {
25546                         if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
25547                                         for (i=0, v = '['; i<o.length; i++) {
25548                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
25549                                         }
25550
25551                                         return v + ']';
25552                                 }
25553
25554                                 v = '{';
25555
25556                                 for (name in o) {
25557                                         if (o.hasOwnProperty(name)) {
25558                                                 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
25559                                                         quote +':' + serialize(o[name], quote) : '';
25560                                         }
25561                                 }
25562
25563                                 return v + '}';
25564                 }
25565
25566                 return '' + o;
25567         }
25568
25569         return {
25570                 /**
25571                  * Serializes the specified object as a JSON string.
25572                  *
25573                  * @method serialize
25574                  * @param {Object} obj Object to serialize as a JSON string.
25575                  * @param {String} quote Optional quote string defaults to ".
25576                  * @return {string} JSON string serialized from input.
25577                  */
25578                 serialize: serialize,
25579
25580                 /**
25581                  * Unserializes/parses the specified JSON string into a object.
25582                  *
25583                  * @method parse
25584                  * @param {string} s JSON String to parse into a JavaScript object.
25585                  * @return {Object} Object from input JSON string or undefined if it failed.
25586                  */
25587                 parse: function(text) {
25588                         try {
25589                                 // Trick uglify JS
25590                                 return window[String.fromCharCode(101) + 'val']('(' + text + ')');
25591                         } catch (ex) {
25592                                 // Ignore
25593                         }
25594                 }
25595
25596                 /**#@-*/
25597         };
25598 });
25599
25600 // Included from: js/tinymce/classes/util/JSONRequest.js
25601
25602 /**
25603  * JSONRequest.js
25604  *
25605  * Copyright, Moxiecode Systems AB
25606  * Released under LGPL License.
25607  *
25608  * License: http://www.tinymce.com/license
25609  * Contributing: http://www.tinymce.com/contributing
25610  */
25611
25612 /**
25613  * This class enables you to use JSON-RPC to call backend methods.
25614  *
25615  * @class tinymce.util.JSONRequest
25616  * @example
25617  * var json = new tinymce.util.JSONRequest({
25618  *     url: 'somebackend.php'
25619  * });
25620  *
25621  * // Send RPC call 1
25622  * json.send({
25623  *     method: 'someMethod1',
25624  *     params: ['a', 'b'],
25625  *     success: function(result) {
25626  *         console.dir(result);
25627  *     }
25628  * });
25629  *
25630  * // Send RPC call 2
25631  * json.send({
25632  *     method: 'someMethod2',
25633  *     params: ['a', 'b'],
25634  *     success: function(result) {
25635  *         console.dir(result);
25636  *     }
25637  * });
25638  */
25639 define("tinymce/util/JSONRequest", [
25640         "tinymce/util/JSON",
25641         "tinymce/util/XHR",
25642         "tinymce/util/Tools"
25643 ], function(JSON, XHR, Tools) {
25644         var extend = Tools.extend;
25645
25646         function JSONRequest(settings) {
25647                 this.settings = extend({}, settings);
25648                 this.count = 0;
25649         }
25650
25651         /**
25652          * Simple helper function to send a JSON-RPC request without the need to initialize an object.
25653          * Consult the Wiki API documentation for more details on what you can pass to this function.
25654          *
25655          * @method sendRPC
25656          * @static
25657          * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
25658          */
25659         JSONRequest.sendRPC = function(o) {
25660                 return new JSONRequest().send(o);
25661         };
25662
25663         JSONRequest.prototype = {
25664                 /**
25665                  * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
25666                  *
25667                  * @method send
25668                  * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
25669                  */
25670                 send: function(args) {
25671                         var ecb = args.error, scb = args.success;
25672
25673                         args = extend(this.settings, args);
25674
25675                         args.success = function(c, x) {
25676                                 c = JSON.parse(c);
25677
25678                                 if (typeof(c) == 'undefined') {
25679                                         c = {
25680                                                 error : 'JSON Parse error.'
25681                                         };
25682                                 }
25683
25684                                 if (c.error) {
25685                                         ecb.call(args.error_scope || args.scope, c.error, x);
25686                                 } else {
25687                                         scb.call(args.success_scope || args.scope, c.result);
25688                                 }
25689                         };
25690
25691                         args.error = function(ty, x) {
25692                                 if (ecb) {
25693                                         ecb.call(args.error_scope || args.scope, ty, x);
25694                                 }
25695                         };
25696
25697                         args.data = JSON.serialize({
25698                                 id: args.id || 'c' + (this.count++),
25699                                 method: args.method,
25700                                 params: args.params
25701                         });
25702
25703                         // JSON content type for Ruby on rails. Bug: #1883287
25704                         args.content_type = 'application/json';
25705
25706                         XHR.send(args);
25707                 }
25708         };
25709
25710         return JSONRequest;
25711 });
25712
25713 // Included from: js/tinymce/classes/util/JSONP.js
25714
25715 /**
25716  * JSONP.js
25717  *
25718  * Copyright, Moxiecode Systems AB
25719  * Released under LGPL License.
25720  *
25721  * License: http://www.tinymce.com/license
25722  * Contributing: http://www.tinymce.com/contributing
25723  */
25724
25725 define("tinymce/util/JSONP", [
25726         "tinymce/dom/DOMUtils"
25727 ], function(DOMUtils) {
25728         return {
25729                 callbacks: {},
25730                 count: 0,
25731
25732                 send: function(settings) {
25733                         var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
25734                         var id = 'tinymce_jsonp_' + count;
25735
25736                         self.callbacks[count] = function(json) {
25737                                 dom.remove(id);
25738                                 delete self.callbacks[count];
25739
25740                                 settings.callback(json);
25741                         };
25742
25743                         dom.add(dom.doc.body, 'script', {
25744                                 id: id,
25745                                 src: settings.url,
25746                                 type: 'text/javascript'
25747                         });
25748
25749                         self.count++;
25750                 }
25751         };
25752 });
25753
25754 // Included from: js/tinymce/classes/util/LocalStorage.js
25755
25756 /**
25757  * LocalStorage.js
25758  *
25759  * Copyright, Moxiecode Systems AB
25760  * Released under LGPL License.
25761  *
25762  * License: http://www.tinymce.com/license
25763  * Contributing: http://www.tinymce.com/contributing
25764  */
25765
25766 /**
25767  * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
25768  * Storage is done using userData on IE 7 and a special serialization format. The format is designed
25769  * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
25770  * makes it possible to store for example HTML data.
25771  *
25772  * Storage format for userData:
25773  * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
25774  *
25775  * For example this data key1=value1,key2=value2 would be:
25776  * 4,key1,6,value1,4,key2,6,value2
25777  *
25778  * @class tinymce.util.LocalStorage
25779  * @static
25780  * @version 4.0
25781  * @example
25782  * tinymce.util.LocalStorage.setItem('key', 'value');
25783  * var value = tinymce.util.LocalStorage.getItem('key');
25784  */
25785 define("tinymce/util/LocalStorage", [], function() {
25786         var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
25787
25788         // Check for native support
25789         if (window.localStorage) {
25790                 return localStorage;
25791         }
25792
25793         userDataKey = "tinymce";
25794         storageElm = document.documentElement;
25795         hasOldIEDataSupport = !!storageElm.addBehavior;
25796
25797         if (hasOldIEDataSupport) {
25798                 storageElm.addBehavior('#default#userData');
25799         }
25800
25801         /**
25802          * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
25803          */
25804         function updateKeys() {
25805                 keys = [];
25806
25807                 for (var key in items) {
25808                         keys.push(key);
25809                 }
25810
25811                 LocalStorage.length = keys.length;
25812         }
25813
25814         /**
25815          * Loads the userData string and parses it into the items structure.
25816          */
25817         function load() {
25818                 var key, data, value, pos = 0;
25819
25820                 items = {};
25821
25822                 // localStorage can be disabled on WebKit/Gecko so make a dummy storage
25823                 if (!hasOldIEDataSupport) {
25824                         return;
25825                 }
25826
25827                 function next(end) {
25828                         var value, nextPos;
25829
25830                         nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
25831                         if (nextPos === -1 || nextPos > data.length) {
25832                                 return null;
25833                         }
25834
25835                         value = data.substring(pos, nextPos);
25836                         pos = nextPos + 1;
25837
25838                         return value;
25839                 }
25840
25841                 storageElm.load(userDataKey);
25842                 data = storageElm.getAttribute(userDataKey) || '';
25843
25844                 do {
25845                         key = next(parseInt(next(), 32) || 0);
25846                         if (key !== null) {
25847                                 value = next(parseInt(next(), 32) || 0);
25848                                 items[key] = value;
25849                         }
25850                 } while (key !== null);
25851
25852                 updateKeys();
25853         }
25854
25855         /**
25856          * Saves the items structure into a the userData format.
25857          */
25858         function save() {
25859                 var value, data = '';
25860
25861                 // localStorage can be disabled on WebKit/Gecko so make a dummy storage
25862                 if (!hasOldIEDataSupport) {
25863                         return;
25864                 }
25865
25866                 for (var key in items) {
25867                         value = items[key];
25868                         data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
25869                 }
25870
25871                 storageElm.setAttribute(userDataKey, data);
25872                 storageElm.save(userDataKey);
25873                 updateKeys();
25874         }
25875
25876         LocalStorage = {
25877                 /**
25878                  * Length of the number of items in storage.
25879                  *
25880                  * @property length
25881                  * @type Number
25882                  * @return {Number} Number of items in storage.
25883                  */
25884                 //length:0,
25885
25886                 /**
25887                  * Returns the key name by index.
25888                  *
25889                  * @method key
25890                  * @param {Number} index Index of key to return.
25891                  * @return {String} Key value or null if it wasn't found.
25892                  */
25893                 key: function(index) {
25894                         return keys[index];
25895                 },
25896
25897                 /**
25898                  * Returns the value if the specified key or null if it wasn't found.
25899                  *
25900                  * @method getItem
25901                  * @param {String} key Key of item to retrive.
25902                  * @return {String} Value of the specified item or null if it wasn't found.
25903                  */
25904                 getItem: function(key) {
25905                         return key in items ? items[key] : null;
25906                 },
25907
25908                 /**
25909                  * Sets the value of the specified item by it's key.
25910                  *
25911                  * @method setItem
25912                  * @param {String} key Key of the item to set.
25913                  * @param {String} value Value of the item to set.
25914                  */
25915                 setItem: function(key, value) {
25916                         items[key] = "" + value;
25917                         save();
25918                 },
25919
25920                 /**
25921                  * Removes the specified item by key.
25922                  *
25923                  * @method removeItem
25924                  * @param {String} key Key of item to remove.
25925                  */
25926                 removeItem: function(key) {
25927                         delete items[key];
25928                         save();
25929                 },
25930
25931                 /**
25932                  * Removes all items.
25933                  *
25934                  * @method clear
25935                  */
25936                 clear: function() {
25937                         items = {};
25938                         save();
25939                 }
25940         };
25941
25942         load();
25943
25944         return LocalStorage;
25945 });
25946
25947 // Included from: js/tinymce/classes/Compat.js
25948
25949 /**
25950  * Compat.js
25951  *
25952  * Copyright, Moxiecode Systems AB
25953  * Released under LGPL License.
25954  *
25955  * License: http://www.tinymce.com/license
25956  * Contributing: http://www.tinymce.com/contributing
25957  */
25958
25959 /**
25960  * TinyMCE core class.
25961  *
25962  * @static
25963  * @class tinymce
25964  * @borrow-members tinymce.EditorManager
25965  * @borrow-members tinymce.util.Tools
25966  */
25967 define("tinymce/Compat", [
25968         "tinymce/dom/DOMUtils",
25969         "tinymce/dom/EventUtils",
25970         "tinymce/dom/ScriptLoader",
25971         "tinymce/AddOnManager",
25972         "tinymce/util/Tools",
25973         "tinymce/Env"
25974 ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
25975         var tinymce = window.tinymce;
25976
25977         /**
25978          * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
25979          * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
25980          * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
25981          * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
25982          */
25983         tinymce.DOM = DOMUtils.DOM;
25984         tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
25985         tinymce.PluginManager = AddOnManager.PluginManager;
25986         tinymce.ThemeManager = AddOnManager.ThemeManager;
25987
25988         tinymce.dom = tinymce.dom || {};
25989         tinymce.dom.Event = EventUtils.Event;
25990
25991         Tools.each(Tools, function(func, key) {
25992                 tinymce[key] = func;
25993         });
25994
25995         Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
25996                 tinymce[name] = Env[name.substr(2).toLowerCase()];
25997         });
25998
25999         return {};
26000 });
26001
26002 // Describe the different namespaces
26003
26004 /**
26005  * Root level namespace this contains classes directly releated to the TinyMCE editor.
26006  *
26007  * @namespace tinymce
26008  */
26009
26010 /**
26011  * Contains classes for handling the browsers DOM.
26012  *
26013  * @namespace tinymce.dom
26014  */
26015
26016 /**
26017  * Contains html parser and serializer logic.
26018  *
26019  * @namespace tinymce.html
26020  */
26021
26022 /**
26023  * Contains the different UI types such as buttons, listboxes etc.
26024  *
26025  * @namespace tinymce.ui
26026  */
26027
26028 /**
26029  * Contains various utility classes such as json parser, cookies etc.
26030  *
26031  * @namespace tinymce.util
26032  */
26033
26034 // Included from: js/tinymce/classes/ui/Layout.js
26035
26036 /**
26037  * Layout.js
26038  *
26039  * Copyright, Moxiecode Systems AB
26040  * Released under LGPL License.
26041  *
26042  * License: http://www.tinymce.com/license
26043  * Contributing: http://www.tinymce.com/contributing
26044  */
26045
26046 /**
26047  * Base layout manager class.
26048  *
26049  * @class tinymce.ui.Layout
26050  */
26051 define("tinymce/ui/Layout", [
26052         "tinymce/util/Class",
26053         "tinymce/util/Tools"
26054 ], function(Class, Tools) {
26055         "use strict";
26056
26057         return Class.extend({
26058                 Defaults: {
26059                         firstControlClass: 'first',
26060                         lastControlClass: 'last'
26061                 },
26062
26063                 /**
26064                  * Constructs a layout instance with the specified settings.
26065                  *
26066                  * @constructor
26067                  * @param {Object} settings Name/value object with settings.
26068                  */
26069                 init: function(settings) {
26070                         this.settings = Tools.extend({}, this.Defaults, settings);
26071                 },
26072
26073                 /**
26074                  * This method gets invoked before the layout renders the controls.
26075                  *
26076                  * @method preRender
26077                  * @param {tinymce.ui.Container} container Container instance to preRender.
26078                  */
26079                 preRender: function(container) {
26080                         container.addClass(this.settings.containerClass, 'body');
26081                 },
26082
26083                 /**
26084                  * Applies layout classes to the container.
26085                  *
26086                  * @private
26087                  */
26088                 applyClasses: function(container) {
26089                         var self = this, settings = self.settings, items, firstClass, lastClass;
26090
26091                         items = container.items().filter(':visible');
26092                         firstClass = settings.firstControlClass;
26093                         lastClass = settings.lastControlClass;
26094
26095                         items.each(function(item) {
26096                                 item.removeClass(firstClass).removeClass(lastClass);
26097
26098                                 if (settings.controlClass) {
26099                                         item.addClass(settings.controlClass);
26100                                 }
26101                         });
26102
26103                         items.eq(0).addClass(firstClass);
26104                         items.eq(-1).addClass(lastClass);
26105                 },
26106
26107                 /**
26108                  * Renders the specified container and any layout specific HTML.
26109                  *
26110                  * @method renderHtml
26111                  * @param {tinymce.ui.Container} container Container to render HTML for.
26112                  */
26113                 renderHtml: function(container) {
26114                         var self = this, settings = self.settings, items, html = '';
26115
26116                         items = container.items();
26117                         items.eq(0).addClass(settings.firstControlClass);
26118                         items.eq(-1).addClass(settings.lastControlClass);
26119
26120                         items.each(function(item) {
26121                                 if (settings.controlClass) {
26122                                         item.addClass(settings.controlClass);
26123                                 }
26124
26125                                 html += item.renderHtml();
26126                         });
26127
26128                         return html;
26129                 },
26130
26131                 /**
26132                  * Recalculates the positions of the controls in the specified container.
26133                  *
26134                  * @method recalc
26135                  * @param {tinymce.ui.Container} container Container instance to recalc.
26136                  */
26137                 recalc: function() {
26138                 },
26139
26140                 /**
26141                  * This method gets invoked after the layout renders the controls.
26142                  *
26143                  * @method postRender
26144                  * @param {tinymce.ui.Container} container Container instance to postRender.
26145                  */
26146                 postRender: function() {
26147                 }
26148         });
26149 });
26150
26151 // Included from: js/tinymce/classes/ui/AbsoluteLayout.js
26152
26153 /**
26154  * AbsoluteLayout.js
26155  *
26156  * Copyright, Moxiecode Systems AB
26157  * Released under LGPL License.
26158  *
26159  * License: http://www.tinymce.com/license
26160  * Contributing: http://www.tinymce.com/contributing
26161  */
26162
26163 /**
26164  * LayoutManager for absolute positioning. This layout manager is more of
26165  * a base class for other layouts but can be created and used directly.
26166  *
26167  * @-x-less AbsoluteLayout.less
26168  * @class tinymce.ui.AbsoluteLayout
26169  * @extends tinymce.ui.Layout
26170  */
26171 define("tinymce/ui/AbsoluteLayout", [
26172         "tinymce/ui/Layout"
26173 ], function(Layout) {
26174         "use strict";
26175
26176         return Layout.extend({
26177                 Defaults: {
26178                         containerClass: 'abs-layout',
26179                         controlClass: 'abs-layout-item'
26180                 },
26181
26182                 /**
26183                  * Recalculates the positions of the controls in the specified container.
26184                  *
26185                  * @method recalc
26186                  * @param {tinymce.ui.Container} container Container instance to recalc.
26187                  */
26188                 recalc: function(container) {
26189                         container.items().filter(':visible').each(function(ctrl) {
26190                                 var settings = ctrl.settings;
26191
26192                                 ctrl.layoutRect({
26193                                         x: settings.x,
26194                                         y: settings.y,
26195                                         w: settings.w,
26196                                         h: settings.h
26197                                 });
26198
26199                                 if (ctrl.recalc) {
26200                                         ctrl.recalc();
26201                                 }
26202                         });
26203                 },
26204
26205                 /**
26206                  * Renders the specified container and any layout specific HTML.
26207                  *
26208                  * @method renderHtml
26209                  * @param {tinymce.ui.Container} container Container to render HTML for.
26210                  */
26211                 renderHtml: function(container) {
26212                         return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
26213                 }
26214         });
26215 });
26216
26217 // Included from: js/tinymce/classes/ui/Tooltip.js
26218
26219 /**
26220  * Tooltip.js
26221  *
26222  * Copyright, Moxiecode Systems AB
26223  * Released under LGPL License.
26224  *
26225  * License: http://www.tinymce.com/license
26226  * Contributing: http://www.tinymce.com/contributing
26227  */
26228
26229 /**
26230  * Creates a tooltip instance.
26231  *
26232  * @-x-less ToolTip.less
26233  * @class tinymce.ui.ToolTip
26234  * @extends tinymce.ui.Control
26235  * @mixes tinymce.ui.Movable
26236  */
26237 define("tinymce/ui/Tooltip", [
26238         "tinymce/ui/Control",
26239         "tinymce/ui/Movable"
26240 ], function(Control, Movable) {
26241         return Control.extend({
26242                 Mixins: [Movable],
26243
26244                 Defaults: {
26245                         classes: 'widget tooltip tooltip-n'
26246                 },
26247
26248                 /**
26249                  * Sets/gets the current label text.
26250                  *
26251                  * @method text
26252                  * @param {String} [text] New label text.
26253                  * @return {String|tinymce.ui.Tooltip} Current text or current label instance.
26254                  */
26255                 text: function(value) {
26256                         var self = this;
26257
26258                         if (typeof(value) != "undefined") {
26259                                 self._value = value;
26260
26261                                 if (self._rendered) {
26262                                         self.getEl().lastChild.innerHTML = self.encode(value);
26263                                 }
26264
26265                                 return self;
26266                         }
26267
26268                         return self._value;
26269                 },
26270
26271                 /**
26272                  * Renders the control as a HTML string.
26273                  *
26274                  * @method renderHtml
26275                  * @return {String} HTML representing the control.
26276                  */
26277                 renderHtml: function() {
26278                         var self = this, prefix = self.classPrefix;
26279
26280                         return (
26281                                 '<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' +
26282                                         '<div class="' + prefix + 'tooltip-arrow"></div>' +
26283                                         '<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' +
26284                                 '</div>'
26285                         );
26286                 },
26287
26288                 /**
26289                  * Repaints the control after a layout operation.
26290                  *
26291                  * @method repaint
26292                  */
26293                 repaint: function() {
26294                         var self = this, style, rect;
26295
26296                         style = self.getEl().style;
26297                         rect = self._layoutRect;
26298
26299                         style.left = rect.x + 'px';
26300                         style.top = rect.y + 'px';
26301                         style.zIndex = 0xFFFF + 0xFFFF;
26302                 }
26303         });
26304 });
26305
26306 // Included from: js/tinymce/classes/ui/Widget.js
26307
26308 /**
26309  * Widget.js
26310  *
26311  * Copyright, Moxiecode Systems AB
26312  * Released under LGPL License.
26313  *
26314  * License: http://www.tinymce.com/license
26315  * Contributing: http://www.tinymce.com/contributing
26316  */
26317
26318 /**
26319  * Widget base class a widget is a control that has a tooltip and some basic states.
26320  *
26321  * @class tinymce.ui.Widget
26322  * @extends tinymce.ui.Control
26323  */
26324 define("tinymce/ui/Widget", [
26325         "tinymce/ui/Control",
26326         "tinymce/ui/Tooltip"
26327 ], function(Control, Tooltip) {
26328         "use strict";
26329
26330         var tooltip;
26331
26332         var Widget = Control.extend({
26333                 /**
26334                  * Constructs a instance with the specified settings.
26335                  *
26336                  * @constructor
26337                  * @param {Object} settings Name/value object with settings.
26338                  * @setting {String} tooltip Tooltip text to display when hovering.
26339                  * @setting {Boolean} autofocus True if the control should be focused when rendered.
26340                  * @setting {String} text Text to display inside widget.
26341                  */
26342                 init: function(settings) {
26343                         var self = this;
26344
26345                         self._super(settings);
26346                         self.canFocus = true;
26347
26348                         if (settings.tooltip && Widget.tooltips !== false) {
26349                                 self.on('mouseenter mouseleave', function(e) {
26350                                         var tooltip = self.tooltip().moveTo(-0xFFFF);
26351
26352                                         if (e.control == self && e.type == 'mouseenter') {
26353                                                 var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
26354
26355                                                 tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
26356                                                 tooltip.toggleClass('tooltip-nw', rel == 'bc-tl');
26357                                                 tooltip.toggleClass('tooltip-ne', rel == 'bc-tr');
26358
26359                                                 tooltip.moveRel(self.getEl(), rel);
26360                                         } else {
26361                                                 tooltip.hide();
26362                                         }
26363                                 });
26364                         }
26365
26366                         self.aria('label', settings.tooltip);
26367                 },
26368
26369                 /**
26370                  * Returns the current tooltip instance.
26371                  *
26372                  * @method tooltip
26373                  * @return {tinymce.ui.Tooltip} Tooltip instance.
26374                  */
26375                 tooltip: function() {
26376                         var self = this;
26377
26378                         if (!tooltip) {
26379                                 tooltip = new Tooltip({type: 'tooltip'});
26380                                 tooltip.renderTo(self.getContainerElm());
26381                         }
26382
26383                         return tooltip;
26384                 },
26385
26386                 /**
26387                  * Sets/gets the active state of the widget.
26388                  *
26389                  * @method active
26390                  * @param {Boolean} [state] State if the control is active.
26391                  * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
26392                  */
26393                 active: function(state) {
26394                         var self = this, undef;
26395
26396                         if (state !== undef) {
26397                                 self.aria('pressed', state);
26398                                 self.toggleClass('active', state);
26399                         }
26400
26401                         return self._super(state);
26402                 },
26403
26404                 /**
26405                  * Sets/gets the disabled state of the widget.
26406                  *
26407                  * @method disabled
26408                  * @param {Boolean} [state] State if the control is disabled.
26409                  * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
26410                  */
26411                 disabled: function(state) {
26412                         var self = this, undef;
26413
26414                         if (state !== undef) {
26415                                 self.aria('disabled', state);
26416                                 self.toggleClass('disabled', state);
26417                         }
26418
26419                         return self._super(state);
26420                 },
26421
26422                 /**
26423                  * Called after the control has been rendered.
26424                  *
26425                  * @method postRender
26426                  */
26427                 postRender: function() {
26428                         var self = this, settings = self.settings;
26429
26430                         self._rendered = true;
26431
26432                         self._super();
26433
26434                         if (!self.parent() && (settings.width || settings.height)) {
26435                                 self.initLayoutRect();
26436                                 self.repaint();
26437                         }
26438
26439                         if (settings.autofocus) {
26440                                 setTimeout(function() {
26441                                         self.focus();
26442                                 }, 0);
26443                         }
26444                 },
26445
26446                 /**
26447                  * Removes the current control from DOM and from UI collections.
26448                  *
26449                  * @method remove
26450                  * @return {tinymce.ui.Control} Current control instance.
26451                  */
26452                 remove: function() {
26453                         this._super();
26454
26455                         if (tooltip) {
26456                                 tooltip.remove();
26457                                 tooltip = null;
26458                         }
26459                 }
26460         });
26461
26462         return Widget;
26463 });
26464
26465 // Included from: js/tinymce/classes/ui/Button.js
26466
26467 /**
26468  * Button.js
26469  *
26470  * Copyright, Moxiecode Systems AB
26471  * Released under LGPL License.
26472  *
26473  * License: http://www.tinymce.com/license
26474  * Contributing: http://www.tinymce.com/contributing
26475  */
26476
26477 /**
26478  * This class is used to create buttons. You can create them directly or through the Factory.
26479  *
26480  * @example
26481  * // Create and render a button to the body element
26482  * tinymce.ui.Factory.create({
26483  *     type: 'button',
26484  *     text: 'My button'
26485  * }).renderTo(document.body);
26486  *
26487  * @-x-less Button.less
26488  * @class tinymce.ui.Button
26489  * @extends tinymce.ui.Widget
26490  */
26491 define("tinymce/ui/Button", [
26492         "tinymce/ui/Widget"
26493 ], function(Widget) {
26494         "use strict";
26495
26496         return Widget.extend({
26497                 Defaults: {
26498                         classes: "widget btn",
26499                         role: "button"
26500                 },
26501
26502                 /**
26503                  * Constructs a new button instance with the specified settings.
26504                  *
26505                  * @constructor
26506                  * @param {Object} settings Name/value object with settings.
26507                  * @setting {String} size Size of the button small|medium|large.
26508                  * @setting {String} image Image to use for icon.
26509                  * @setting {String} icon Icon to use for button.
26510                  */
26511                 init: function(settings) {
26512                         var self = this, size;
26513
26514                         self.on('click mousedown', function(e) {
26515                                 e.preventDefault();
26516                         });
26517
26518                         self._super(settings);
26519                         size = settings.size;
26520
26521                         if (settings.subtype) {
26522                                 self.addClass(settings.subtype);
26523                         }
26524
26525                         if (size) {
26526                                 self.addClass('btn-' + size);
26527                         }
26528                 },
26529
26530                 /**
26531                  * Repaints the button for example after it's been resizes by a layout engine.
26532                  *
26533                  * @method repaint
26534                  */
26535                 repaint: function() {
26536                         var btnStyle = this.getEl().firstChild.style;
26537
26538                         btnStyle.width = btnStyle.height = "100%";
26539
26540                         this._super();
26541                 },
26542
26543                 /**
26544                  * Renders the control as a HTML string.
26545                  *
26546                  * @method renderHtml
26547                  * @return {String} HTML representing the control.
26548                  */
26549                 renderHtml: function() {
26550                         var self = this, id = self._id, prefix = self.classPrefix;
26551                         var icon = self.settings.icon, image = '';
26552
26553                         if (self.settings.image) {
26554                                 icon = 'none';
26555                                 image = ' style="background-image: url(\'' + self.settings.image + '\')"';
26556                         }
26557
26558                         icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
26559
26560                         return (
26561                                 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
26562                                         '<button role="presentation" type="button" tabindex="-1">' +
26563                                                 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
26564                                                 (self._text ? (icon ? ' ' : '') + self.encode(self._text) : '') +
26565                                         '</button>' +
26566                                 '</div>'
26567                         );
26568                 }
26569         });
26570 });
26571
26572 // Included from: js/tinymce/classes/ui/ButtonGroup.js
26573
26574 /**
26575  * ButtonGroup.js
26576  *
26577  * Copyright, Moxiecode Systems AB
26578  * Released under LGPL License.
26579  *
26580  * License: http://www.tinymce.com/license
26581  * Contributing: http://www.tinymce.com/contributing
26582  */
26583
26584 /**
26585  * This control enables you to put multiple buttons into a group. This is
26586  * useful when you want to combine similar toolbar buttons into a group.
26587  *
26588  * @example
26589  * // Create and render a buttongroup with two buttons to the body element
26590  * tinymce.ui.Factory.create({
26591  *     type: 'buttongroup',
26592  *     items: [
26593  *         {text: 'Button A'},
26594  *         {text: 'Button B'}
26595  *     ]
26596  * }).renderTo(document.body);
26597  *
26598  * @-x-less ButtonGroup.less
26599  * @class tinymce.ui.ButtonGroup
26600  * @extends tinymce.ui.Container
26601  */
26602 define("tinymce/ui/ButtonGroup", [
26603         "tinymce/ui/Container"
26604 ], function(Container) {
26605         "use strict";
26606
26607         return Container.extend({
26608                 Defaults: {
26609                         defaultType: 'button',
26610                         role: 'toolbar'
26611                 },
26612
26613                 /**
26614                  * Renders the control as a HTML string.
26615                  *
26616                  * @method renderHtml
26617                  * @return {String} HTML representing the control.
26618                  */
26619                 renderHtml: function() {
26620                         var self = this, layout = self._layout;
26621
26622                         self.addClass('btn-group');
26623                         self.preRender();
26624                         layout.preRender(self);
26625
26626                         return (
26627                                 '<div id="' + self._id + '" class="' + self.classes() + '">'+
26628                                         '<div id="' + self._id + '-body">'+
26629                                                 (self.settings.html || '') + layout.renderHtml(self) +
26630                                         '</div>' +
26631                                 '</div>'
26632                         );
26633                 }
26634         });
26635 });
26636
26637 // Included from: js/tinymce/classes/ui/Checkbox.js
26638
26639 /**
26640  * Checkbox.js
26641  *
26642  * Copyright, Moxiecode Systems AB
26643  * Released under LGPL License.
26644  *
26645  * License: http://www.tinymce.com/license
26646  * Contributing: http://www.tinymce.com/contributing
26647  */
26648
26649 /**
26650  * This control creates a custom checkbox.
26651  *
26652  * @example
26653  * // Create and render a checkbox to the body element
26654  * tinymce.ui.Factory.create({
26655  *     type: 'checkbox',
26656  *     checked: true,
26657  *     text: 'My checkbox'
26658  * }).renderTo(document.body);
26659  *
26660  * @-x-less Checkbox.less
26661  * @class tinymce.ui.Checkbox
26662  * @extends tinymce.ui.Widget
26663  */
26664 define("tinymce/ui/Checkbox", [
26665         "tinymce/ui/Widget"
26666 ], function(Widget) {
26667         "use strict";
26668
26669         return Widget.extend({
26670                 Defaults: {
26671                         classes: "checkbox",
26672                         role: "checkbox",
26673                         checked: false
26674                 },
26675
26676                 /**
26677                  * Constructs a new Checkbox instance with the specified settings.
26678                  *
26679                  * @constructor
26680                  * @param {Object} settings Name/value object with settings.
26681                  * @setting {Boolean} checked True if the checkbox should be checked by default.
26682                  */
26683                 init: function(settings) {
26684                         var self = this;
26685
26686                         self._super(settings);
26687
26688                         self.on('click mousedown', function(e) {
26689                                 e.preventDefault();
26690                         });
26691
26692                         self.on('click', function(e) {
26693                                 e.preventDefault();
26694
26695                                 if (!self.disabled()) {
26696                                         self.checked(!self.checked());
26697                                 }
26698                         });
26699
26700                         self.checked(self.settings.checked);
26701                 },
26702
26703                 /**
26704                  * Getter/setter function for the checked state.
26705                  *
26706                  * @method checked
26707                  * @param {Boolean} [state] State to be set.
26708                  * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
26709                  */
26710                 checked: function(state) {
26711                         var self = this;
26712
26713                         if (typeof state != "undefined") {
26714                                 if (state) {
26715                                         self.addClass('checked');
26716                                 } else {
26717                                         self.removeClass('checked');
26718                                 }
26719
26720                                 self._checked = state;
26721                                 self.aria('checked', state);
26722
26723                                 return self;
26724                         }
26725
26726                         return self._checked;
26727                 },
26728
26729                 /**
26730                  * Getter/setter function for the value state.
26731                  *
26732                  * @method value
26733                  * @param {Boolean} [state] State to be set.
26734                  * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
26735                  */
26736                 value: function(state) {
26737                         return this.checked(state);
26738                 },
26739
26740                 /**
26741                  * Renders the control as a HTML string.
26742                  *
26743                  * @method renderHtml
26744                  * @return {String} HTML representing the control.
26745                  */
26746                 renderHtml: function() {
26747                         var self = this, id = self._id, prefix = self.classPrefix;
26748
26749                         return (
26750                                 '<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labeledby="' + id + '-al" tabindex="-1">' +
26751                                         '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
26752                                         '<span id="' + id +'-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
26753                                 '</div>'
26754                         );
26755                 }
26756         });
26757 });
26758
26759 // Included from: js/tinymce/classes/ui/PanelButton.js
26760
26761 /**
26762  * PanelButton.js
26763  *
26764  * Copyright, Moxiecode Systems AB
26765  * Released under LGPL License.
26766  *
26767  * License: http://www.tinymce.com/license
26768  * Contributing: http://www.tinymce.com/contributing
26769  */
26770
26771 /**
26772  * Creates a new panel button.
26773  *
26774  * @class tinymce.ui.PanelButton
26775  * @extends tinymce.ui.Button
26776  */
26777 define("tinymce/ui/PanelButton", [
26778         "tinymce/ui/Button",
26779         "tinymce/ui/FloatPanel"
26780 ], function(Button, FloatPanel) {
26781         "use strict";
26782
26783         return Button.extend({
26784                 /**
26785                  * Shows the panel for the button.
26786                  *
26787                  * @method showPanel
26788                  */
26789                 showPanel: function() {
26790                         var self = this, settings = self.settings;
26791
26792                         settings.panel.popover = true;
26793                         settings.panel.autohide = true;
26794                         self.active(true);
26795
26796                         if (!self.panel) {
26797                                 self.panel = new FloatPanel(settings.panel).on('hide', function() {
26798                                         self.active(false);
26799                                 }).parent(self).renderTo(self.getContainerElm());
26800                                 self.panel.fire('show');
26801                                 self.panel.reflow();
26802                         } else {
26803                                 self.panel.show();
26804                         }
26805
26806                         self.panel.moveRel(self.getEl(), settings.popoverAlign || 'bc-tc');
26807                 },
26808
26809                 /**
26810                  * Hides the panel for the button.
26811                  *
26812                  * @method hidePanel
26813                  */
26814                 hidePanel: function() {
26815                         var self = this;
26816
26817                         if (self.panel) {
26818                                 self.panel.hide();
26819                         }
26820                 },
26821
26822                 /**
26823                  * Called after the control has been rendered.
26824                  *
26825                  * @method postRender
26826                  */
26827                 postRender: function() {
26828                         var self = this;
26829
26830                         self.on('click', function(e) {
26831                                 if (e.control === self) {
26832                                         if (self.panel && self.panel.visible()) {
26833                                                 self.hidePanel();
26834                                         } else {
26835                                                 self.showPanel();
26836                                         }
26837                                 }
26838                         });
26839
26840                         return self._super();
26841                 }
26842         });
26843 });
26844
26845 // Included from: js/tinymce/classes/ui/ColorButton.js
26846
26847 /**
26848  * ColorButton.js
26849  *
26850  * Copyright, Moxiecode Systems AB
26851  * Released under LGPL License.
26852  *
26853  * License: http://www.tinymce.com/license
26854  * Contributing: http://www.tinymce.com/contributing
26855  */
26856
26857 /**
26858  * This class creates a color button control. This is a split button in which the main
26859  * button has a visual representation of the currently selected color. When clicked 
26860  * the caret button displays a color picker, allowing the user to select a new color.
26861  *
26862  * @-x-less ColorButton.less
26863  * @class tinymce.ui.ColorButton
26864  * @extends tinymce.ui.PanelButton
26865  */
26866 define("tinymce/ui/ColorButton", [
26867         "tinymce/ui/PanelButton",
26868         "tinymce/dom/DOMUtils"
26869 ], function(PanelButton, DomUtils) {
26870         "use strict";
26871         
26872         var DOM = DomUtils.DOM;
26873
26874         return PanelButton.extend({
26875                 /**
26876                  * Constructs a new ColorButton instance with the specified settings.
26877                  *
26878                  * @constructor
26879                  * @param {Object} settings Name/value object with settings.
26880                  */
26881                 init: function(settings) {
26882                         this._super(settings);
26883                         this.addClass('colorbutton');
26884                 },
26885
26886                 /**
26887                  * Getter/setter for the current color.
26888                  *
26889                  * @method color
26890                  * @param {String} [color] Color to set.
26891                  * @return {String|tinymce.ui.ColorButton} Current color or current instance.
26892                  */
26893                 color: function(color) {
26894                         if (color) {
26895                                 this._color = color;
26896                                 this.getEl('preview').style.backgroundColor = color;
26897                                 return this;
26898                         }
26899
26900                         return this._color;
26901                 },
26902
26903                 /**
26904                  * Renders the control as a HTML string.
26905                  *
26906                  * @method renderHtml
26907                  * @return {String} HTML representing the control.
26908                  */
26909                 renderHtml: function() {
26910                         var self = this, id = self._id, prefix = self.classPrefix;
26911                         var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
26912                         var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
26913
26914                         return (
26915                                 '<div id="' + id + '" class="' + self.classes() + '">' +
26916                                         '<button role="presentation" hidefocus type="button" tabindex="-1">' +
26917                                                 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
26918                                                 '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
26919                                                 (self._text ? (icon ? ' ' : '') + (self._text) : '') +
26920                                         '</button>' +
26921                                         '<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' +
26922                                                 ' <i class="' + prefix + 'caret"></i>' +
26923                                         '</button>' +
26924                                 '</div>'
26925                         );
26926                 },
26927                 
26928                 /**
26929                  * Called after the control has been rendered.
26930                  *
26931                  * @method postRender
26932                  */
26933                 postRender: function() {
26934                         var self = this, onClickHandler = self.settings.onclick;
26935
26936                         self.on('click', function(e) {
26937                                 if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
26938                                         e.stopImmediatePropagation();
26939                                         onClickHandler.call(self, e);
26940                                 }
26941                         });
26942
26943                         delete self.settings.onclick;
26944
26945                         return self._super();
26946                 }
26947                 
26948         });
26949 });
26950
26951 // Included from: js/tinymce/classes/ui/ComboBox.js
26952
26953 /**
26954  * ComboBox.js
26955  *
26956  * Copyright, Moxiecode Systems AB
26957  * Released under LGPL License.
26958  *
26959  * License: http://www.tinymce.com/license
26960  * Contributing: http://www.tinymce.com/contributing
26961  */
26962
26963 /**
26964  * This class creates a combobox control. Select box that you select a value from or
26965  * type a value into.
26966  *
26967  * @-x-less ComboBox.less
26968  * @class tinymce.ui.ComboBox
26969  * @extends tinymce.ui.Widget
26970  */
26971 define("tinymce/ui/ComboBox", [
26972         "tinymce/ui/Widget",
26973         "tinymce/ui/DomUtils"
26974 ], function(Widget, DomUtils) {
26975         "use strict";
26976
26977         return Widget.extend({
26978                 /**
26979                  * Constructs a new control instance with the specified settings.
26980                  *
26981                  * @constructor
26982                  * @param {Object} settings Name/value object with settings.
26983                  * @setting {String} placeholder Placeholder text to display.
26984                  */
26985                 init: function(settings) {
26986                         var self = this;
26987
26988                         self._super(settings);
26989                         self.addClass('combobox');
26990
26991                         self.on('click', function(e) {
26992                                 var elm = e.target;
26993
26994                                 while (elm) {
26995                                         if (elm.id && elm.id.indexOf('-open') != -1) {
26996                                                 self.fire('action');
26997                                         }
26998
26999                                         elm = elm.parentNode;
27000                                 }
27001                         });
27002
27003                         // TODO: Rework this
27004                         self.on('keydown', function(e) {
27005                                 if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
27006                                         self.parents().reverse().each(function(ctrl) {
27007                                                 e.preventDefault();
27008                                                 self.fire('change');
27009
27010                                                 if (ctrl.submit) {
27011                                                         ctrl.submit();
27012                                                         return false;
27013                                                 }
27014                                         });
27015                                 }
27016                         });
27017
27018                         if (settings.placeholder) {
27019                                 self.addClass('placeholder');
27020
27021                                 self.on('focusin', function() {
27022                                         if (!self._hasOnChange) {
27023                                                 DomUtils.on(self.getEl('inp'), 'change', function() {
27024                                                         self.fire('change');
27025                                                 });
27026
27027                                                 self._hasOnChange = true;
27028                                         }
27029
27030                                         if (self.hasClass('placeholder')) {
27031                                                 self.getEl('inp').value = '';
27032                                                 self.removeClass('placeholder');
27033                                         }
27034                                 });
27035
27036                                 self.on('focusout', function() {
27037                                         if (self.value().length === 0) {
27038                                                 self.getEl('inp').value = settings.placeholder;
27039                                                 self.addClass('placeholder');
27040                                         }
27041                                 });
27042                         }
27043                 },
27044
27045                 /**
27046                  * Getter/setter function for the control value.
27047                  *
27048                  * @method value
27049                  * @param {String} [value] Value to be set.
27050                  * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
27051                  */
27052                 value: function(value) {
27053                         var self = this;
27054
27055                         if (typeof(value) != "undefined") {
27056                                 self._value = value;
27057                                 self.removeClass('placeholder');
27058
27059                                 if (self._rendered) {
27060                                         self.getEl('inp').value = value;
27061                                 }
27062
27063                                 return self;
27064                         }
27065
27066                         if (self._rendered) {
27067                                 value = self.getEl('inp').value;
27068
27069                                 if (value != self.settings.placeholder) {
27070                                         return value;
27071                                 }
27072
27073                                 return '';
27074                         }
27075
27076                         return self._value;
27077                 },
27078
27079                 /**
27080                  * Getter/setter function for the disabled state.
27081                  *
27082                  * @method value
27083                  * @param {Boolean} [state] State to be set.
27084                  * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
27085                  */
27086                 disabled: function(state) {
27087                         var self = this;
27088
27089                         self._super(state);
27090
27091                         if (self._rendered) {
27092                                 self.getEl('inp').disabled = state;
27093                         }
27094                 },
27095
27096                 /**
27097                  * Focuses the input area of the control.
27098                  *
27099                  * @method focus
27100                  */
27101                 focus: function() {
27102                         this.getEl('inp').focus();
27103                 },
27104
27105                 /**
27106                  * Repaints the control after a layout operation.
27107                  *
27108                  * @method repaint
27109                  */
27110                 repaint: function() {
27111                         var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
27112                         var width, lineHeight;
27113
27114                         if (openElm) {
27115                                 width = rect.w - openElm.offsetWidth - 10;
27116                         } else {
27117                                 width = rect.w - 10;
27118                         }
27119
27120                         // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
27121                         var doc = document;
27122                         if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
27123                                 lineHeight = (self.layoutRect().h - 2) + 'px';
27124                         }
27125
27126                         DomUtils.css(elm.firstChild, {
27127                                 width: width,
27128                                 lineHeight: lineHeight
27129                         });
27130
27131                         self._super();
27132
27133                         return self;
27134                 },
27135
27136                 /**
27137                  * Post render method. Called after the control has been rendered to the target.
27138                  *
27139                  * @method postRender
27140                  * @return {tinymce.ui.ComboBox} Current combobox instance.
27141                  */
27142                 postRender: function() {
27143                         var self = this;
27144
27145                         DomUtils.on(this.getEl('inp'), 'change', function() {
27146                                 self.fire('change');
27147                         });
27148
27149                         return self._super();
27150                 },
27151
27152                 /**
27153                  * Renders the control as a HTML string.
27154                  *
27155                  * @method renderHtml
27156                  * @return {String} HTML representing the control.
27157                  */
27158                 renderHtml: function() {
27159                         var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
27160                         var value = settings.value || settings.placeholder || '';
27161                         var icon, text, openBtnHtml = '';
27162
27163                         icon = settings.icon ? prefix + 'ico ' + prefix + 'i-' + settings.icon : '';
27164                         text = self._text;
27165
27166                         if (icon || text) {
27167                                 openBtnHtml = (
27168                                         '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1">' +
27169                                                 '<button id="' + id + '-action" type="button" hidefocus tabindex="-1">' +
27170                                                         (icon ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
27171                                                         (text ? (icon ? ' ' : '') + text : '') +
27172                                                 '</button>' +
27173                                         '</div>'
27174                                 );
27175
27176                                 self.addClass('has-open');
27177                         }
27178
27179                         return (
27180                                 '<div id="' + id + '" class="' + self.classes() + '">' +
27181                                         '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
27182                                         value + '" hidefocus="true">' +
27183                                         openBtnHtml +
27184                                 '</div>'
27185                         );
27186                 }
27187         });
27188 });
27189
27190 // Included from: js/tinymce/classes/ui/Path.js
27191
27192 /**
27193  * Path.js
27194  *
27195  * Copyright, Moxiecode Systems AB
27196  * Released under LGPL License.
27197  *
27198  * License: http://www.tinymce.com/license
27199  * Contributing: http://www.tinymce.com/contributing
27200  */
27201
27202 /**
27203  * Creates a new path control.
27204  *
27205  * @-x-less Path.less
27206  * @class tinymce.ui.Path
27207  * @extends tinymce.ui.Control
27208  */
27209 define("tinymce/ui/Path", [
27210         "tinymce/ui/Control",
27211         "tinymce/ui/KeyboardNavigation"
27212 ], function(Control, KeyboardNavigation) {
27213         "use strict";
27214
27215         return Control.extend({
27216                 Defaults: {
27217                         delimiter: "\u00BB"
27218                 },
27219
27220                 /**
27221                  * Constructs a instance with the specified settings.
27222                  *
27223                  * @constructor
27224                  * @param {Object} settings Name/value object with settings.
27225                  * @setting {String} delimiter Delimiter to display between items in path.
27226                  */
27227                 init: function(settings) {
27228                         var self = this;
27229
27230                         self._super(settings);
27231                         self.addClass('path');
27232                         self.canFocus = true;
27233
27234                         self.on('click', function(e) {
27235                                 var index, target = e.target;
27236
27237                                 if ((index = target.getAttribute('data-index'))) {
27238                                         self.fire('select', {value: self.data()[index], index: index});
27239                                 }
27240                         });
27241                 },
27242
27243                 /**
27244                  * Focuses the current control.
27245                  *
27246                  * @method focus
27247                  * @return {tinymce.ui.Control} Current control instance.
27248                  */
27249                 focus: function() {
27250                         var self = this;
27251
27252                         self.keyNav = new KeyboardNavigation({
27253                                 root: self,
27254                                 enableLeftRight: true
27255                         });
27256
27257                         self.keyNav.focusFirst();
27258
27259                         return self;
27260                 },
27261
27262                 /**
27263                  * Sets/gets the data to be used for the path.
27264                  *
27265                  * @method data
27266                  * @param {Array} data Array with items name is rendered to path.
27267                  */
27268                 data: function(data) {
27269                         var self = this;
27270
27271                         if (typeof(data) !== "undefined") {
27272                                 self._data = data;
27273                                 self.update();
27274
27275                                 return self;
27276                         }
27277
27278                         return self._data;
27279                 },
27280
27281                 /**
27282                  * Updated the path.
27283                  *
27284                  * @private
27285                  */
27286                 update: function() {
27287                         this.innerHtml(this._getPathHtml());
27288                 },
27289
27290                 /**
27291                  * Called after the control has been rendered.
27292                  *
27293                  * @method postRender
27294                  */
27295                 postRender: function() {
27296                         var self = this;
27297
27298                         self._super();
27299
27300                         self.data(self.settings.data);
27301                 },
27302
27303                 /**
27304                  * Renders the control as a HTML string.
27305                  *
27306                  * @method renderHtml
27307                  * @return {String} HTML representing the control.
27308                  */
27309                 renderHtml: function() {
27310                         var self = this;
27311
27312                         return (
27313                                 '<div id="' + self._id + '" class="' + self.classPrefix + 'path">' +
27314                                         self._getPathHtml() +
27315                                 '</div>'
27316                         );
27317                 },
27318
27319                 _getPathHtml: function() {
27320                         var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix;
27321
27322                         for (i = 0, l = parts.length; i < l; i++) {
27323                                 html += (
27324                                         (i > 0 ? '<div class="'+ prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
27325                                         '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
27326                                         i + '" tabindex="-1" id="' + self._id + '-' + i +'">' + parts[i].name + '</div>'
27327                                 );
27328                         }
27329
27330                         if (!html) {
27331                                 html = '<div class="' + prefix + 'path-item">&nbsp;</div>';
27332                         }
27333
27334                         return html;
27335                 }
27336         });
27337 });
27338
27339 // Included from: js/tinymce/classes/ui/ElementPath.js
27340
27341 /**
27342  * ElementPath.js
27343  *
27344  * Copyright, Moxiecode Systems AB
27345  * Released under LGPL License.
27346  *
27347  * License: http://www.tinymce.com/license
27348  * Contributing: http://www.tinymce.com/contributing
27349  */
27350
27351 /**
27352  * This control creates an path for the current selections parent elements in TinyMCE.
27353  *
27354  * @class tinymce.ui.ElementPath
27355  * @extends tinymce.ui.Path
27356  */
27357 define("tinymce/ui/ElementPath", [
27358         "tinymce/ui/Path",
27359         "tinymce/EditorManager"
27360 ], function(Path, EditorManager) {
27361         return Path.extend({
27362                 /**
27363                  * Post render method. Called after the control has been rendered to the target.
27364                  *
27365                  * @method postRender
27366                  * @return {tinymce.ui.ElementPath} Current combobox instance.
27367                  */
27368                 postRender: function() {
27369                         var self = this, editor = EditorManager.activeEditor;
27370
27371                         function isBogus(elm) {
27372                                 return elm.nodeType === 1 && (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus'));
27373                         }
27374
27375                         self.on('select', function(e) {
27376                                 var parents = [], node, body = editor.getBody();
27377
27378                                 editor.focus();
27379
27380                                 node = editor.selection.getStart();
27381                                 while (node && node != body) {
27382                                         if (!isBogus(node)) {
27383                                                 parents.push(node);
27384                                         }
27385
27386                                         node = node.parentNode;
27387                                 }
27388
27389                                 editor.selection.select(parents[parents.length - 1 - e.index]);
27390                                 editor.nodeChanged();
27391                         });
27392
27393                         editor.on('nodeChange', function(e) {
27394                                 var parents = [], selectionParents = e.parents, i = selectionParents.length;
27395
27396                                 while (i--) {
27397                                         if (selectionParents[i].nodeType == 1 && !isBogus(selectionParents[i])) {
27398                                                 var args = editor.fire('ResolveName', {
27399                                                         name: selectionParents[i].nodeName.toLowerCase(),
27400                                                         target: selectionParents[i]
27401                                                 });
27402
27403                                                 parents.push({name: args.name});
27404                                         }
27405                                 }
27406
27407                                 self.data(parents);
27408                         });
27409
27410                         return self._super();
27411                 }
27412         });
27413 });
27414
27415 // Included from: js/tinymce/classes/ui/FormItem.js
27416
27417 /**
27418  * FormItem.js
27419  *
27420  * Copyright, Moxiecode Systems AB
27421  * Released under LGPL License.
27422  *
27423  * License: http://www.tinymce.com/license
27424  * Contributing: http://www.tinymce.com/contributing
27425  */
27426
27427 /**
27428  * This class is a container created by the form element with
27429  * a label and control item.
27430  *
27431  * @class tinymce.ui.FormItem
27432  * @extends tinymce.ui.Container
27433  * @setting {String} label Label to display for the form item.
27434  */
27435 define("tinymce/ui/FormItem", [
27436         "tinymce/ui/Container"
27437 ], function(Container) {
27438         "use strict";
27439
27440         return Container.extend({
27441                 Defaults: {
27442                         layout: 'flex',
27443                         align: 'center',
27444                         defaults: {
27445                                 flex: 1
27446                         }
27447                 },
27448
27449                 /**
27450                  * Renders the control as a HTML string.
27451                  *
27452                  * @method renderHtml
27453                  * @return {String} HTML representing the control.
27454                  */
27455                 renderHtml: function() {
27456                         var self = this, layout = self._layout, prefix = self.classPrefix;
27457
27458                         self.addClass('formitem');
27459                         layout.preRender(self);
27460
27461                         return (
27462                                 '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
27463                                         (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
27464                                                 self.settings.title + '</div>') : '') +
27465                                         '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
27466                                                 (self.settings.html || '') + layout.renderHtml(self) +
27467                                         '</div>' +
27468                                 '</div>'
27469                         );
27470                 }
27471         });
27472 });
27473
27474 // Included from: js/tinymce/classes/ui/Form.js
27475
27476 /**
27477  * Form.js
27478  *
27479  * Copyright, Moxiecode Systems AB
27480  * Released under LGPL License.
27481  *
27482  * License: http://www.tinymce.com/license
27483  * Contributing: http://www.tinymce.com/contributing
27484  */
27485
27486 /**
27487  * This class creates a form container. A form container has the ability
27488  * to automatically wrap items in tinymce.ui.FormItem instances.
27489  *
27490  * Each FormItem instance is a container for the label and the item.
27491  *
27492  * @example
27493  * tinymce.ui.Factory.create({
27494  *     type: 'form',
27495  *     items: [
27496  *         {type: 'textbox', label: 'My text box'}
27497  *     ]
27498  * }).renderTo(document.body);
27499  *
27500  * @class tinymce.ui.Form
27501  * @extends tinymce.ui.Container
27502  */
27503 define("tinymce/ui/Form", [
27504         "tinymce/ui/Container",
27505         "tinymce/ui/FormItem"
27506 ], function(Container, FormItem) {
27507         "use strict";
27508
27509         return Container.extend({
27510                 Defaults: {
27511                         containerCls: 'form',
27512                         layout: 'flex',
27513                         direction: 'column',
27514                         align: 'stretch',
27515                         flex: 1,
27516                         padding: 20,
27517                         labelGap: 30,
27518                         spacing: 10
27519                 },
27520
27521                 /**
27522                  * This method gets invoked before the control is rendered.
27523                  *
27524                  * @method preRender
27525                  */
27526                 preRender: function() {
27527                         var self = this, items = self.items();
27528
27529                         // Wrap any labeled items in FormItems
27530                         items.each(function(ctrl) {
27531                                 var formItem, label = ctrl.settings.label;
27532
27533                                 if (label) {
27534                                         formItem = new FormItem({
27535                                                 layout: 'flex',
27536                                                 autoResize: "overflow",
27537                                                 defaults: {flex: 1},
27538                                                 items: [
27539                                                         {type: 'label', text: label, flex: 0, forId: ctrl._id}
27540                                                 ]
27541                                         });
27542
27543                                         formItem.type = 'formitem';
27544
27545                                         if (typeof(ctrl.settings.flex) == "undefined") {
27546                                                 ctrl.settings.flex = 1;
27547                                         }
27548
27549                                         self.replace(ctrl, formItem);
27550                                         formItem.add(ctrl);
27551                                 }
27552                         });
27553                 },
27554
27555                 /**
27556                  * Recalcs label widths.
27557                  *
27558                  * @private
27559                  */
27560                 recalcLabels: function() {
27561                         var self = this, maxLabelWidth = 0, labels = [], i, labelGap;
27562
27563                         if (self.settings.labelGapCalc === false) {
27564                                 return;
27565                         }
27566
27567                         self.items().filter('formitem').each(function(item) {
27568                                 var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
27569
27570                                 maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
27571                                 labels.push(labelCtrl);
27572                         });
27573
27574                         labelGap = self.settings.labelGap || 0;
27575
27576                         i = labels.length;
27577                         while (i--) {
27578                                 labels[i].settings.minWidth = maxLabelWidth + labelGap;
27579                         }
27580                 },
27581
27582                 /**
27583                  * Getter/setter for the visibility state.
27584                  *
27585                  * @method visible
27586                  * @param {Boolean} [state] True/false state to show/hide.
27587                  * @return {tinymce.ui.Form|Boolean} True/false state or current control.
27588                  */
27589                 visible: function(state) {
27590                         var val = this._super(state);
27591
27592                         if (state === true && this._rendered) {
27593                                 this.recalcLabels();
27594                         }
27595
27596                         return val;
27597                 },
27598
27599                 /**
27600                  * Fires a submit event with the serialized form.
27601                  *
27602                  * @method submit
27603                  * @return {Object} Event arguments object.
27604                  */
27605                 submit: function() {
27606                         // Blur current control so a onchange is fired before submit
27607                         var ctrl = this.getParentCtrl(document.activeElement);
27608                         if (ctrl) {
27609                                 ctrl.blur();
27610                         }
27611
27612                         return this.fire('submit', {data: this.toJSON()});
27613                 },
27614
27615                 /**
27616                  * Post render method. Called after the control has been rendered to the target.
27617                  *
27618                  * @method postRender
27619                  * @return {tinymce.ui.ComboBox} Current combobox instance.
27620                  */
27621                 postRender: function() {
27622                         var self = this;
27623
27624                         self._super();
27625                         self.recalcLabels();
27626                         self.fromJSON(self.settings.data);
27627                 }
27628         });
27629 });
27630
27631 // Included from: js/tinymce/classes/ui/FieldSet.js
27632
27633 /**
27634  * FieldSet.js
27635  *
27636  * Copyright, Moxiecode Systems AB
27637  * Released under LGPL License.
27638  *
27639  * License: http://www.tinymce.com/license
27640  * Contributing: http://www.tinymce.com/contributing
27641  */
27642
27643 /**
27644  * This class creates fieldset containers.
27645  *
27646  * @-x-less FieldSet.less
27647  * @class tinymce.ui.FieldSet
27648  * @extends tinymce.ui.Form
27649  */
27650 define("tinymce/ui/FieldSet", [
27651         "tinymce/ui/Form"
27652 ], function(Form) {
27653         "use strict";
27654
27655         return Form.extend({
27656                 Defaults: {
27657                         containerCls: 'fieldset',
27658                         layout: 'flex',
27659                         direction: 'column',
27660                         align: 'stretch',
27661                         flex: 1,
27662                         padding: "25 15 5 15",
27663                         labelGap: 30,
27664                         spacing: 10,
27665                         border: 1
27666                 },
27667
27668                 /**
27669                  * Renders the control as a HTML string.
27670                  *
27671                  * @method renderHtml
27672                  * @return {String} HTML representing the control.
27673                  */
27674                 renderHtml: function() {
27675                         var self = this, layout = self._layout, prefix = self.classPrefix;
27676
27677                         self.preRender();
27678                         layout.preRender(self);
27679
27680                         return (
27681                                 '<fieldset id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
27682                                         (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
27683                                                 self.settings.title + '</legend>') : '') +
27684                                         '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
27685                                                 (self.settings.html || '') + layout.renderHtml(self) +
27686                                         '</div>' +
27687                                 '</fieldset>'
27688                         );
27689                 }
27690         });
27691 });
27692
27693 // Included from: js/tinymce/classes/ui/FilePicker.js
27694
27695 /**
27696  * FilePicker.js
27697  *
27698  * Copyright, Moxiecode Systems AB
27699  * Released under LGPL License.
27700  *
27701  * License: http://www.tinymce.com/license
27702  * Contributing: http://www.tinymce.com/contributing
27703  */
27704
27705 /*global tinymce:true */
27706
27707 /**
27708  * This class creates a file picker control.
27709  *
27710  * @class tinymce.ui.FilePicker
27711  * @extends tinymce.ui.ComboBox
27712  */
27713 define("tinymce/ui/FilePicker", [
27714         "tinymce/ui/ComboBox"
27715 ], function(ComboBox) {
27716         "use strict";
27717
27718         return ComboBox.extend({
27719                 /**
27720                  * Constructs a new control instance with the specified settings.
27721                  *
27722                  * @constructor
27723                  * @param {Object} settings Name/value object with settings.
27724                  */
27725                 init: function(settings) {
27726                         var self = this, editor = tinymce.activeEditor, fileBrowserCallback;
27727
27728                         settings.spellcheck = false;
27729
27730                         fileBrowserCallback = editor.settings.file_browser_callback;
27731                         if (fileBrowserCallback) {
27732                                 settings.icon = 'browse';
27733
27734                                 settings.onaction = function() {
27735                                         fileBrowserCallback(
27736                                                 self.getEl('inp').id,
27737                                                 self.getEl('inp').value,
27738                                                 settings.filetype,
27739                                                 window
27740                                         );
27741                                 };
27742                         }
27743
27744                         self._super(settings);
27745                 }
27746         });
27747 });
27748
27749 // Included from: js/tinymce/classes/ui/FitLayout.js
27750
27751 /**
27752  * FitLayout.js
27753  *
27754  * Copyright, Moxiecode Systems AB
27755  * Released under LGPL License.
27756  *
27757  * License: http://www.tinymce.com/license
27758  * Contributing: http://www.tinymce.com/contributing
27759  */
27760
27761 /**
27762  * This layout manager will resize the control to be the size of it's parent container.
27763  * In other words width: 100% and height: 100%.
27764  *
27765  * @-x-less FitLayout.less
27766  * @class tinymce.ui.FitLayout
27767  * @extends tinymce.ui.AbsoluteLayout
27768  */
27769 define("tinymce/ui/FitLayout", [
27770         "tinymce/ui/AbsoluteLayout"
27771 ], function(AbsoluteLayout) {
27772         "use strict";
27773
27774         return AbsoluteLayout.extend({
27775                 /**
27776                  * Recalculates the positions of the controls in the specified container.
27777                  *
27778                  * @method recalc
27779                  * @param {tinymce.ui.Container} container Container instance to recalc.
27780                  */
27781                 recalc: function(container) {
27782                         var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox();
27783
27784                         container.items().filter(':visible').each(function(ctrl) {
27785                                 ctrl.layoutRect({
27786                                         x: paddingBox.left,
27787                                         y: paddingBox.top,
27788                                         w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
27789                                         h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
27790                                 });
27791
27792                                 if (ctrl.recalc) {
27793                                         ctrl.recalc();
27794                                 }
27795                         });
27796                 }
27797         });
27798 });
27799
27800 // Included from: js/tinymce/classes/ui/FlexLayout.js
27801
27802 /**
27803  * FlexLayout.js
27804  *
27805  * Copyright, Moxiecode Systems AB
27806  * Released under LGPL License.
27807  *
27808  * License: http://www.tinymce.com/license
27809  * Contributing: http://www.tinymce.com/contributing
27810  */
27811
27812 /**
27813  * This layout manager works similar to the CSS flex box.
27814  *
27815  * @setting {String} direction row|row-reverse|column|column-reverse
27816  * @setting {Number} flex A positive-number to flex by.
27817  * @setting {String} align start|end|center|stretch
27818  * @setting {String} pack start|end|justify
27819  *
27820  * @class tinymce.ui.FlexLayout
27821  * @extends tinymce.ui.AbsoluteLayout
27822  */
27823 define("tinymce/ui/FlexLayout", [
27824         "tinymce/ui/AbsoluteLayout"
27825 ], function(AbsoluteLayout) {
27826         "use strict";
27827
27828         return AbsoluteLayout.extend({
27829                 /**
27830                  * Recalculates the positions of the controls in the specified container.
27831                  *
27832                  * @method recalc
27833                  * @param {tinymce.ui.Container} container Container instance to recalc.
27834                  */
27835                 recalc: function(container) {
27836                         // A ton of variables, needs to be in the same scope for performance
27837                         var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
27838                         var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
27839                         var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, afterName, deltaSizeName, contentSizeName;
27840                         var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignMaxSizeName, alignBeforeName, alignAfterName;
27841                         var alignDeltaSizeName, alignContentSizeName;
27842                         var max = Math.max, min = Math.min;
27843
27844                         // Get container items, properties and settings
27845                         items = container.items().filter(':visible');
27846                         contLayoutRect = container.layoutRect();
27847                         contPaddingBox = container._paddingBox;
27848                         contSettings = container.settings;
27849                         direction = contSettings.direction;
27850                         align = contSettings.align;
27851                         pack = contSettings.pack;
27852                         spacing = contSettings.spacing || 0;
27853
27854                         if (direction == "row-reversed" || direction == "column-reverse") {
27855                                 items = items.set(items.toArray().reverse());
27856                                 direction = direction.split('-')[0];
27857                         }
27858
27859                         // Setup axis variable name for row/column direction since the calculations is the same
27860                         if (direction == "column") {
27861                                 posName = "y";
27862                                 sizeName = "h";
27863                                 minSizeName = "minH";
27864                                 maxSizeName = "maxH";
27865                                 innerSizeName = "innerH";
27866                                 beforeName = 'top';
27867                                 afterName = 'bottom';
27868                                 deltaSizeName = "deltaH";
27869                                 contentSizeName = "contentH";
27870
27871                                 alignBeforeName = "left";
27872                                 alignSizeName = "w";
27873                                 alignAxisName = "x";
27874                                 alignInnerSizeName = "innerW";
27875                                 alignMinSizeName = "minW";
27876                                 alignMaxSizeName = "maxW";
27877                                 alignAfterName = "right";
27878                                 alignDeltaSizeName = "deltaW";
27879                                 alignContentSizeName = "contentW";
27880                         } else {
27881                                 posName = "x";
27882                                 sizeName = "w";
27883                                 minSizeName = "minW";
27884                                 maxSizeName = "maxW";
27885                                 innerSizeName = "innerW";
27886                                 beforeName = 'left';
27887                                 afterName = 'right';
27888                                 deltaSizeName = "deltaW";
27889                                 contentSizeName = "contentW";
27890
27891                                 alignBeforeName = "top";
27892                                 alignSizeName = "h";
27893                                 alignAxisName = "y";
27894                                 alignInnerSizeName = "innerH";
27895                                 alignMinSizeName = "minH";
27896                                 alignMaxSizeName = "maxH";
27897                                 alignAfterName = "bottom";
27898                                 alignDeltaSizeName = "deltaH";
27899                                 alignContentSizeName = "contentH";
27900                         }
27901
27902                         // Figure out total flex, availableSpace and collect any max size elements
27903                         availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
27904                         maxAlignEndPos = totalFlex = 0;
27905                         for (i = 0, l = items.length; i < l; i++) {
27906                                 ctrl = items[i];
27907                                 ctrlLayoutRect = ctrl.layoutRect();
27908                                 ctrlSettings = ctrl.settings;
27909                                 flex = ctrlSettings.flex;
27910                                 availableSpace -= (i < l - 1 ? spacing : 0);
27911
27912                                 if (flex > 0) {
27913                                         totalFlex += flex;
27914
27915                                         // Flexed item has a max size then we need to check if we will hit that size
27916                                         if (ctrlLayoutRect[maxSizeName]) {
27917                                                 maxSizeItems.push(ctrl);
27918                                         }
27919
27920                                         ctrlLayoutRect.flex = flex;
27921                                 }
27922
27923                                 availableSpace -= ctrlLayoutRect[minSizeName];
27924
27925                                 // Calculate the align end position to be used to check for overflow/underflow
27926                                 size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
27927                                 if (size > maxAlignEndPos) {
27928                                         maxAlignEndPos = size;
27929                                 }
27930                         }
27931
27932                         // Calculate minW/minH
27933                         rect = {};
27934                         if (availableSpace < 0) {
27935                                 rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
27936                         } else {
27937                                 rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
27938                         }
27939
27940                         rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
27941
27942                         rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
27943                         rect[alignContentSizeName] = maxAlignEndPos;
27944                         rect.minW = min(rect.minW, contLayoutRect.maxW);
27945                         rect.minH = min(rect.minH, contLayoutRect.maxH);
27946                         rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
27947                         rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
27948
27949                         // Resize container container if minSize was changed
27950                         if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
27951                                 rect.w = rect.minW;
27952                                 rect.h = rect.minH;
27953
27954                                 container.layoutRect(rect);
27955                                 this.recalc(container);
27956
27957                                 // Forced recalc for example if items are hidden/shown
27958                                 if (container._lastRect === null) {
27959                                         var parentCtrl = container.parent();
27960                                         if (parentCtrl) {
27961                                                 parentCtrl._lastRect = null;
27962                                                 parentCtrl.recalc();
27963                                         }
27964                                 }
27965
27966                                 return;
27967                         }
27968
27969                         // Handle max size elements, check if they will become to wide with current options
27970                         ratio = availableSpace / totalFlex;
27971                         for (i = 0, l = maxSizeItems.length; i < l; i++) {
27972                                 ctrl = maxSizeItems[i];
27973                                 ctrlLayoutRect = ctrl.layoutRect();
27974                                 maxSize = ctrlLayoutRect[maxSizeName];
27975                                 size = ctrlLayoutRect[minSizeName] + Math.ceil(ctrlLayoutRect.flex * ratio);
27976
27977                                 if (size > maxSize) {
27978                                         availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
27979                                         totalFlex -= ctrlLayoutRect.flex;
27980                                         ctrlLayoutRect.flex = 0;
27981                                         ctrlLayoutRect.maxFlexSize = maxSize;
27982                                 } else {
27983                                         ctrlLayoutRect.maxFlexSize = 0;
27984                                 }
27985                         }
27986
27987                         // Setup new ratio, target layout rect, start position
27988                         ratio = availableSpace / totalFlex;
27989                         pos = contPaddingBox[beforeName];
27990                         rect = {};
27991
27992                         // Handle pack setting moves the start position to end, center
27993                         if (totalFlex === 0) {
27994                                 if (pack == "end") {
27995                                         pos = availableSpace + contPaddingBox[beforeName];
27996                                 } else if (pack == "center") {
27997                                         pos = Math.round(
27998                                                 (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
27999                                         ) + contPaddingBox[beforeName];
28000
28001                                         if (pos < 0) {
28002                                                 pos = contPaddingBox[beforeName];
28003                                         }
28004                                 } else if (pack == "justify") {
28005                                         pos = contPaddingBox[beforeName];
28006                                         spacing = Math.floor(availableSpace / (items.length - 1));
28007                                 }
28008                         }
28009
28010                         // Default aligning (start) the other ones needs to be calculated while doing the layout
28011                         rect[alignAxisName] = contPaddingBox[alignBeforeName];
28012
28013                         // Start laying out controls
28014                         for (i = 0, l = items.length; i < l; i++) {
28015                                 ctrl = items[i];
28016                                 ctrlLayoutRect = ctrl.layoutRect();
28017                                 size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
28018
28019                                 // Align the control on the other axis
28020                                 if (align === "center") {
28021                                         rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
28022                                 } else if (align === "stretch") {
28023                                         rect[alignSizeName] = max(
28024                                                 ctrlLayoutRect[alignMinSizeName] || 0,
28025                                                 contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
28026                                         );
28027                                         rect[alignAxisName] = contPaddingBox[alignBeforeName];
28028                                 } else if (align === "end") {
28029                                         rect[alignAxisName] = contLayoutRect[alignInnerSizeName]  - ctrlLayoutRect[alignSizeName]  - contPaddingBox.top;
28030                                 }
28031
28032                                 // Calculate new size based on flex
28033                                 if (ctrlLayoutRect.flex > 0) {
28034                                         size += Math.ceil(ctrlLayoutRect.flex * ratio);
28035                                 }
28036
28037                                 rect[sizeName] = size;
28038                                 rect[posName] = pos;
28039                                 ctrl.layoutRect(rect);
28040
28041                                 // Recalculate containers
28042                                 if (ctrl.recalc) {
28043                                         ctrl.recalc();
28044                                 }
28045
28046                                 // Move x/y position
28047                                 pos += size + spacing;
28048                         }
28049                 }
28050         });
28051 });
28052
28053 // Included from: js/tinymce/classes/ui/FlowLayout.js
28054
28055 /**
28056  * FlowLayout.js
28057  *
28058  * Copyright, Moxiecode Systems AB
28059  * Released under LGPL License.
28060  *
28061  * License: http://www.tinymce.com/license
28062  * Contributing: http://www.tinymce.com/contributing
28063  */
28064
28065 /**
28066  * This layout manager will place the controls by using the browsers native layout.
28067  *
28068  * @-x-less FlowLayout.less
28069  * @class tinymce.ui.FlowLayout
28070  * @extends tinymce.ui.Layout
28071  */
28072 define("tinymce/ui/FlowLayout", [
28073         "tinymce/ui/Layout"
28074 ], function(Layout) {
28075         return Layout.extend({
28076                 Defaults: {
28077                         containerClass: 'flow-layout',
28078                         controlClass: 'flow-layout-item',
28079                         endClass : 'break'
28080                 },
28081
28082                 /**
28083                  * Recalculates the positions of the controls in the specified container.
28084                  *
28085                  * @method recalc
28086                  * @param {tinymce.ui.Container} container Container instance to recalc.
28087                  */
28088                 recalc: function(container) {
28089                         container.items().filter(':visible').each(function(ctrl) {
28090                                 if (ctrl.recalc) {
28091                                         ctrl.recalc();
28092                                 }
28093                         });
28094                 }
28095         });
28096 });
28097
28098 // Included from: js/tinymce/classes/ui/FormatControls.js
28099
28100 /**
28101  * FormatControls.js
28102  *
28103  * Copyright, Moxiecode Systems AB
28104  * Released under LGPL License.
28105  *
28106  * License: http://www.tinymce.com/license
28107  * Contributing: http://www.tinymce.com/contributing
28108  */
28109
28110 /**
28111  * Internal class containing all TinyMCE specific control types such as
28112  * format listboxes, fontlist boxes, toolbar buttons etc.
28113  *
28114  * @class tinymce.ui.FormatControls
28115  */
28116 define("tinymce/ui/FormatControls", [
28117         "tinymce/ui/Control",
28118         "tinymce/ui/Widget",
28119         "tinymce/ui/FloatPanel",
28120         "tinymce/util/Tools",
28121         "tinymce/EditorManager",
28122         "tinymce/Env"
28123 ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
28124         var each = Tools.each;
28125
28126         EditorManager.on('AddEditor', function(e) {
28127                 registerControls(e.editor);
28128         });
28129
28130         Control.translate = function(text) {
28131                 return EditorManager.translate(text);
28132         };
28133
28134         Widget.tooltips = !Env.iOS;
28135
28136         function registerControls(editor) {
28137                 var formatMenu;
28138
28139                 // Generates a preview for a format
28140                 function getPreviewCss(format) {
28141                         var name, previewElm, dom = editor.dom;
28142                         var previewCss = '', parentFontSize, previewStyles;
28143
28144                         previewStyles = editor.settings.preview_styles;
28145
28146                         // No preview forced
28147                         if (previewStyles === false) {
28148                                 return '';
28149                         }
28150
28151                         // Default preview
28152                         if (!previewStyles) {
28153                                 previewStyles = 'font-family font-size font-weight text-decoration ' +
28154                                         'text-transform color background-color border border-radius';
28155                         }
28156
28157                         // Removes any variables since these can't be previewed
28158                         function removeVars(val) {
28159                                 return val.replace(/%(\w+)/g, '');
28160                         }
28161
28162                         // Create block/inline element to use for preview
28163                         format = editor.formatter.get(format);
28164                         if (!format) {
28165                                 return;
28166                         }
28167
28168                         format = format[0];
28169                         name = format.block || format.inline || 'span';
28170                         previewElm = dom.create(name);
28171
28172                         // Add format styles to preview element
28173                         each(format.styles, function(value, name) {
28174                                 value = removeVars(value);
28175
28176                                 if (value) {
28177                                         dom.setStyle(previewElm, name, value);
28178                                 }
28179                         });
28180
28181                         // Add attributes to preview element
28182                         each(format.attributes, function(value, name) {
28183                                 value = removeVars(value);
28184
28185                                 if (value) {
28186                                         dom.setAttrib(previewElm, name, value);
28187                                 }
28188                         });
28189
28190                         // Add classes to preview element
28191                         each(format.classes, function(value) {
28192                                 value = removeVars(value);
28193
28194                                 if (!dom.hasClass(previewElm, value)) {
28195                                         dom.addClass(previewElm, value);
28196                                 }
28197                         });
28198
28199                         editor.fire('PreviewFormats');
28200
28201                         // Add the previewElm outside the visual area
28202                         dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
28203                         editor.getBody().appendChild(previewElm);
28204
28205                         // Get parent container font size so we can compute px values out of em/% for older IE:s
28206                         parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
28207                         parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
28208
28209                         each(previewStyles.split(' '), function(name) {
28210                                 var value = dom.getStyle(previewElm, name, true);
28211
28212                                 // If background is transparent then check if the body has a background color we can use
28213                                 if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
28214                                         value = dom.getStyle(editor.getBody(), name, true);
28215
28216                                         // Ignore white since it's the default color, not the nicest fix
28217                                         // TODO: Fix this by detecting runtime style
28218                                         if (dom.toHex(value).toLowerCase() == '#ffffff') {
28219                                                 return;
28220                                         }
28221                                 }
28222
28223                                 if (name == 'color') {
28224                                         // Ignore black since it's the default color, not the nicest fix
28225                                         // TODO: Fix this by detecting runtime style
28226                                         if (dom.toHex(value).toLowerCase() == '#000000') {
28227                                                 return;
28228                                         }
28229                                 }
28230
28231                                 // Old IE won't calculate the font size so we need to do that manually
28232                                 if (name == 'font-size') {
28233                                         if (/em|%$/.test(value)) {
28234                                                 if (parentFontSize === 0) {
28235                                                         return;
28236                                                 }
28237
28238                                                 // Convert font size from em/% to px
28239                                                 value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
28240                                                 value = (value * parentFontSize) + 'px';
28241                                         }
28242                                 }
28243
28244                                 if (name == "border" && value) {
28245                                         previewCss += 'padding:0 2px;';
28246                                 }
28247
28248                                 previewCss += name + ':' + value + ';';
28249                         });
28250
28251                         editor.fire('AfterPreviewFormats');
28252
28253                         //previewCss += 'line-height:normal';
28254
28255                         dom.remove(previewElm);
28256
28257                         return previewCss;
28258                 }
28259
28260                 function createListBoxChangeHandler(items, formatName) {
28261                         return function() {
28262                                 var self = this;
28263
28264                                 editor.on('nodeChange', function(e) {
28265                                         var formatter = editor.formatter;
28266                                         var value = null;
28267
28268                                         each(e.parents, function(node) {
28269                                                 each(items, function(item) {
28270                                                         if (formatName) {
28271                                                                 if (formatter.matchNode(node, formatName, {value: item.value})) {
28272                                                                         value = item.value;
28273                                                                 }
28274                                                         } else {
28275                                                                 if (formatter.matchNode(node, item.value)) {
28276                                                                         value = item.value;
28277                                                                 }
28278                                                         }
28279
28280                                                         if (value) {
28281                                                                 return false;
28282                                                         }
28283                                                 });
28284
28285                                                 if (value) {
28286                                                         return false;
28287                                                 }
28288                                         });
28289
28290                                         self.value(value);
28291                                 });
28292                         };
28293                 }
28294
28295                 function createFormats(formats) {
28296                         formats = formats.split(';');
28297
28298                         var i = formats.length;
28299                         while (i--) {
28300                                 formats[i] = formats[i].split('=');
28301                         }
28302
28303                         return formats;
28304                 }
28305
28306                 function createFormatMenu() {
28307                         var count = 0, newFormats = [];
28308
28309                         var defaultStyleFormats = [
28310                                 {title: 'Headers', items: [
28311                                         {title: 'Header 1', format: 'h1'},
28312                                         {title: 'Header 2', format: 'h2'},
28313                                         {title: 'Header 3', format: 'h3'},
28314                                         {title: 'Header 4', format: 'h4'},
28315                                         {title: 'Header 5', format: 'h5'},
28316                                         {title: 'Header 6', format: 'h6'}
28317                                 ]},
28318
28319                                 {title: 'Inline', items: [
28320                                         {title: 'Bold', icon: 'bold', format: 'bold'},
28321                                         {title: 'Italic', icon: 'italic', format: 'italic'},
28322                                         {title: 'Underline', icon: 'underline', format: 'underline'},
28323                                         {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
28324                                         {title: 'Superscript', icon: 'superscript', format: 'superscript'},
28325                                         {title: 'Subscript', icon: 'subscript', format: 'subscript'},
28326                                         {title: 'Code', icon: 'code', format: 'code'}
28327                                 ]},
28328
28329                                 {title: 'Blocks', items: [
28330                                         {title: 'Paragraph', format: 'p'},
28331                                         {title: 'Blockquote', format: 'blockquote'},
28332                                         {title: 'Div', format: 'div'},
28333                                         {title: 'Pre', format: 'pre'}
28334                                 ]},
28335
28336                                 {title: 'Alignment', items: [
28337                                         {title: 'Left', icon: 'alignleft', format: 'alignleft'},
28338                                         {title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
28339                                         {title: 'Right', icon: 'alignright', format: 'alignright'},
28340                                         {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
28341                                 ]}
28342                         ];
28343
28344                         function createMenu(formats) {
28345                                 var menu = [];
28346
28347                                 if (!formats) {
28348                                         return;
28349                                 }
28350
28351                                 each(formats, function(format) {
28352                                         var menuItem = {
28353                                                 text: format.title,
28354                                                 icon: format.icon
28355                                         };
28356
28357                                         if (format.items) {
28358                                                 menuItem.menu = createMenu(format.items);
28359                                         } else {
28360                                                 var formatName = format.format || "custom" + count++;
28361
28362                                                 if (!format.format) {
28363                                                         format.name = formatName;
28364                                                         newFormats.push(format);
28365                                                 }
28366
28367                                                 menuItem.format = formatName;
28368                                         }
28369
28370                                         menu.push(menuItem);
28371                                 });
28372
28373                                 return menu;
28374                         }
28375
28376                         editor.on('init', function() {
28377                                 each(newFormats, function(format) {
28378                                         editor.formatter.register(format.name, format);
28379                                 });
28380                         });
28381
28382                         var menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
28383
28384                         menu = {
28385                                 type: 'menu',
28386                                 items: menu,
28387                                 onPostRender: function(e) {
28388                                         editor.fire('renderFormatsMenu', {control: e.control});
28389                                 },
28390                                 itemDefaults: {
28391                                         preview: true,
28392
28393                                         textStyle: function() {
28394                                                 if (this.settings.format) {
28395                                                         return getPreviewCss(this.settings.format);
28396                                                 }
28397                                         },
28398
28399                                         onPostRender: function() {
28400                                                 var self = this, formatName = this.settings.format;
28401
28402                                                 if (formatName) {
28403                                                         self.parent().on('show', function() {
28404                                                                 self.disabled(!editor.formatter.canApply(formatName));
28405                                                                 self.active(editor.formatter.match(formatName));
28406                                                         });
28407                                                 }
28408                                         },
28409
28410                                         onclick: function() {
28411                                                 if (this.settings.format) {
28412                                                         toggleFormat(this.settings.format);
28413                                                 }
28414                                         }
28415                                 }
28416                         };
28417
28418                         return menu;
28419                 }
28420
28421                 formatMenu = createFormatMenu();
28422
28423                 // Simple format controls <control/format>:<UI text>
28424                 each({
28425                         bold: 'Bold',
28426                         italic: 'Italic',
28427                         underline: 'Underline',
28428                         strikethrough: 'Strikethrough',
28429                         subscript: 'Subscript',
28430                         superscript: 'Superscript'
28431                 }, function(text, name) {
28432                         editor.addButton(name, {
28433                                 tooltip: text,
28434                                 onPostRender: function() {
28435                                         var self = this;
28436
28437                                         // TODO: Fix this
28438                                         if (editor.formatter) {
28439                                                 editor.formatter.formatChanged(name, function(state) {
28440                                                         self.active(state);
28441                                                 });
28442                                         } else {
28443                                                 editor.on('init', function() {
28444                                                         editor.formatter.formatChanged(name, function(state) {
28445                                                                 self.active(state);
28446                                                         });
28447                                                 });
28448                                         }
28449                                 },
28450                                 onclick: function() {
28451                                         toggleFormat(name);
28452                                 }
28453                         });
28454                 });
28455
28456                 // Simple command controls <control>:[<UI text>,<Command>]
28457                 each({
28458                         outdent: ['Decrease indent', 'Outdent'],
28459                         indent: ['Increase indent', 'Indent'],
28460                         cut: ['Cut', 'Cut'],
28461                         copy: ['Copy', 'Copy'],
28462                         paste: ['Paste', 'Paste'],
28463                         help: ['Help', 'mceHelp'],
28464                         selectall: ['Select all', 'SelectAll'],
28465                         hr: ['Insert horizontal rule', 'InsertHorizontalRule'],
28466                         removeformat: ['Clear formatting', 'RemoveFormat'],
28467                         visualaid: ['Visual aids', 'mceToggleVisualAid'],
28468                         newdocument: ['New document', 'mceNewDocument']
28469                 }, function(item, name) {
28470                         editor.addButton(name, {
28471                                 tooltip: item[0],
28472                                 cmd: item[1]
28473                         });
28474                 });
28475
28476                 // Simple command controls with format state
28477                 each({
28478                         blockquote: ['Toggle blockquote', 'mceBlockQuote'],
28479                         numlist: ['Numbered list', 'InsertOrderedList'],
28480                         bullist: ['Bullet list', 'InsertUnorderedList'],
28481                         subscript: ['Subscript', 'Subscript'],
28482                         superscript: ['Superscript', 'Superscript'],
28483                         alignleft: ['Align left', 'JustifyLeft'],
28484                         aligncenter: ['Align center', 'JustifyCenter'],
28485                         alignright: ['Align right', 'JustifyRight'],
28486                         alignjustify: ['Justify', 'JustifyFull']
28487                 }, function(item, name) {
28488                         editor.addButton(name, {
28489                                 tooltip: item[0],
28490                                 cmd: item[1],
28491                                 onPostRender: function() {
28492                                         var self = this;
28493
28494                                         // TODO: Fix this
28495                                         if (editor.formatter) {
28496                                                 editor.formatter.formatChanged(name, function(state) {
28497                                                         self.active(state);
28498                                                 });
28499                                         } else {
28500                                                 editor.on('init', function() {
28501                                                         editor.formatter.formatChanged(name, function(state) {
28502                                                                 self.active(state);
28503                                                         });
28504                                                 });
28505                                         }
28506                                 }
28507                         });
28508                 });
28509
28510                 function hasUndo() {
28511                         return editor.undoManager ? editor.undoManager.hasUndo() : false;
28512                 }
28513
28514                 function hasRedo() {
28515                         return editor.undoManager ? editor.undoManager.hasRedo() : false;
28516                 }
28517
28518                 function toggleUndoState() {
28519                         var self = this;
28520
28521                         self.disabled(!hasUndo());
28522                         editor.on('Undo Redo AddUndo TypingUndo', function() {
28523                                 self.disabled(!hasUndo());
28524                         });
28525                 }
28526
28527                 function toggleRedoState() {
28528                         var self = this;
28529
28530                         self.disabled(!hasRedo());
28531                         editor.on('Undo Redo AddUndo TypingUndo', function() {
28532                                 self.disabled(!hasRedo());
28533                         });
28534                 }
28535
28536                 function toggleVisualAidState() {
28537                         var self = this;
28538
28539                         editor.on('VisualAid', function(e) {
28540                                 self.active(e.hasVisual);
28541                         });
28542
28543                         self.active(editor.hasVisual);
28544                 }
28545
28546                 editor.addButton('undo', {
28547                         tooltip: 'Undo',
28548                         onPostRender: toggleUndoState,
28549                         cmd: 'undo'
28550                 });
28551
28552                 editor.addButton('redo', {
28553                         tooltip: 'Redo',
28554                         onPostRender: toggleRedoState,
28555                         cmd: 'redo'
28556                 });
28557
28558                 editor.addMenuItem('newdocument', {
28559                         text: 'New document',
28560                         shortcut: 'Ctrl+N',
28561                         icon: 'newdocument',
28562                         cmd: 'mceNewDocument'
28563                 });
28564
28565                 editor.addMenuItem('undo', {
28566                         text: 'Undo',
28567                         icon: 'undo',
28568                         shortcut: 'Ctrl+Z',
28569                         onPostRender: toggleUndoState,
28570                         cmd: 'undo'
28571                 });
28572
28573                 editor.addMenuItem('redo', {
28574                         text: 'Redo',
28575                         icon: 'redo',
28576                         shortcut: 'Ctrl+Y',
28577                         onPostRender: toggleRedoState,
28578                         cmd: 'redo'
28579                 });
28580
28581                 editor.addMenuItem('visualaid', {
28582                         text: 'Visual aids',
28583                         selectable: true,
28584                         onPostRender: toggleVisualAidState,
28585                         cmd: 'mceToggleVisualAid'
28586                 });
28587
28588                 each({
28589                         cut: ['Cut', 'Cut', 'Ctrl+X'],
28590                         copy: ['Copy', 'Copy', 'Ctrl+C'],
28591                         paste: ['Paste', 'Paste', 'Ctrl+V'],
28592                         selectall: ['Select all', 'SelectAll', 'Ctrl+A'],
28593                         bold: ['Bold', 'Bold', 'Ctrl+B'],
28594                         italic: ['Italic', 'Italic', 'Ctrl+I'],
28595                         underline: ['Underline', 'Underline'],
28596                         strikethrough: ['Strikethrough', 'Strikethrough'],
28597                         subscript: ['Subscript', 'Subscript'],
28598                         superscript: ['Superscript', 'Superscript'],
28599                         removeformat: ['Clear formatting', 'RemoveFormat']
28600                 }, function(item, name) {
28601                         editor.addMenuItem(name, {
28602                                 text: item[0],
28603                                 icon: name,
28604                                 shortcut: item[2],
28605                                 cmd: item[1]
28606                         });
28607                 });
28608
28609                 editor.on('mousedown', function() {
28610                         FloatPanel.hideAll();
28611                 });
28612
28613                 function toggleFormat(fmt) {
28614                         if (fmt.control) {
28615                                 fmt = fmt.control.value();
28616                         }
28617
28618                         if (fmt) {
28619                                 editor.execCommand('mceToggleFormat', false, fmt);
28620                         }
28621                 }
28622
28623                 editor.addButton('styleselect', {
28624                         type: 'menubutton',
28625                         text: 'Formats',
28626                         menu: formatMenu
28627                 });
28628
28629                 editor.addButton('formatselect', function() {
28630                         var items = [], blocks = createFormats(editor.settings.block_formats ||
28631                                 'Paragraph=p;' +
28632                                 'Address=address;' +
28633                                 'Pre=pre;' +
28634                                 'Header 1=h1;' +
28635                                 'Header 2=h2;' +
28636                                 'Header 3=h3;' +
28637                                 'Header 4=h4;' +
28638                                 'Header 5=h5;' +
28639                                 'Header 6=h6'
28640                         );
28641
28642                         each(blocks, function(block) {
28643                                 items.push({
28644                                         text: block[0],
28645                                         value: block[1],
28646                                         textStyle: function() {
28647                                                 return getPreviewCss(block[1]);
28648                                         }
28649                                 });
28650                         });
28651
28652                         return {
28653                                 type: 'listbox',
28654                                 text: {raw: blocks[0][0]},
28655                                 values: items,
28656                                 fixedWidth: true,
28657                                 onselect: toggleFormat,
28658                                 onPostRender: createListBoxChangeHandler(items)
28659                         };
28660                 });
28661
28662                 editor.addButton('fontselect', function() {
28663                         var defaultFontsFormats =
28664                                 'Andale Mono=andale mono,times;' +
28665                                 'Arial=arial,helvetica,sans-serif;' +
28666                                 'Arial Black=arial black,avant garde;' +
28667                                 'Book Antiqua=book antiqua,palatino;' +
28668                                 'Comic Sans MS=comic sans ms,sans-serif;' +
28669                                 'Courier New=courier new,courier;' +
28670                                 'Georgia=georgia,palatino;' +
28671                                 'Helvetica=helvetica;' +
28672                                 'Impact=impact,chicago;' +
28673                                 'Symbol=symbol;' +
28674                                 'Tahoma=tahoma,arial,helvetica,sans-serif;' +
28675                                 'Terminal=terminal,monaco;' +
28676                                 'Times New Roman=times new roman,times;' +
28677                                 'Trebuchet MS=trebuchet ms,geneva;' +
28678                                 'Verdana=verdana,geneva;' +
28679                                 'Webdings=webdings;' +
28680                                 'Wingdings=wingdings,zapf dingbats';
28681
28682                         var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
28683
28684                         each(fonts, function(font) {
28685                                 items.push({
28686                                         text: {raw: font[0]},
28687                                         value: font[1],
28688                                         textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
28689                                 });
28690                         });
28691
28692                         return {
28693                                 type: 'listbox',
28694                                 text: 'Font Family',
28695                                 tooltip: 'Font Family',
28696                                 values: items,
28697                                 fixedWidth: true,
28698                                 onPostRender: createListBoxChangeHandler(items, 'fontname'),
28699                                 onselect: function(e) {
28700                                         if (e.control.settings.value) {
28701                                                 editor.execCommand('FontName', false, e.control.settings.value);
28702                                         }
28703                                 }
28704                         };
28705                 });
28706
28707                 editor.addButton('fontsizeselect', function() {
28708                         var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
28709                         var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
28710
28711                         each(fontsize_formats.split(' '), function(item) {
28712                                 items.push({text: item, value: item});
28713                         });
28714
28715                         return {
28716                                 type: 'listbox',
28717                                 text: 'Font Sizes',
28718                                 tooltip: 'Font Sizes',
28719                                 values: items,
28720                                 fixedWidth: true,
28721                                 onPostRender: createListBoxChangeHandler(items, 'fontsize'),
28722                                 onclick: function(e) {
28723                                         if (e.control.settings.value) {
28724                                                 editor.execCommand('FontSize', false, e.control.settings.value);
28725                                         }
28726                                 }
28727                         };
28728                 });
28729
28730                 editor.addMenuItem('formats', {
28731                         text: 'Formats',
28732                         menu: formatMenu
28733                 });
28734         }
28735 });
28736
28737 // Included from: js/tinymce/classes/ui/GridLayout.js
28738
28739 /**
28740  * GridLayout.js
28741  *
28742  * Copyright, Moxiecode Systems AB
28743  * Released under LGPL License.
28744  *
28745  * License: http://www.tinymce.com/license
28746  * Contributing: http://www.tinymce.com/contributing
28747  */
28748
28749 /**
28750  * This layout manager places controls in a grid.
28751  *
28752  * @setting {Number} spacing Spacing between controls.
28753  * @setting {Number} spacingH Horizontal spacing between controls.
28754  * @setting {Number} spacingV Vertical spacing between controls.
28755  * @setting {Number} columns Number of columns to use.
28756  * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
28757  * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
28758  * @setting {String} pack start|end
28759  *
28760  * @class tinymce.ui.GridLayout
28761  * @extends tinymce.ui.AbsoluteLayout
28762  */
28763 define("tinymce/ui/GridLayout", [
28764         "tinymce/ui/AbsoluteLayout"
28765 ], function(AbsoluteLayout) {
28766         "use strict";
28767
28768         return AbsoluteLayout.extend({
28769                 /**
28770                  * Recalculates the positions of the controls in the specified container.
28771                  *
28772                  * @method recalc
28773                  * @param {tinymce.ui.Container} container Container instance to recalc.
28774                  */
28775                 recalc: function(container) {
28776                         var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
28777                                 ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
28778                                 colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, alignX, alignY, availableWidth, availableHeight;
28779
28780                         // Get layout settings
28781                         settings = container.settings;
28782                         items = container.items().filter(':visible');
28783                         contLayoutRect = container.layoutRect();
28784                         cols = settings.columns || Math.ceil(Math.sqrt(items.length));
28785                         rows = Math.ceil(items.length / cols);
28786                         spacingH = settings.spacingH || settings.spacing || 0;
28787                         spacingV = settings.spacingV || settings.spacing || 0;
28788                         alignH = settings.alignH || settings.align;
28789                         alignV = settings.alignV || settings.align;
28790                         contPaddingBox = container._paddingBox;
28791
28792                         if (alignH && typeof(alignH) == "string") {
28793                                 alignH = [alignH];
28794                         }
28795
28796                         if (alignV && typeof(alignV) == "string") {
28797                                 alignV = [alignV];
28798                         }
28799
28800                         // Zero padd columnWidths
28801                         for (x = 0; x < cols; x++) {
28802                                 colWidths.push(0);
28803                         }
28804
28805                         // Zero padd rowHeights
28806                         for (y = 0; y < rows; y++) {
28807                                 rowHeights.push(0);
28808                         }
28809
28810                         // Calculate columnWidths and rowHeights
28811                         for (y = 0; y < rows; y++) {
28812                                 for (x = 0; x < cols; x++) {
28813                                         ctrl = items[y * cols + x];
28814
28815                                         // Out of bounds
28816                                         if (!ctrl) {
28817                                                 break;
28818                                         }
28819
28820                                         ctrlLayoutRect = ctrl.layoutRect();
28821                                         ctrlMinWidth = ctrlLayoutRect.minW;
28822                                         ctrlMinHeight = ctrlLayoutRect.minH;
28823
28824                                         colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
28825                                         rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
28826                                 }
28827                         }
28828
28829                         // Calculate maxX
28830                         availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
28831                         for (maxX = 0, x = 0; x < cols; x++) {
28832                                 maxX += colWidths[x] + (x > 0 ? spacingH : 0);
28833                                 availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
28834                         }
28835
28836                         // Calculate maxY
28837                         availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
28838                         for (maxY = 0, y = 0; y < rows; y++) {
28839                                 maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
28840                                 availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
28841                         }
28842
28843                         maxX += contPaddingBox.left + contPaddingBox.right;
28844                         maxY += contPaddingBox.top + contPaddingBox.bottom;
28845
28846                         // Calculate minW/minH
28847                         rect = {};
28848                         rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
28849                         rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
28850
28851                         rect.contentW = rect.minW - contLayoutRect.deltaW;
28852                         rect.contentH = rect.minH - contLayoutRect.deltaH;
28853                         rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
28854                         rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
28855                         rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
28856                         rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
28857
28858                         // Resize container container if minSize was changed
28859                         if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
28860                                 rect.w = rect.minW;
28861                                 rect.h = rect.minH;
28862
28863                                 container.layoutRect(rect);
28864                                 this.recalc(container);
28865
28866                                 // Forced recalc for example if items are hidden/shown
28867                                 if (container._lastRect === null) {
28868                                         var parentCtrl = container.parent();
28869                                         if (parentCtrl) {
28870                                                 parentCtrl._lastRect = null;
28871                                                 parentCtrl.recalc();
28872                                         }
28873                                 }
28874
28875                                 return;
28876                         }
28877
28878                         // Update contentW/contentH so absEnd moves correctly
28879                         if (contLayoutRect.autoResize) {
28880                                 rect = container.layoutRect(rect);
28881                                 rect.contentW = rect.minW - contLayoutRect.deltaW;
28882                                 rect.contentH = rect.minH - contLayoutRect.deltaH;
28883                         }
28884
28885                         var flexV;
28886
28887                         if (settings.packV == 'start') {
28888                                 flexV = 0;
28889                         } else {
28890                                 flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
28891                         }
28892
28893                         // Calculate totalFlex
28894                         var totalFlex = 0;
28895                         var flexWidths = settings.flexWidths;
28896                         if (flexWidths) {
28897                                 for (x = 0; x < flexWidths.length; x++) {
28898                                         totalFlex += flexWidths[x];
28899                                 }
28900                         } else {
28901                                 totalFlex = cols;
28902                         }
28903
28904                         // Calculate new column widths based on flex values
28905                         var ratio = availableWidth / totalFlex;
28906                         for (x = 0; x < cols; x++) {
28907                                 colWidths[x] += flexWidths ? Math.ceil(flexWidths[x] * ratio) : ratio;
28908                         }
28909
28910                         // Move/resize controls
28911                         posY = contPaddingBox.top;
28912                         for (y = 0; y < rows; y++) {
28913                                 posX = contPaddingBox.left;
28914                                 height = rowHeights[y] + flexV;
28915
28916                                 for (x = 0; x < cols; x++) {
28917                                         ctrl = items[y * cols + x];
28918
28919                                         // No more controls to render then break
28920                                         if (!ctrl) {
28921                                                 break;
28922                                         }
28923
28924                                         // Get control settings and calculate x, y
28925                                         ctrlSettings = ctrl.settings;
28926                                         ctrlLayoutRect = ctrl.layoutRect();
28927                                         width = colWidths[x];
28928                                         alignX = alignY = 0;
28929                                         ctrlLayoutRect.x = posX;
28930                                         ctrlLayoutRect.y = posY;
28931
28932                                         // Align control horizontal
28933                                         align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
28934                                         if (align == "center") {
28935                                                 ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
28936                                         } else if (align == "right") {
28937                                                 ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
28938                                         } else if (align == "stretch") {
28939                                                 ctrlLayoutRect.w = width;
28940                                         }
28941
28942                                         // Align control vertical
28943                                         align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
28944                                         if (align == "center") {
28945                                                 ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
28946                                         } else  if (align == "bottom") {
28947                                                 ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
28948                                         } else if (align == "stretch") {
28949                                                 ctrlLayoutRect.h = height;
28950                                         }
28951
28952                                         ctrl.layoutRect(ctrlLayoutRect);
28953
28954                                         posX += width + spacingH;
28955
28956                                         if (ctrl.recalc) {
28957                                                 ctrl.recalc();
28958                                         }
28959                                 }
28960
28961                                 posY += height + spacingV;
28962                         }
28963                 }
28964         });
28965 });
28966
28967 // Included from: js/tinymce/classes/ui/Iframe.js
28968
28969 /**
28970  * Iframe.js
28971  *
28972  * Copyright, Moxiecode Systems AB
28973  * Released under LGPL License.
28974  *
28975  * License: http://www.tinymce.com/license
28976  * Contributing: http://www.tinymce.com/contributing
28977  */
28978
28979 /*jshint scripturl:true */
28980
28981 /**
28982  * This class creates an iframe.
28983  *
28984  * @setting {String} url Url to open in the iframe.
28985  *
28986  * @-x-less Iframe.less
28987  * @class tinymce.ui.Iframe
28988  * @extends tinymce.ui.Widget
28989  */
28990 define("tinymce/ui/Iframe", [
28991         "tinymce/ui/Widget"
28992 ], function(Widget) {
28993         "use strict";
28994
28995         return Widget.extend({
28996                 /**
28997                  * Renders the control as a HTML string.
28998                  *
28999                  * @method renderHtml
29000                  * @return {String} HTML representing the control.
29001                  */
29002                 renderHtml: function() {
29003                         var self = this;
29004
29005                         self.addClass('iframe');
29006                         self.canFocus = false;
29007
29008                         return (
29009                                 '<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
29010                                 (self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
29011                         );
29012                 },
29013
29014                 /**
29015                  * Setter for the iframe source.
29016                  *
29017                  * @method src
29018                  * @param {String} src Source URL for iframe.
29019                  */
29020                 src: function(src) {
29021                         this.getEl().src = src;
29022                 },
29023
29024                 /**
29025                  * Inner HTML for the iframe.
29026                  *
29027                  * @method html
29028                  * @param {String} html HTML string to set as HTML inside the iframe.
29029                  * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
29030                  * @return {tinymce.ui.Iframe} Current iframe control.
29031                  */
29032                 html: function(html, callback) {
29033                         var self = this, body = this.getEl().contentWindow.document.body;
29034
29035                         // Wait for iframe to initialize IE 10 takes time
29036                         if (!body) {
29037                                 setTimeout(function() {
29038                                         self.html(html);
29039                                 }, 0);
29040                         } else {
29041                                 body.innerHTML = html;
29042
29043                                 if (callback) {
29044                                         callback();
29045                                 }
29046                         }
29047
29048                         return this;
29049                 }
29050         });
29051 });
29052
29053 // Included from: js/tinymce/classes/ui/Label.js
29054
29055 /**
29056  * Label.js
29057  *
29058  * Copyright, Moxiecode Systems AB
29059  * Released under LGPL License.
29060  *
29061  * License: http://www.tinymce.com/license
29062  * Contributing: http://www.tinymce.com/contributing
29063  */
29064
29065 /**
29066  * This class creates a label element. A label is a simple text control
29067  * that can be bound to other controls.
29068  *
29069  * @-x-less Label.less
29070  * @class tinymce.ui.Label
29071  * @extends tinymce.ui.Widget
29072  */
29073 define("tinymce/ui/Label", [
29074         "tinymce/ui/Widget"
29075 ], function(Widget) {
29076         "use strict";
29077
29078         return Widget.extend({
29079                 /**
29080                  * Constructs a instance with the specified settings.
29081                  *
29082                  * @constructor
29083                  * @param {Object} settings Name/value object with settings.
29084                  * @param {Boolean} multiline Multiline label.
29085                  */
29086                 init: function(settings) {
29087                         var self = this;
29088
29089                         self._super(settings);
29090                         self.addClass('widget');
29091                         self.addClass('label');
29092                         self.canFocus = false;
29093
29094                         if (settings.multiline) {
29095                                 self.addClass('autoscroll');
29096                         }
29097
29098                         if (settings.strong) {
29099                                 self.addClass('strong');
29100                         }
29101                 },
29102
29103                 /**
29104                  * Initializes the current controls layout rect.
29105                  * This will be executed by the layout managers to determine the
29106                  * default minWidth/minHeight etc.
29107                  *
29108                  * @method initLayoutRect
29109                  * @return {Object} Layout rect instance.
29110                  */
29111                 initLayoutRect: function() {
29112                         var self = this, layoutRect = self._super();
29113
29114                         if (self.settings.multiline) {
29115                                 // Check if the text fits within maxW if not then try word wrapping it
29116                                 if (self.getEl().offsetWidth > layoutRect.maxW) {
29117                                         layoutRect.minW = layoutRect.maxW;
29118                                         self.addClass('multiline');
29119                                 }
29120
29121                                 self.getEl().style.width = layoutRect.minW + 'px';
29122                                 layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, self.getEl().offsetHeight);
29123                         }
29124
29125                         return layoutRect;
29126                 },
29127
29128                 /**
29129                  * Sets/gets the disabled state on the control.
29130                  *
29131                  * @method disabled
29132                  * @param {Boolean} state Value to set to control.
29133                  * @return {Boolean/tinymce.ui.Label} Current control on a set operation or current state on a get.
29134                  */
29135                 disabled: function(state) {
29136                         var self = this, undef;
29137
29138                         if (state !== undef) {
29139                                 self.toggleClass('label-disabled', state);
29140
29141                                 if (self._rendered) {
29142                                         self.getEl()[0].className = self.classes();
29143                                 }
29144                         }
29145
29146                         return self._super(state);
29147                 },
29148
29149                 /**
29150                  * Repaints the control after a layout operation.
29151                  *
29152                  * @method repaint
29153                  */
29154                 repaint: function() {
29155                         var self = this;
29156
29157                         if (!self.settings.multiline) {
29158                                 self.getEl().style.lineHeight = self.layoutRect().h + 'px';
29159                         }
29160
29161                         return self._super();
29162                 },
29163
29164                 /**
29165                  * Sets/gets the current label text.
29166                  *
29167                  * @method text
29168                  * @param {String} [text] New label text.
29169                  * @return {String|tinymce.ui.Label} Current text or current label instance.
29170                  */
29171                 text: function(text) {
29172                         var self = this;
29173
29174                         if (self._rendered && text) {
29175                                 this.innerHtml(self.encode(text));
29176                         }
29177
29178                         return self._super(text);
29179                 },
29180
29181                 /**
29182                  * Renders the control as a HTML string.
29183                  *
29184                  * @method renderHtml
29185                  * @return {String} HTML representing the control.
29186                  */
29187                 renderHtml: function() {
29188                         var self = this, forId = self.settings.forId;
29189
29190                         return (
29191                                 '<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId : '') + '">' +
29192                                         self.encode(self._text) +
29193                                 '</label>'
29194                         );
29195                 }
29196         });
29197 });
29198
29199 // Included from: js/tinymce/classes/ui/Toolbar.js
29200
29201 /**
29202  * Toolbar.js
29203  *
29204  * Copyright, Moxiecode Systems AB
29205  * Released under LGPL License.
29206  *
29207  * License: http://www.tinymce.com/license
29208  * Contributing: http://www.tinymce.com/contributing
29209  */
29210
29211 /**
29212  * Creates a new toolbar.
29213  *
29214  * @class tinymce.ui.Toolbar
29215  * @extends tinymce.ui.Container
29216  */
29217 define("tinymce/ui/Toolbar", [
29218         "tinymce/ui/Container",
29219         "tinymce/ui/KeyboardNavigation"
29220 ], function(Container, KeyboardNavigation) {
29221         "use strict";
29222
29223         return Container.extend({
29224                 Defaults: {
29225                         role: 'toolbar',
29226                         layout: 'flow'
29227                 },
29228
29229                 /**
29230                  * Constructs a instance with the specified settings.
29231                  *
29232                  * @constructor
29233                  * @param {Object} settings Name/value object with settings.
29234                  */
29235                 init: function(settings) {
29236                         var self = this;
29237
29238                         self._super(settings);
29239                         self.addClass('toolbar');
29240                 },
29241
29242                 /**
29243                  * Called after the control has been rendered.
29244                  *
29245                  * @method postRender
29246                  */
29247                 postRender: function() {
29248                         var self = this;
29249
29250                         self.items().addClass('toolbar-item');
29251
29252                         self.keyNav = new KeyboardNavigation({
29253                                 root: self,
29254                                 enableLeftRight: true
29255                         });
29256
29257                         return self._super();
29258                 }
29259         });
29260 });
29261
29262 // Included from: js/tinymce/classes/ui/MenuBar.js
29263
29264 /**
29265  * MenuBar.js
29266  *
29267  * Copyright, Moxiecode Systems AB
29268  * Released under LGPL License.
29269  *
29270  * License: http://www.tinymce.com/license
29271  * Contributing: http://www.tinymce.com/contributing
29272  */
29273
29274 /**
29275  * Creates a new menubar.
29276  *
29277  * @-x-less MenuBar.less
29278  * @class tinymce.ui.MenuBar
29279  * @extends tinymce.ui.Container
29280  */
29281 define("tinymce/ui/MenuBar", [
29282         "tinymce/ui/Toolbar"
29283 ], function(Toolbar) {
29284         "use strict";
29285
29286         return Toolbar.extend({
29287                 Defaults: {
29288                         role: 'menubar',
29289                         containerCls: 'menubar',
29290                         defaults: {
29291                                 type: 'menubutton'
29292                         }
29293                 }
29294         });
29295 });
29296
29297 // Included from: js/tinymce/classes/ui/MenuButton.js
29298
29299 /**
29300  * MenuButton.js
29301  *
29302  * Copyright, Moxiecode Systems AB
29303  * Released under LGPL License.
29304  *
29305  * License: http://www.tinymce.com/license
29306  * Contributing: http://www.tinymce.com/contributing
29307  */
29308
29309 /**
29310  * Creates a new menu button.
29311  *
29312  * @-x-less MenuButton.less
29313  * @class tinymce.ui.MenuButton
29314  * @extends tinymce.ui.Button
29315  */
29316 define("tinymce/ui/MenuButton", [
29317         "tinymce/ui/Button",
29318         "tinymce/ui/Factory",
29319         "tinymce/ui/MenuBar"
29320 ], function(Button, Factory, MenuBar) {
29321         "use strict";
29322
29323         // TODO: Maybe add as some global function
29324         function isChildOf(node, parent) {
29325                 while (node) {
29326                         if (parent === node) {
29327                                 return true;
29328                         }
29329
29330                         node = node.parentNode;
29331                 }
29332
29333                 return false;
29334         }
29335
29336         var MenuButton = Button.extend({
29337                 /**
29338                  * Constructs a instance with the specified settings.
29339                  *
29340                  * @constructor
29341                  * @param {Object} settings Name/value object with settings.
29342                  */
29343                 init: function(settings) {
29344                         var self = this;
29345
29346                         self._renderOpen = true;
29347                         self._super(settings);
29348
29349                         self.addClass('menubtn');
29350
29351                         if (settings.fixedWidth) {
29352                                 self.addClass('fixed-width');
29353                         }
29354
29355                         self.aria('haspopup', true);
29356                         self.hasPopup = true;
29357                 },
29358
29359                 /**
29360                  * Shows the menu for the button.
29361                  *
29362                  * @method showMenu
29363                  */
29364                 showMenu: function() {
29365                         var self = this, settings = self.settings, menu;
29366
29367                         if (self.menu && self.menu.visible()) {
29368                                 return self.hideMenu();
29369                         }
29370
29371                         if (!self.menu) {
29372                                 menu = settings.menu || [];
29373
29374                                 // Is menu array then auto constuct menu control
29375                                 if (menu.length) {
29376                                         menu = {
29377                                                 type: 'menu',
29378                                                 items: menu
29379                                         };
29380                                 } else {
29381                                         menu.type = menu.type || 'menu';
29382                                 }
29383
29384                                 self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
29385                                 self.fire('createmenu');
29386                                 self.menu.reflow();
29387                                 self.menu.on('cancel', function(e) {
29388                                         if (e.control === self.menu) {
29389                                                 self.focus();
29390                                         }
29391                                 });
29392
29393                                 self.menu.on('show hide', function(e) {
29394                                         if (e.control == self.menu) {
29395                                                 self.activeMenu(e.type == 'show');
29396                                         }
29397                                 }).fire('show');
29398
29399                                 self.aria('expanded', true);
29400                         }
29401
29402                         self.menu.show();
29403                         self.menu.layoutRect({w: self.layoutRect().w});
29404                         self.menu.moveRel(self.getEl(), ['bl-tl', 'tl-bl']);
29405                 },
29406
29407                 /**
29408                  * Hides the menu for the button.
29409                  *
29410                  * @method hideMenu
29411                  */
29412                 hideMenu: function() {
29413                         var self = this;
29414
29415                         if (self.menu) {
29416                                 self.menu.items().each(function(item) {
29417                                         if (item.hideMenu) {
29418                                                 item.hideMenu();
29419                                         }
29420                                 });
29421
29422                                 self.menu.hide();
29423                                 self.aria('expanded', false);
29424                         }
29425                 },
29426
29427                 /**
29428                  * Sets the active menu state.
29429                  *
29430                  * @private
29431                  */
29432                 activeMenu: function(state) {
29433                         this.toggleClass('active', state);
29434                 },
29435
29436                 /**
29437                  * Renders the control as a HTML string.
29438                  *
29439                  * @method renderHtml
29440                  * @return {String} HTML representing the control.
29441                  */
29442                 renderHtml: function() {
29443                         var self = this, id = self._id, prefix = self.classPrefix;
29444                         var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
29445
29446                         self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
29447
29448                         return (
29449                                 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
29450                                         '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
29451                                                 (icon ? '<i class="' + icon + '"></i>' : '') +
29452                                                 '<span>' + (self._text ? (icon ? ' ' : '') + self.encode(self._text) : '') + '</span>' +
29453                                                 ' <i class="' + prefix + 'caret"></i>' +
29454                                         '</button>' +
29455                                 '</div>'
29456                         );
29457                 },
29458
29459                 /**
29460                  * Gets invoked after the control has been rendered.
29461                  *
29462                  * @method postRender
29463                  */
29464                 postRender: function() {
29465                         var self = this;
29466
29467                         self.on('click', function(e) {
29468                                 if (e.control === self && isChildOf(e.target, self.getEl())) {
29469                                         self.showMenu();
29470
29471                                         if (e.keyboard) {
29472                                                 self.menu.items()[0].focus();
29473                                         }
29474                                 }
29475                         });
29476
29477                         self.on('mouseenter', function(e) {
29478                                 var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
29479
29480                                 if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
29481                                         parent.items().filter('MenuButton').each(function(ctrl) {
29482                                                 if (ctrl.hideMenu && ctrl != overCtrl) {
29483                                                         if (ctrl.menu && ctrl.menu.visible()) {
29484                                                                 hasVisibleSiblingMenu = true;
29485                                                         }
29486
29487                                                         ctrl.hideMenu();
29488                                                 }
29489                                         });
29490
29491                                         if (hasVisibleSiblingMenu) {
29492                                                 overCtrl.focus(); // Fix for: #5887
29493                                                 overCtrl.showMenu();
29494                                         }
29495                                 }
29496                         });
29497
29498                         return self._super();
29499                 },
29500
29501                 /**
29502                  * Sets/gets the current button text.
29503                  *
29504                  * @method text
29505                  * @param {String} [text] New button text.
29506                  * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance.
29507                  */
29508                 text: function(text) {
29509                         var self = this, i, children;
29510
29511                         if (self._rendered) {
29512                                 children = self.getEl('open').getElementsByTagName('span');
29513                                 for (i = 0; i < children.length; i++) {
29514                                         children[i].innerHTML = self.encode(text);
29515                                 }
29516                         }
29517
29518                         return this._super(text);
29519                 },
29520
29521                 /**
29522                  * Removes the control and it's menus.
29523                  *
29524                  * @method remove
29525                  */
29526                 remove: function() {
29527                         this._super();
29528
29529                         if (this.menu) {
29530                                 this.menu.remove();
29531                         }
29532                 }
29533         });
29534
29535         return MenuButton;
29536 });
29537
29538 // Included from: js/tinymce/classes/ui/ListBox.js
29539
29540 /**
29541  * ListBox.js
29542  *
29543  * Copyright, Moxiecode Systems AB
29544  * Released under LGPL License.
29545  *
29546  * License: http://www.tinymce.com/license
29547  * Contributing: http://www.tinymce.com/contributing
29548  */
29549
29550 /**
29551  * Creates a new list box control.
29552  *
29553  * @-x-less ListBox.less
29554  * @class tinymce.ui.ListBox
29555  * @extends tinymce.ui.MenuButton
29556  */
29557 define("tinymce/ui/ListBox", [
29558         "tinymce/ui/MenuButton"
29559 ], function(MenuButton) {
29560         "use strict";
29561
29562         return MenuButton.extend({
29563                 /**
29564                  * Constructs a instance with the specified settings.
29565                  *
29566                  * @constructor
29567                  * @param {Object} settings Name/value object with settings.
29568                  * @setting {Array} values Array with values to add to list box.
29569                  */
29570                 init: function(settings) {
29571                         var self = this, values, i, selected, selectedText, lastItemCtrl;
29572
29573                         self._values = values = settings.values;
29574                         if (values) {
29575                                 for (i = 0; i < values.length; i++) {
29576                                         selected = values[i].selected || settings.value === values[i].value;
29577
29578                                         if (selected) {
29579                                                 selectedText = selectedText || values[i].text;
29580                                                 self._value = values[i].value;
29581                                         }
29582                                 }
29583
29584                                 settings.menu = values;
29585                         }
29586
29587                         settings.text = settings.text || selectedText || values[0].text;
29588
29589                         self._super(settings);
29590                         self.addClass('listbox');
29591
29592                         self.on('select', function(e) {
29593                                 var ctrl = e.control;
29594
29595                                 if (lastItemCtrl) {
29596                                         e.lastControl = lastItemCtrl;
29597                                 }
29598
29599                                 if (settings.multiple) {
29600                                         ctrl.active(!ctrl.active());
29601                                 } else {
29602                                         self.value(e.control.settings.value);
29603                                 }
29604
29605                                 lastItemCtrl = ctrl;
29606                         });
29607                 },
29608
29609                 /**
29610                  * Getter/setter function for the control value.
29611                  *
29612                  * @method value
29613                  * @param {String} [value] Value to be set.
29614                  * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
29615                  */
29616                 value: function(value) {
29617                         var self = this, active, selectedText, menu, i;
29618
29619                         function activateByValue(menu, value) {
29620                                 menu.items().each(function(ctrl) {
29621                                         active = ctrl.value() === value;
29622
29623                                         if (active) {
29624                                                 selectedText = selectedText || ctrl.text();
29625                                         }
29626
29627                                         ctrl.active(active);
29628
29629                                         if (ctrl.menu) {
29630                                                 activateByValue(ctrl.menu, value);
29631                                         }
29632                                 });
29633                         }
29634
29635                         if (typeof(value) != "undefined") {
29636                                 if (self.menu) {
29637                                         activateByValue(self.menu, value);
29638                                 } else {
29639                                         menu = self.settings.menu;
29640                                         for (i = 0; i < menu.length; i++) {
29641                                                 active = menu[i].value == value;
29642
29643                                                 if (active) {
29644                                                         selectedText = selectedText || menu[i].text;
29645                                                 }
29646
29647                                                 menu[i].active = active;
29648                                         }
29649                                 }
29650
29651                                 self.text(selectedText || this.settings.text);
29652                         }
29653
29654                         return self._super(value);
29655                 }
29656         });
29657 });
29658
29659 // Included from: js/tinymce/classes/ui/MenuItem.js
29660
29661 /**
29662  * MenuItem.js
29663  *
29664  * Copyright, Moxiecode Systems AB
29665  * Released under LGPL License.
29666  *
29667  * License: http://www.tinymce.com/license
29668  * Contributing: http://www.tinymce.com/contributing
29669  */
29670
29671 /**
29672  * Creates a new menu item.
29673  *
29674  * @-x-less MenuItem.less
29675  * @class tinymce.ui.MenuItem
29676  * @extends tinymce.ui.Control
29677  */
29678 define("tinymce/ui/MenuItem", [
29679         "tinymce/ui/Widget",
29680         "tinymce/ui/Factory"
29681 ], function(Widget, Factory) {
29682         "use strict";
29683
29684         return Widget.extend({
29685                 Defaults: {
29686                         border: 0,
29687                         role: 'menuitem'
29688                 },
29689
29690                 /**
29691                  * Constructs a instance with the specified settings.
29692                  *
29693                  * @constructor
29694                  * @param {Object} settings Name/value object with settings.
29695                  * @setting {Boolean} selectable Selectable menu.
29696                  * @setting {Array} menu Submenu array with items.
29697                  * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
29698                  */
29699                 init: function(settings) {
29700                         var self = this;
29701
29702                         self.hasPopup = true;
29703
29704                         self._super(settings);
29705
29706                         settings = self.settings;
29707
29708                         self.addClass('menu-item');
29709
29710                         if (settings.menu) {
29711                                 self.addClass('menu-item-expand');
29712                         }
29713
29714                         if (settings.preview) {
29715                                 self.addClass('menu-item-preview');
29716                         }
29717
29718                         if (self._text === '-' || self._text === '|') {
29719                                 self.addClass('menu-item-sep');
29720                                 self.aria('role', 'separator');
29721                                 self.canFocus = false;
29722                                 self._text = '-';
29723                         }
29724
29725                         if (settings.selectable) {
29726                                 self.aria('role', 'menuitemcheckbox');
29727                                 self.aria('checked', true);
29728                                 self.addClass('menu-item-checkbox');
29729                                 settings.icon = 'selected';
29730                         }
29731
29732                         self.on('mousedown', function(e) {
29733                                 e.preventDefault();
29734                         });
29735
29736                         self.on('mouseenter click', function(e) {
29737                                 if (e.control === self) {
29738                                         if (!settings.menu && e.type === 'click') {
29739                                                 self.parent().hideAll();
29740                                                 self.fire('cancel');
29741                                                 self.fire('select');
29742                                         } else {
29743                                                 self.showMenu();
29744
29745                                                 if (e.keyboard) {
29746                                                         setTimeout(function() {
29747                                                                 self.menu.items()[0].focus();
29748                                                         }, 0);
29749                                                 }
29750                                         }
29751                                 }
29752                         });
29753
29754                         if (settings.menu) {
29755                                 self.aria('haspopup', true);
29756                         }
29757                 },
29758
29759                 /**
29760                  * Returns true/false if the menuitem has sub menu.
29761                  *
29762                  * @method hasMenus
29763                  * @return {Boolean} True/false state if it has submenu.
29764                  */
29765                 hasMenus: function() {
29766                         return !!this.settings.menu;
29767                 },
29768
29769                 /**
29770                  * Shows the menu for the menu item.
29771                  *
29772                  * @method showMenu
29773                  */
29774                 showMenu: function() {
29775                         var self = this, settings = self.settings, menu, parent = self.parent();
29776
29777                         parent.items().each(function(ctrl) {
29778                                 if (ctrl !== self) {
29779                                         ctrl.hideMenu();
29780                                 }
29781                         });
29782
29783                         if (settings.menu) {
29784                                 menu = self.menu;
29785
29786                                 if (!menu) {
29787                                         menu = settings.menu;
29788
29789                                         // Is menu array then auto constuct menu control
29790                                         if (menu.length) {
29791                                                 menu = {
29792                                                         type: 'menu',
29793                                                         items: menu
29794                                                 };
29795                                         } else {
29796                                                 menu.type = menu.type || 'menu';
29797                                         }
29798
29799                                         if (parent.settings.itemDefaults) {
29800                                                 menu.itemDefaults = parent.settings.itemDefaults;
29801                                         }
29802
29803                                         menu = self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
29804                                         menu.reflow();
29805                                         menu.fire('show');
29806                                         menu.on('cancel', function() {
29807                                                 self.focus();
29808                                         });
29809
29810                                         menu.on('hide', function(e) {
29811                                                 if (e.control === menu) {
29812                                                         self.removeClass('selected');
29813                                                 }
29814                                         });
29815                                 } else {
29816                                         menu.show();
29817                                 }
29818
29819                                 menu._parentMenu = parent;
29820
29821                                 menu.addClass('menu-sub');
29822
29823                                 var rel = menu.testMoveRel(self.getEl(), ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']);
29824                                 menu.moveRel(self.getEl(), rel);
29825
29826                                 rel = 'menu-sub-' + rel;
29827                                 menu.removeClass(menu._lastRel);
29828                                 menu.addClass(rel);
29829                                 menu._lastRel = rel;
29830
29831                                 self.addClass('selected');
29832                                 self.aria('expanded', true);
29833                         }
29834                 },
29835
29836                 /**
29837                  * Hides the menu for the menu item.
29838                  *
29839                  * @method hideMenu
29840                  */
29841                 hideMenu: function() {
29842                         var self = this;
29843
29844                         if (self.menu) {
29845                                 self.menu.items().each(function(item) {
29846                                         if (item.hideMenu) {
29847                                                 item.hideMenu();
29848                                         }
29849                                 });
29850
29851                                 self.menu.hide();
29852                                 self.aria('expanded', false);
29853                         }
29854
29855                         return self;
29856                 },
29857
29858                 /**
29859                  * Renders the control as a HTML string.
29860                  *
29861                  * @method renderHtml
29862                  * @return {String} HTML representing the control.
29863                  */
29864                 renderHtml: function() {
29865                         var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
29866                         var icon = self.settings.icon;
29867
29868                         if (icon) {
29869                                 self.parent().addClass('menu-has-icons');
29870                         }
29871
29872                         icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
29873
29874                         return (
29875                                 '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
29876                                         (text !== '-' ? '<i class="' + icon + '"></i>&nbsp;' : '') +
29877                                         (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
29878                                         (settings.shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' +
29879                                                 settings.shortcut + '</div>' : '') +
29880                                         (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
29881                                 '</div>'
29882                         );
29883                 },
29884
29885                 /**
29886                  * Gets invoked after the control has been rendered.
29887                  *
29888                  * @method postRender
29889                  */
29890                 postRender: function() {
29891                         var self = this, settings = self.settings;
29892
29893                         var textStyle = settings.textStyle;
29894                         if (typeof(textStyle) == "function") {
29895                                 textStyle = textStyle.call(this);
29896                         }
29897
29898                         if (textStyle) {
29899                                 var textElm = self.getEl('text');
29900                                 if (textElm) {
29901                                         textElm.setAttribute('style', textStyle);
29902                                 }
29903                         }
29904
29905                         return self._super();
29906                 },
29907
29908                 /**
29909                  * Removes the control and it's menus.
29910                  *
29911                  * @method remove
29912                  */
29913                 remove: function() {
29914                         this._super();
29915
29916                         if (this.menu) {
29917                                 this.menu.remove();
29918                         }
29919                 }
29920         });
29921 });
29922
29923 // Included from: js/tinymce/classes/ui/Menu.js
29924
29925 /**
29926  * Menu.js
29927  *
29928  * Copyright, Moxiecode Systems AB
29929  * Released under LGPL License.
29930  *
29931  * License: http://www.tinymce.com/license
29932  * Contributing: http://www.tinymce.com/contributing
29933  */
29934
29935 /**
29936  * Creates a new menu.
29937  *
29938  * @-x-less Menu.less
29939  * @class tinymce.ui.Menu
29940  * @extends tinymce.ui.FloatPanel
29941  */
29942 define("tinymce/ui/Menu", [
29943         "tinymce/ui/FloatPanel",
29944         "tinymce/ui/KeyboardNavigation",
29945         "tinymce/ui/MenuItem",
29946         "tinymce/util/Tools"
29947 ], function(FloatPanel, KeyboardNavigation, MenuItem, Tools) {
29948         "use strict";
29949
29950         var Menu = FloatPanel.extend({
29951                 Defaults: {
29952                         defaultType: 'menuitem',
29953                         border: 1,
29954                         layout: 'stack',
29955                         role: 'menu'
29956                 },
29957
29958                 /**
29959                  * Constructs a instance with the specified settings.
29960                  *
29961                  * @constructor
29962                  * @param {Object} settings Name/value object with settings.
29963                  */
29964                 init: function(settings) {
29965                         var self = this;
29966
29967                         settings.autohide = true;
29968                         settings.constrainToViewport = true;
29969
29970                         if (settings.itemDefaults) {
29971                                 var items = settings.items, i = items.length;
29972
29973                                 while (i--) {
29974                                         items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
29975                                 }
29976                         }
29977
29978                         self._super(settings);
29979                         self.addClass('menu');
29980
29981                         self.keyNav = new KeyboardNavigation({
29982                                 root: self,
29983                                 enableUpDown: true,
29984                                 enableLeftRight: true,
29985
29986                                 leftAction: function() {
29987                                         if (self.parent() instanceof MenuItem) {
29988                                                 self.keyNav.cancel();
29989                                         }
29990                                 },
29991
29992                                 onCancel: function() {
29993                                         self.fire('cancel', {}, false);
29994                                         self.hide();
29995                                 }
29996                         });
29997                 },
29998
29999                 /**
30000                  * Repaints the control after a layout operation.
30001                  *
30002                  * @method repaint
30003                  */
30004                 repaint: function() {
30005                         this.toggleClass('menu-align', true);
30006
30007                         this._super();
30008
30009                         this.getEl().style.height = '';
30010                         this.getEl('body').style.height = '';
30011
30012                         return this;
30013                 },
30014
30015                 /**
30016                  * Hides/closes the menu.
30017                  *
30018                  * @method cancel
30019                  */
30020                 cancel: function() {
30021                         var self = this;
30022
30023                         self.hideAll();
30024                         self.fire('cancel');
30025                         self.fire('select');
30026                 },
30027
30028                 /**
30029                  * Hide menu and all sub menus.
30030                  *
30031                  * @method hideAll
30032                  */
30033                 hideAll: function() {
30034                         var self = this;
30035
30036                         this.find('menuitem').exec('hideMenu');
30037
30038                         return self._super();
30039                 },
30040
30041                 /**
30042                  * Invoked before the menu is rendered.
30043                  *
30044                  * @method preRender
30045                  */
30046                 preRender: function() {
30047                         var self = this;
30048
30049                         self.items().each(function(ctrl) {
30050                                 var settings = ctrl.settings;
30051
30052                                 if (settings.icon || settings.selectable) {
30053                                         self._hasIcons = true;
30054                                         return false;
30055                                 }
30056                         });
30057
30058                         return self._super();
30059                 }
30060         });
30061
30062         return Menu;
30063 });
30064
30065 // Included from: js/tinymce/classes/ui/Radio.js
30066
30067 /**
30068  * Radio.js
30069  *
30070  * Copyright, Moxiecode Systems AB
30071  * Released under LGPL License.
30072  *
30073  * License: http://www.tinymce.com/license
30074  * Contributing: http://www.tinymce.com/contributing
30075  */
30076
30077 /**
30078  * Creates a new radio button.
30079  *
30080  * @-x-less Radio.less
30081  * @class tinymce.ui.Radio
30082  * @extends tinymce.ui.Checkbox
30083  */
30084 define("tinymce/ui/Radio", [
30085         "tinymce/ui/Checkbox"
30086 ], function(Checkbox) {
30087         "use strict";
30088
30089         return Checkbox.extend({
30090                 Defaults: {
30091                         classes: "radio",
30092                         role: "radio"
30093                 }
30094         });
30095 });
30096
30097 // Included from: js/tinymce/classes/ui/ResizeHandle.js
30098
30099 /**
30100  * ResizeHandle.js
30101  *
30102  * Copyright, Moxiecode Systems AB
30103  * Released under LGPL License.
30104  *
30105  * License: http://www.tinymce.com/license
30106  * Contributing: http://www.tinymce.com/contributing
30107  */
30108
30109 /**
30110  * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
30111  *
30112  * @-x-less ResizeHandle.less
30113  * @class tinymce.ui.ResizeHandle
30114  * @extends tinymce.ui.Widget
30115  */
30116 define("tinymce/ui/ResizeHandle", [
30117         "tinymce/ui/Widget",
30118         "tinymce/ui/DragHelper"
30119 ], function(Widget, DragHelper) {
30120         "use strict";
30121
30122         return Widget.extend({
30123                 /**
30124                  * Renders the control as a HTML string.
30125                  *
30126                  * @method renderHtml
30127                  * @return {String} HTML representing the control.
30128                  */
30129                 renderHtml: function() {
30130                         var self = this, prefix = self.classPrefix;
30131
30132                         self.addClass('resizehandle');
30133
30134                         if (self.settings.direction == "both") {
30135                                 self.addClass('resizehandle-both');
30136                         }
30137
30138                         self.canFocus = false;
30139
30140                         return (
30141                                 '<div id="' + self._id + '" class="' + self.classes() + '">' +
30142                                         '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
30143                                 '</div>'
30144                         );
30145                 },
30146
30147                 /**
30148                  * Called after the control has been rendered.
30149                  *
30150                  * @method postRender
30151                  */
30152                 postRender: function() {
30153                         var self = this;
30154
30155                         self._super();
30156
30157                         self.resizeDragHelper = new DragHelper(this._id, {
30158                                 start: function() {
30159                                         self.fire('ResizeStart');
30160                                 },
30161
30162                                 drag: function(e) {
30163                                         if (self.settings.direction != "both") {
30164                                                 e.deltaX = 0;
30165                                         }
30166
30167                                         self.fire('Resize', e);
30168                                 },
30169
30170                                 end: function() {
30171                                         self.fire('ResizeEnd');
30172                                 }
30173                         });
30174                 }
30175         });
30176 });
30177
30178 // Included from: js/tinymce/classes/ui/Spacer.js
30179
30180 /**
30181  * Spacer.js
30182  *
30183  * Copyright, Moxiecode Systems AB
30184  * Released under LGPL License.
30185  *
30186  * License: http://www.tinymce.com/license
30187  * Contributing: http://www.tinymce.com/contributing
30188  */
30189
30190 /**
30191  * Creates a spacer. This control is used in flex layouts for example.
30192  *
30193  * @-x-less Spacer.less
30194  * @class tinymce.ui.Spacer
30195  * @extends tinymce.ui.Widget
30196  */
30197 define("tinymce/ui/Spacer", [
30198         "tinymce/ui/Widget"
30199 ], function(Widget) {
30200         "use strict";
30201
30202         return Widget.extend({
30203                 /**
30204                  * Renders the control as a HTML string.
30205                  *
30206                  * @method renderHtml
30207                  * @return {String} HTML representing the control.
30208                  */
30209                 renderHtml: function() {
30210                         var self = this;
30211
30212                         self.addClass('spacer');
30213                         self.canFocus = false;
30214
30215                         return '<div id="' + self._id + '" class="' + self.classes() + '"></div>';
30216                 }
30217         });
30218 });
30219
30220 // Included from: js/tinymce/classes/ui/SplitButton.js
30221
30222 /**
30223  * SplitButton.js
30224  *
30225  * Copyright, Moxiecode Systems AB
30226  * Released under LGPL License.
30227  *
30228  * License: http://www.tinymce.com/license
30229  * Contributing: http://www.tinymce.com/contributing
30230  */
30231
30232 /**
30233  * Creates a split button.
30234  *
30235  * @-x-less SplitButton.less
30236  * @class tinymce.ui.SplitButton
30237  * @extends tinymce.ui.Button
30238  */
30239 define("tinymce/ui/SplitButton", [
30240         "tinymce/ui/MenuButton",
30241         "tinymce/dom/DOMUtils"
30242 ], function(MenuButton, DomUtils) {
30243         var DOM = DomUtils.DOM;
30244
30245         return MenuButton.extend({
30246                 Defaults: {
30247                         classes: "widget btn splitbtn",
30248                         role: "splitbutton"
30249                 },
30250
30251                 /**
30252                  * Repaints the control after a layout operation.
30253                  *
30254                  * @method repaint
30255                  */
30256                 repaint: function() {
30257                         var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm, btnStyle;
30258
30259                         self._super();
30260
30261                         mainButtonElm = elm.firstChild;
30262                         menuButtonElm = elm.lastChild;
30263
30264                         DOM.css(mainButtonElm, {
30265                                 width: rect.w - menuButtonElm.offsetWidth,
30266                                 height: rect.h - 2
30267                         });
30268
30269                         DOM.css(menuButtonElm, {
30270                                 height: rect.h - 2
30271                         });
30272
30273                         btnStyle = mainButtonElm.firstChild.style;
30274                         btnStyle.width = btnStyle.height = "100%";
30275
30276                         btnStyle = menuButtonElm.firstChild.style;
30277                         btnStyle.width = btnStyle.height = "100%";
30278
30279                         return self;
30280                 },
30281
30282                 /**
30283                  * Sets the active menu state.
30284                  *
30285                  * @private
30286                  */
30287                 activeMenu: function(state) {
30288                         var self = this;
30289
30290                         DOM.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state);
30291                 },
30292
30293                 /**
30294                  * Renders the control as a HTML string.
30295                  *
30296                  * @method renderHtml
30297                  * @return {String} HTML representing the control.
30298                  */
30299                 renderHtml: function() {
30300                         var self = this, id = self._id, prefix = self.classPrefix;
30301                         var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
30302
30303                         return (
30304                                 '<div id="' + id + '" class="' + self.classes() + '">' +
30305                                         '<button type="button" hidefocus tabindex="-1">' +
30306                                                 (icon ? '<i class="' + icon + '"></i>' : '') +
30307                                                 (self._text ? (icon ? ' ' : '') + self._text : '') +
30308                                         '</button>' +
30309                                         '<button type="button" class="' + prefix + 'open" hidefocus tabindex="-1">' +
30310                                                 //(icon ? '<i class="' + icon + '"></i>' : '') +
30311                                                 (self._menuBtnText ? (icon ? ' ' : '') + self._menuBtnText : '') +
30312                                                 ' <i class="' + prefix + 'caret"></i>' +
30313                                         '</button>' +
30314                                 '</div>'
30315                         );
30316                 },
30317
30318                 /**
30319                  * Called after the control has been rendered.
30320                  *
30321                  * @method postRender
30322                  */
30323                 postRender: function() {
30324                         var self = this, onClickHandler = self.settings.onclick;
30325
30326                         self.on('click', function(e) {
30327                                 if (e.control == this && !DOM.getParent(e.target, '.' + this.classPrefix + 'open')) {
30328                                         e.stopImmediatePropagation();
30329                                         onClickHandler.call(this, e);
30330                                 }
30331                         });
30332
30333                         delete self.settings.onclick;
30334
30335                         return self._super();
30336                 }
30337         });
30338 });
30339
30340 // Included from: js/tinymce/classes/ui/StackLayout.js
30341
30342 /**
30343  * StackLayout.js
30344  *
30345  * Copyright, Moxiecode Systems AB
30346  * Released under LGPL License.
30347  *
30348  * License: http://www.tinymce.com/license
30349  * Contributing: http://www.tinymce.com/contributing
30350  */
30351
30352 /**
30353  * This layout uses the browsers layout when the items are blocks.
30354  *
30355  * @-x-less StackLayout.less
30356  * @class tinymce.ui.StackLayout
30357  * @extends tinymce.ui.FlowLayout
30358  */
30359 define("tinymce/ui/StackLayout", [
30360         "tinymce/ui/FlowLayout"
30361 ], function(FlowLayout) {
30362         "use strict";
30363
30364         return FlowLayout.extend({
30365                 Defaults: {
30366                         containerClass: 'stack-layout',
30367                         controlClass: 'stack-layout-item',
30368                         endClass : 'break'
30369                 }
30370         });
30371 });
30372
30373 // Included from: js/tinymce/classes/ui/TabPanel.js
30374
30375 /**
30376  * TabPanel.js
30377  *
30378  * Copyright, Moxiecode Systems AB
30379  * Released under LGPL License.
30380  *
30381  * License: http://www.tinymce.com/license
30382  * Contributing: http://www.tinymce.com/contributing
30383  */
30384
30385 /**
30386  * Creates a tab panel control.
30387  *
30388  * @-x-less TabPanel.less
30389  * @class tinymce.ui.TabPanel
30390  * @extends tinymce.ui.Panel
30391  *
30392  * @setting {Number} activeTab Active tab index.
30393  */
30394 define("tinymce/ui/TabPanel", [
30395         "tinymce/ui/Panel",
30396         "tinymce/ui/DomUtils"
30397 ], function(Panel, DomUtils) {
30398         "use stict";
30399
30400         return Panel.extend({
30401                 lastIdx: 0,
30402
30403                 Defaults: {
30404                         layout: 'absolute',
30405                         defaults: {
30406                                 type: 'panel'
30407                         }
30408                 },
30409
30410                 /**
30411                  * Activates the specified tab by index.
30412                  *
30413                  * @method activateTab
30414                  * @param {Number} idx Index of the tab to activate.
30415                  */
30416                 activateTab: function(idx) {
30417                         if (this.activeTabId) {
30418                                 DomUtils.removeClass(this.getEl(this.activeTabId), this.classPrefix + 'active');
30419                         }
30420
30421                         this.activeTabId = 't' + idx;
30422
30423                         DomUtils.addClass(this.getEl('t' + idx), this.classPrefix + 'active');
30424
30425                         if (idx != this.lastIdx) {
30426                                 this.items()[this.lastIdx].hide();
30427                                 this.lastIdx = idx;
30428                         }
30429
30430                         this.items()[idx].show().fire('showtab');
30431                         this.reflow();
30432                 },
30433
30434                 /**
30435                  * Renders the control as a HTML string.
30436                  *
30437                  * @method renderHtml
30438                  * @return {String} HTML representing the control.
30439                  */
30440                 renderHtml: function() {
30441                         var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
30442
30443                         self.preRender();
30444                         layout.preRender(self);
30445
30446                         self.items().each(function(ctrl, i) {
30447                                 tabsHtml += (
30448                                         '<div id="' + self._id + '-t' + i + '" class="' + prefix + 'tab" unselectable="on">' +
30449                                                 self.encode(ctrl.settings.title) +
30450                                         '</div>'
30451                                 );
30452                         });
30453
30454                         return (
30455                                 '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
30456                                         '<div id="' + self._id + '-head" class="' + prefix + 'tabs">' +
30457                                                 tabsHtml +
30458                                         '</div>' +
30459                                         '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
30460                                                 layout.renderHtml(self) +
30461                                         '</div>' +
30462                                 '</div>'
30463                         );
30464                 },
30465
30466                 /**
30467                  * Called after the control has been rendered.
30468                  *
30469                  * @method postRender
30470                  */
30471                 postRender: function() {
30472                         var self = this;
30473
30474                         self._super();
30475
30476                         self.settings.activeTab = self.settings.activeTab || 0;
30477                         self.activateTab(self.settings.activeTab);
30478
30479                         this.on('click', function(e) {
30480                                 var targetParent = e.target.parentNode;
30481
30482                                 if (e.target.parentNode.id == self._id + '-head') {
30483                                         var i = targetParent.childNodes.length;
30484
30485                                         while (i--) {
30486                                                 if (targetParent.childNodes[i] == e.target) {
30487                                                         self.activateTab(i);
30488                                                 }
30489                                         }
30490                                 }
30491                         });
30492                 },
30493
30494                 /**
30495                  * Initializes the current controls layout rect.
30496                  * This will be executed by the layout managers to determine the
30497                  * default minWidth/minHeight etc.
30498                  *
30499                  * @method initLayoutRect
30500                  * @return {Object} Layout rect instance.
30501                  */
30502                 initLayoutRect: function() {
30503                         var self = this, rect, minW, minH;
30504
30505                         minW = minH = 0;
30506                         self.items().each(function(item, i) {
30507                                 minW = Math.max(minW, item.layoutRect().minW);
30508                                 minH = Math.max(minH, item.layoutRect().minH);
30509                                 if (self.settings.activeTab != i) {
30510                                         item.hide();
30511                                 }
30512                         });
30513
30514                         self.items().each(function(ctrl) {
30515                                 ctrl.settings.x = 0;
30516                                 ctrl.settings.y = 0;
30517                                 ctrl.settings.w = minW;
30518                                 ctrl.settings.h = minH;
30519
30520                                 ctrl.layoutRect({
30521                                         x: 0,
30522                                         y: 0,
30523                                         w: minW,
30524                                         h: minH
30525                                 });
30526                         });
30527
30528                         var headH = self.getEl('head').offsetHeight;
30529
30530                         self.settings.minWidth = minW;
30531                         self.settings.minHeight = minH + headH;
30532
30533                         rect = self._super();
30534                         rect.deltaH += self.getEl('head').offsetHeight;
30535                         rect.innerH = rect.h - rect.deltaH;
30536
30537                         return rect;
30538                 }
30539         });
30540 });
30541
30542 // Included from: js/tinymce/classes/ui/TextBox.js
30543
30544 /**
30545  * TextBox.js
30546  *
30547  * Copyright, Moxiecode Systems AB
30548  * Released under LGPL License.
30549  *
30550  * License: http://www.tinymce.com/license
30551  * Contributing: http://www.tinymce.com/contributing
30552  */
30553
30554 /**
30555  * Creates a new textbox.
30556  *
30557  * @-x-less TextBox.less
30558  * @class tinymce.ui.TextBox
30559  * @extends tinymce.ui.Widget
30560  */
30561 define("tinymce/ui/TextBox", [
30562         "tinymce/ui/Widget",
30563         "tinymce/ui/DomUtils"
30564 ], function(Widget, DomUtils) {
30565         "use strict";
30566
30567         return Widget.extend({
30568                 /**
30569                  * Constructs a instance with the specified settings.
30570                  *
30571                  * @constructor
30572                  * @param {Object} settings Name/value object with settings.
30573                  * @setting {Boolean} multiline True if the textbox is a multiline control.
30574                  * @setting {Number} maxLength Max length for the textbox.
30575                  * @setting {Number} size Size of the textbox in characters.
30576                  */
30577                 init: function(settings) {
30578                         var self = this;
30579
30580                         self._super(settings);
30581
30582                         self._value = settings.value || '';
30583                         self.addClass('textbox');
30584
30585                         if (settings.multiline) {
30586                                 self.addClass('multiline');
30587                         } else {
30588                                 // TODO: Rework this
30589                                 self.on('keydown', function(e) {
30590                                         if (e.keyCode == 13) {
30591                                                 self.parents().reverse().each(function(ctrl) {
30592                                                         e.preventDefault();
30593
30594                                                         if (ctrl.submit) {
30595                                                                 ctrl.submit();
30596                                                                 return false;
30597                                                         }
30598                                                 });
30599                                         }
30600                                 });
30601                         }
30602                 },
30603
30604                 /**
30605                  * Getter/setter function for the control value.
30606                  *
30607                  * @method value
30608                  * @param {String} [value] Value to be set.
30609                  * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
30610                  */
30611                 value: function(value) {
30612                         var self = this;
30613
30614                         if (typeof(value) != "undefined") {
30615                                 self._value = value;
30616
30617                                 if (self._rendered) {
30618                                         self.getEl().value = value;
30619                                 }
30620
30621                                 return self;
30622                         }
30623
30624                         if (self._rendered) {
30625                                 return self.getEl().value;
30626                         }
30627
30628                         return self._value;
30629                 },
30630
30631                 /**
30632                  * Repaints the control after a layout operation.
30633                  *
30634                  * @method repaint
30635                  */
30636                 repaint: function() {
30637                         var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
30638
30639                         style = self.getEl().style;
30640                         rect = self._layoutRect;
30641                         lastRepaintRect = self._lastRepaintRect || {};
30642
30643                         // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
30644                         var doc = document;
30645                         if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
30646                                 style.lineHeight = (rect.h - borderH) + 'px';
30647                         }
30648
30649                         borderBox = self._borderBox;
30650                         borderW = borderBox.left + borderBox.right + 8;
30651                         borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
30652
30653                         if (rect.x !== lastRepaintRect.x) {
30654                                 style.left = rect.x + 'px';
30655                                 lastRepaintRect.x = rect.x;
30656                         }
30657
30658                         if (rect.y !== lastRepaintRect.y) {
30659                                 style.top = rect.y + 'px';
30660                                 lastRepaintRect.y = rect.y;
30661                         }
30662
30663                         if (rect.w !== lastRepaintRect.w) {
30664                                 style.width = (rect.w - borderW) + 'px';
30665                                 lastRepaintRect.w = rect.w;
30666                         }
30667
30668                         if (rect.h !== lastRepaintRect.h) {
30669                                 style.height = (rect.h - borderH) + 'px';
30670                                 lastRepaintRect.h = rect.h;
30671                         }
30672
30673                         self._lastRepaintRect = lastRepaintRect;
30674                         self.fire('repaint', {}, false);
30675
30676                         return self;
30677                 },
30678
30679                 /**
30680                  * Renders the control as a HTML string.
30681                  *
30682                  * @method renderHtml
30683                  * @return {String} HTML representing the control.
30684                  */
30685                 renderHtml: function() {
30686                         var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = '';
30687
30688                         if ("spellcheck" in settings) {
30689                                 extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
30690                         }
30691
30692                         if (settings.maxLength) {
30693                                 extraAttrs += ' maxlength="' + settings.maxLength + '"';
30694                         }
30695
30696                         if (settings.size) {
30697                                 extraAttrs += ' size="' + settings.size + '"';
30698                         }
30699
30700                         if (settings.subtype) {
30701                                 extraAttrs += ' type="' + settings.subtype + '"';
30702                         }
30703
30704                         if (settings.multiline) {
30705                                 return (
30706                                         '<textarea id="' + id + '" class="' + self.classes() + '" ' +
30707                                         (settings.rows ? ' rows="' + settings.rows + '"' : '') +
30708                                         ' hidefocus="true"' + extraAttrs + '>' + value +
30709                                         '</textarea>'
30710                                 );
30711                         }
30712
30713                         return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="true"' + extraAttrs + '>';
30714                 },
30715
30716                 /**
30717                  * Called after the control has been rendered.
30718                  *
30719                  * @method postRender
30720                  */
30721                 postRender: function() {
30722                         var self = this;
30723
30724                         DomUtils.on(self.getEl(), 'change', function(e) {
30725                                 self.fire('change', e);
30726                         });
30727
30728                         return self._super();
30729                 }
30730         });
30731 });
30732
30733 // Included from: js/tinymce/classes/ui/Throbber.js
30734
30735 /**
30736  * Throbber.js
30737  *
30738  * Copyright, Moxiecode Systems AB
30739  * Released under LGPL License.
30740  *
30741  * License: http://www.tinymce.com/license
30742  * Contributing: http://www.tinymce.com/contributing
30743  */
30744
30745 /**
30746  * This class enables you to display a Throbber for any element.
30747  *
30748  * @-x-less Throbber.less
30749  * @class tinymce.ui.Throbber
30750  */
30751 define("tinymce/ui/Throbber", [
30752         "tinymce/ui/DomUtils"
30753 ], function(DomUtils) {
30754         "use strict";
30755
30756         /**
30757          * Constructs a new throbber.
30758          *
30759          * @constructor
30760          * @param {Element} elm DOM Html element to display throbber in.
30761          */
30762         return function(elm) {
30763                 var self = this, state;
30764
30765                 /**
30766                  * Shows the throbber.
30767                  *
30768                  * @method show
30769                  * @param {Number} [time] Time to wait before showing.
30770                  * @return {tinymce.ui.Throbber} Current throbber instance.
30771                  */
30772                 self.show = function(time) {
30773                         self.hide();
30774
30775                         state = true;
30776
30777                         window.setTimeout(function() {
30778                                 if (state) {
30779                                         elm.appendChild(DomUtils.createFragment('<div class="mce-throbber"></div>'));
30780                                 }
30781                         }, time || 0);
30782
30783                         return self;
30784                 };
30785
30786                 /**
30787                  * Hides the throbber.
30788                  *
30789                  * @method hide
30790                  * @return {tinymce.ui.Throbber} Current throbber instance.
30791                  */
30792                 self.hide = function() {
30793                         var child = elm.lastChild;
30794
30795                         if (child && child.className.indexOf('throbber') != -1) {
30796                                 child.parentNode.removeChild(child);
30797                         }
30798
30799                         state = false;
30800
30801                         return self;
30802                 };
30803         };
30804 });
30805
30806 expose(["tinymce/dom/Sizzle","tinymce/html/Styles","tinymce/dom/EventUtils","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","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/Selection","tinymce/dom/RangeUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/KeyboardNavigation","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","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/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/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"]);
30807 })(this);