diff --git a/wp-includes/class-wp-editor.php b/wp-includes/class-wp-editor.php index e4bf5a0890..572ac31182 100644 --- a/wp-includes/class-wp-editor.php +++ b/wp-includes/class-wp-editor.php @@ -1080,12 +1080,14 @@ final class _WP_Editors { \n"; } else { - echo "\n"; + echo "\n"; echo "\n"; } diff --git a/wp-includes/js/tinymce/tinymce.js b/wp-includes/js/tinymce/tinymce.js deleted file mode 100644 index f9186d6597..0000000000 --- a/wp-includes/js/tinymce/tinymce.js +++ /dev/null @@ -1,34989 +0,0 @@ -// 4.0.21.1 (2014-04-09) - -/** - * Compiled inline version. (Library mode) - */ - -/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ -/*globals $code */ - -(function(exports, undefined) { - "use strict"; - - var modules = {}; - - function require(ids, callback) { - var module, defs = []; - - for (var i = 0; i < ids.length; ++i) { - module = modules[ids[i]] || resolve(ids[i]); - if (!module) { - throw 'module definition dependecy not found: ' + ids[i]; - } - - defs.push(module); - } - - callback.apply(null, defs); - } - - function define(id, dependencies, definition) { - if (typeof id !== 'string') { - throw 'invalid module definition, module id must be defined and be a string'; - } - - if (dependencies === undefined) { - throw 'invalid module definition, dependencies must be specified'; - } - - if (definition === undefined) { - throw 'invalid module definition, definition function must be specified'; - } - - require(dependencies, function() { - modules[id] = definition.apply(null, arguments); - }); - } - - function defined(id) { - return !!modules[id]; - } - - function resolve(id) { - var target = exports; - var fragments = id.split(/[.\/]/); - - for (var fi = 0; fi < fragments.length; ++fi) { - if (!target[fragments[fi]]) { - return; - } - - target = target[fragments[fi]]; - } - - return target; - } - - function expose(ids) { - for (var i = 0; i < ids.length; i++) { - var target = exports; - var id = ids[i]; - var fragments = id.split(/[.\/]/); - - for (var fi = 0; fi < fragments.length - 1; ++fi) { - if (target[fragments[fi]] === undefined) { - target[fragments[fi]] = {}; - } - - target = target[fragments[fi]]; - } - - target[fragments[fragments.length - 1]] = modules[id]; - } - } - -// Included from: js/tinymce/classes/dom/EventUtils.js - -/** - * EventUtils.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*jshint loopfunc:true*/ -/*eslint no-loop-func:0 */ - -define("tinymce/dom/EventUtils", [], function() { - "use strict"; - - var eventExpandoPrefix = "mce-data-"; - var mouseEventRe = /^(?:mouse|contextmenu)|click/; - var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1}; - - /** - * Binds a native event to a callback on the speified target. - */ - function addEvent(target, name, callback, capture) { - if (target.addEventListener) { - target.addEventListener(name, callback, capture || false); - } else if (target.attachEvent) { - target.attachEvent('on' + name, callback); - } - } - - /** - * Unbinds a native event callback on the specified target. - */ - function removeEvent(target, name, callback, capture) { - if (target.removeEventListener) { - target.removeEventListener(name, callback, capture || false); - } else if (target.detachEvent) { - target.detachEvent('on' + name, callback); - } - } - - /** - * Normalizes a native event object or just adds the event specific methods on a custom event. - */ - function fix(originalEvent, data) { - var name, event = data || {}, undef; - - // Dummy function that gets replaced on the delegation state functions - function returnFalse() { - return false; - } - - // Dummy function that gets replaced on the delegation state functions - function returnTrue() { - return true; - } - - // Copy all properties from the original event - for (name in originalEvent) { - // layerX/layerY is deprecated in Chrome and produces a warning - if (!deprecated[name]) { - event[name] = originalEvent[name]; - } - } - - // Normalize target IE uses srcElement - if (!event.target) { - event.target = event.srcElement || document; - } - - // Calculate pageX/Y if missing and clientX/Y available - if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) { - var eventDoc = event.target.ownerDocument || document; - var doc = eventDoc.documentElement; - var body = eventDoc.body; - - event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - - ( doc && doc.clientLeft || body && body.clientLeft || 0); - - event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0 ) - - ( doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add preventDefault method - event.preventDefault = function() { - event.isDefaultPrevented = returnTrue; - - // Execute preventDefault on the original event object - if (originalEvent) { - if (originalEvent.preventDefault) { - originalEvent.preventDefault(); - } else { - originalEvent.returnValue = false; // IE - } - } - }; - - // Add stopPropagation - event.stopPropagation = function() { - event.isPropagationStopped = returnTrue; - - // Execute stopPropagation on the original event object - if (originalEvent) { - if (originalEvent.stopPropagation) { - originalEvent.stopPropagation(); - } else { - originalEvent.cancelBubble = true; // IE - } - } - }; - - // Add stopImmediatePropagation - event.stopImmediatePropagation = function() { - event.isImmediatePropagationStopped = returnTrue; - event.stopPropagation(); - }; - - // Add event delegation states - if (!event.isDefaultPrevented) { - event.isDefaultPrevented = returnFalse; - event.isPropagationStopped = returnFalse; - event.isImmediatePropagationStopped = returnFalse; - } - - return event; - } - - /** - * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized. - * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times. - */ - function bindOnReady(win, callback, eventUtils) { - var doc = win.document, event = {type: 'ready'}; - - if (eventUtils.domLoaded) { - callback(event); - return; - } - - // Gets called when the DOM is ready - function readyHandler() { - if (!eventUtils.domLoaded) { - eventUtils.domLoaded = true; - callback(event); - } - } - - function waitForDomLoaded() { - // Check complete or interactive state if there is a body - // element on some iframes IE 8 will produce a null body - if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) { - removeEvent(doc, "readystatechange", waitForDomLoaded); - readyHandler(); - } - } - - function tryScroll() { - try { - // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. - // http://javascript.nwbox.com/IEContentLoaded/ - doc.documentElement.doScroll("left"); - } catch (ex) { - setTimeout(tryScroll, 0); - return; - } - - readyHandler(); - } - - // Use W3C method - if (doc.addEventListener) { - if (doc.readyState === "complete") { - readyHandler(); - } else { - addEvent(win, 'DOMContentLoaded', readyHandler); - } - } else { - // Use IE method - addEvent(doc, "readystatechange", waitForDomLoaded); - - // Wait until we can scroll, when we can the DOM is initialized - if (doc.documentElement.doScroll && win.self === win.top) { - tryScroll(); - } - } - - // Fallback if any of the above methods should fail for some odd reason - addEvent(win, 'load', readyHandler); - } - - /** - * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers. - */ - function EventUtils() { - var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; - - expando = eventExpandoPrefix + (+new Date()).toString(32); - hasMouseEnterLeave = "onmouseenter" in document.documentElement; - hasFocusIn = "onfocusin" in document.documentElement; - mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; - count = 1; - - // State if the DOMContentLoaded was executed or not - self.domLoaded = false; - self.events = events; - - /** - * Executes all event handler callbacks for a specific event. - * - * @private - * @param {Event} evt Event object. - * @param {String} id Expando id value to look for. - */ - function executeHandlers(evt, id) { - var callbackList, i, l, callback, container = events[id]; - - callbackList = container && container[evt.type]; - if (callbackList) { - for (i = 0, l = callbackList.length; i < l; i++) { - callback = callbackList[i]; - - // Check if callback exists might be removed if a unbind is called inside the callback - if (callback && callback.func.call(callback.scope, evt) === false) { - evt.preventDefault(); - } - - // Should we stop propagation to immediate listeners - if (evt.isImmediatePropagationStopped()) { - return; - } - } - } - } - - /** - * Binds a callback to an event on the specified target. - * - * @method bind - * @param {Object} target Target node/window or custom object. - * @param {String} names Name of the event to bind. - * @param {function} callback Callback function to execute when the event occurs. - * @param {Object} scope Scope to call the callback function on, defaults to target. - * @return {function} Callback function that got bound. - */ - self.bind = function(target, names, callback, scope) { - var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; - - // Native event handler function patches the event and executes the callbacks for the expando - function defaultNativeHandler(evt) { - executeHandlers(fix(evt || win.event), id); - } - - // Don't bind to text nodes or comments - if (!target || target.nodeType === 3 || target.nodeType === 8) { - return; - } - - // Create or get events id for the target - if (!target[expando]) { - id = count++; - target[expando] = id; - events[id] = {}; - } else { - id = target[expando]; - } - - // Setup the specified scope or use the target as a default - scope = scope || target; - - // Split names and bind each event, enables you to bind multiple events with one call - names = names.split(' '); - i = names.length; - while (i--) { - name = names[i]; - nativeHandler = defaultNativeHandler; - fakeName = capture = false; - - // Use ready instead of DOMContentLoaded - if (name === "DOMContentLoaded") { - name = "ready"; - } - - // DOM is already ready - if (self.domLoaded && name === "ready" && target.readyState == 'complete') { - callback.call(scope, fix({type: name})); - continue; - } - - // Handle mouseenter/mouseleaver - if (!hasMouseEnterLeave) { - fakeName = mouseEnterLeave[name]; - - if (fakeName) { - nativeHandler = function(evt) { - var current, related; - - current = evt.currentTarget; - related = evt.relatedTarget; - - // Check if related is inside the current target if it's not then the event should - // be ignored since it's a mouseover/mouseout inside the element - if (related && current.contains) { - // Use contains for performance - related = current.contains(related); - } else { - while (related && related !== current) { - related = related.parentNode; - } - } - - // Fire fake event - if (!related) { - evt = fix(evt || win.event); - evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; - evt.target = current; - executeHandlers(evt, id); - } - }; - } - } - - // Fake bubbeling of focusin/focusout - if (!hasFocusIn && (name === "focusin" || name === "focusout")) { - capture = true; - fakeName = name === "focusin" ? "focus" : "blur"; - nativeHandler = function(evt) { - evt = fix(evt || win.event); - evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; - executeHandlers(evt, id); - }; - } - - // Setup callback list and bind native event - callbackList = events[id][name]; - if (!callbackList) { - events[id][name] = callbackList = [{func: callback, scope: scope}]; - callbackList.fakeName = fakeName; - callbackList.capture = capture; - - // Add the nativeHandler to the callback list so that we can later unbind it - callbackList.nativeHandler = nativeHandler; - - // Check if the target has native events support - - if (name === "ready") { - bindOnReady(target, nativeHandler, self); - } else { - addEvent(target, fakeName || name, nativeHandler, capture); - } - } else { - if (name === "ready" && self.domLoaded) { - callback({type: name}); - } else { - // If it already has an native handler then just push the callback - callbackList.push({func: callback, scope: scope}); - } - } - } - - target = callbackList = 0; // Clean memory for IE - - return callback; - }; - - /** - * Unbinds the specified event by name, name and callback or all events on the target. - * - * @method unbind - * @param {Object} target Target node/window or custom object. - * @param {String} names Optional event name to unbind. - * @param {function} callback Optional callback function to unbind. - * @return {EventUtils} Event utils instance. - */ - self.unbind = function(target, names, callback) { - var id, callbackList, i, ci, name, eventMap; - - // Don't bind to text nodes or comments - if (!target || target.nodeType === 3 || target.nodeType === 8) { - return self; - } - - // Unbind event or events if the target has the expando - id = target[expando]; - if (id) { - eventMap = events[id]; - - // Specific callback - if (names) { - names = names.split(' '); - i = names.length; - while (i--) { - name = names[i]; - callbackList = eventMap[name]; - - // Unbind the event if it exists in the map - if (callbackList) { - // Remove specified callback - if (callback) { - ci = callbackList.length; - while (ci--) { - if (callbackList[ci].func === callback) { - var nativeHandler = callbackList.nativeHandler; - var fakeName = callbackList.fakeName, capture = callbackList.capture; - - // Clone callbackList since unbind inside a callback would otherwise break the handlers loop - callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1)); - callbackList.nativeHandler = nativeHandler; - callbackList.fakeName = fakeName; - callbackList.capture = capture; - - eventMap[name] = callbackList; - } - } - } - - // Remove all callbacks if there isn't a specified callback or there is no callbacks left - if (!callback || callbackList.length === 0) { - delete eventMap[name]; - removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); - } - } - } - } else { - // All events for a specific element - for (name in eventMap) { - callbackList = eventMap[name]; - removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); - } - - eventMap = {}; - } - - // Check if object is empty, if it isn't then we won't remove the expando map - for (name in eventMap) { - return self; - } - - // Delete event object - delete events[id]; - - // Remove expando from target - try { - // IE will fail here since it can't delete properties from window - delete target[expando]; - } catch (ex) { - // IE will set it to null - target[expando] = null; - } - } - - return self; - }; - - /** - * Fires the specified event on the specified target. - * - * @method fire - * @param {Object} target Target node/window or custom object. - * @param {String} name Event name to fire. - * @param {Object} args Optional arguments to send to the observers. - * @return {EventUtils} Event utils instance. - */ - self.fire = function(target, name, args) { - var id; - - // Don't bind to text nodes or comments - if (!target || target.nodeType === 3 || target.nodeType === 8) { - return self; - } - - // Build event object by patching the args - args = fix(null, args); - args.type = name; - args.target = target; - - do { - // Found an expando that means there is listeners to execute - id = target[expando]; - if (id) { - executeHandlers(args, id); - } - - // Walk up the DOM - target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; - } while (target && !args.isPropagationStopped()); - - return self; - }; - - /** - * Removes all bound event listeners for the specified target. This will also remove any bound - * listeners to child nodes within that target. - * - * @method clean - * @param {Object} target Target node/window object. - * @return {EventUtils} Event utils instance. - */ - self.clean = function(target) { - var i, children, unbind = self.unbind; - - // Don't bind to text nodes or comments - if (!target || target.nodeType === 3 || target.nodeType === 8) { - return self; - } - - // Unbind any element on the specificed target - if (target[expando]) { - unbind(target); - } - - // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children - if (!target.getElementsByTagName) { - target = target.document; - } - - // Remove events from each child element - if (target && target.getElementsByTagName) { - unbind(target); - - children = target.getElementsByTagName('*'); - i = children.length; - while (i--) { - target = children[i]; - - if (target[expando]) { - unbind(target); - } - } - } - - return self; - }; - - /** - * Destroys the event object. Call this on IE to remove memory leaks. - */ - self.destroy = function() { - events = {}; - }; - - // Legacy function for canceling events - self.cancel = function(e) { - if (e) { - e.preventDefault(); - e.stopImmediatePropagation(); - } - - return false; - }; - } - - EventUtils.Event = new EventUtils(); - EventUtils.Event.bind(window, 'ready', function() {}); - - return EventUtils; -}); - -// Included from: js/tinymce/classes/dom/Sizzle.js - -/** - * Sizzle.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - * - * @ignore-file - */ - -/*jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */ -/*eslint dot-notation:0, no-empty:0, no-cond-assign:0, no-unused-expressions:0, new-cap:0, no-nested-ternary:0, func-style:0, no-bitwise: 0 */ - -/* - * Sizzle CSS Selector Engine - * Copyright, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -define("tinymce/dom/Sizzle", [], function() { -var i, - cachedruns, - Expr, - getText, - isXML, - compile, - outermostContext, - recompare, - sortInput, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + -(new Date()), - preferredDoc = window.document, - support = {}, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - hasDuplicate = false, - sortOrder = function() { return 0; }, - - // General-purpose constants - strundefined = typeof undefined, - MAX_NEGATIVE = 1 << 31, - - // Array methods - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf if we can't use a native one - indexOf = arr.indexOf || function( elem ) { - var i = 0, - len = this.length; - for ( ; i < len; i++ ) { - if ( this[i] === elem ) { - return i; - } - } - return -1; - }, - - - // Regular expressions - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace( "w", "w#" ), - - // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors - operators = "([*^$|!~]?=)", - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + - "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", - - // Prefer arguments quoted, - // then not containing pseudos/brackets, - // then attribute selectors/non-parenthetical expressions, - // then anything else - // These preferences are here to reduce the number of selectors - // needing tokenize in the PSEUDO preFilter - pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + characterEncoding + ")" ), - "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), - "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), - "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rsibling = /[\x20\t\r\n\f]*[+~]/, - - rnative = /^[^{]+\{\s*\[native code/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rescape = /'|\\/g, - rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, - funescape = function( _, escaped ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - return high !== high ? - escaped : - // BMP codepoint - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }; - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -/** - * For feature detection - * @param {Function} fn The function to test for native support - */ -function isNative( fn ) { - return rnative.test( fn + "" ); -} - -/** - * Create key-value caches of limited size - * @returns {Function(string, Object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var cache, - keys = []; - - cache = function( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key += " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - cache[ key ] = value; - return value; - }; - - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return !!fn( div ); - } catch (e) { - return false; - } finally { - // release memory in IE - div = null; - } -} - -function Sizzle( selector, context, results, seed ) { - var match, elem, m, nodeType, - // QSA vars - i, groups, old, nid, newContext, newSelector; - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - - context = context || document; - results = results || []; - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { - return []; - } - - if ( documentIsHTML && !seed ) { - - // Shortcuts - if ( (match = rquickExpr.exec( selector )) ) { - // Speed-up: Sizzle("#ID") - if ( (m = match[1]) ) { - if ( nodeType === 9 ) { - elem = context.getElementById( m ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && - contains( context, elem ) && elem.id === m ) { - results.push( elem ); - return results; - } - } - - // Speed-up: Sizzle("TAG") - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // QSA path - if ( support.qsa && !rbuggyQSA.test(selector) ) { - old = true; - nid = expando; - newContext = context; - newSelector = nodeType === 9 && selector; - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - groups = tokenize( selector ); - - if ( (old = context.getAttribute("id")) ) { - nid = old.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", nid ); - } - nid = "[id='" + nid + "'] "; - - i = groups.length; - while ( i-- ) { - groups[i] = nid + toSelector( groups[i] ); - } - newContext = rsibling.test( selector ) && context.parentNode || context; - newSelector = groups.join(","); - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch(qsaError) { - } finally { - if ( !old ) { - context.removeAttribute("id"); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Detect xml - * @param {Element|Object} elem An element or a document - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var doc = node ? node.ownerDocument || node : preferredDoc; - - // If no document and documentElement is available, return - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Set our document - document = doc; - docElem = doc.documentElement; - - // Support tests - documentIsHTML = !isXML( doc ); - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( div ) { - div.appendChild( doc.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Check if attributes should be retrieved by attribute nodes - support.attributes = assert(function( div ) { - div.innerHTML = ""; - var type = typeof div.lastChild.getAttribute("multiple"); - // IE8 returns a string for some attributes even when not present - return type !== "boolean" && type !== "string"; - }); - - // Check if getElementsByClassName can be trusted - support.getElementsByClassName = assert(function( div ) { - // Opera can't find a second classname (in 9.6) - div.innerHTML = "
"; - if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { - return false; - } - - // Safari 3.2 caches class attributes and doesn't catch changes - div.lastChild.className = "e"; - return div.getElementsByClassName("e").length === 2; - }); - - // Check if getElementsByName privileges form controls or returns elements by ID - // If so, assume (for broader support) that getElementById returns elements by name - support.getByName = assert(function( div ) { - // Inject content - div.id = expando + 0; - // Support: Windows 8 Native Apps - // Assigning innerHTML with "name" attributes throws uncatchable exceptions - // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx - div.appendChild( document.createElement("a") ).setAttribute( "name", expando ); - div.appendChild( document.createElement("i") ).setAttribute( "name", expando ); - docElem.appendChild( div ); - - // Test - var pass = doc.getElementsByName && - // buggy browsers will return fewer than the correct 2 - doc.getElementsByName( expando ).length === 2 + - // buggy browsers will return more than the correct 0 - doc.getElementsByName( expando + 0 ).length; - - // Cleanup - docElem.removeChild( div ); - - return pass; - }); - - // Support: Webkit<537.32 - // Detached nodes confoundingly follow *each other* - support.sortDetached = assert(function( div1 ) { - return div1.compareDocumentPosition && - // Should return 1, but Webkit returns 4 (following) - (div1.compareDocumentPosition( document.createElement("div") ) & 1); - }); - - // IE6/7 return modified attributes - Expr.attrHandle = assert(function( div ) { - div.innerHTML = ""; - return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && - div.firstChild.getAttribute("href") === "#"; - }) ? - {} : - { - "href": function( elem ) { - return elem.getAttribute( "href", 2 ); - }, - "type": function( elem ) { - return elem.getAttribute("type"); - } - }; - - // ID find and filter - if ( support.getByName ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && documentIsHTML ) { - var m = context.getElementById( id ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && documentIsHTML ) { - var m = context.getElementById( id ); - - return m ? - m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? - [m] : - undefined : - []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== strundefined ) { - return context.getElementsByTagName( tag ); - } - } : - function( tag, context ) { - var elem, - tmp = [], - i = 0, - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Name - Expr.find["NAME"] = support.getByName && function( tag, context ) { - if ( typeof context.getElementsByName !== strundefined ) { - return context.getElementsByName( name ); - } - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21), - // no need to also add to buggyMatches since matches checks buggyQSA - // A support test would require too much code (would include document ready) - rbuggyQSA = [ ":focus" ]; - - if ( (support.qsa = isNative(doc.querySelectorAll)) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; - - // IE8 - Some boolean attributes are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - }); - - assert(function( div ) { - - // Opera 10-12/IE8 - ^= $= *= and empty values - // Should not select anything - div.innerHTML = ""; - if ( div.querySelectorAll("[i^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || - docElem.mozMatchesSelector || - docElem.webkitMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - // Element contains another - // Purposefully does not implement inclusive descendant - // As in, an element does not contain itself - contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - // Document order sorting - sortOrder = docElem.compareDocumentPosition ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); - - if ( compare ) { - // Disconnected nodes - if ( compare & 1 || - (recompare && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === doc || contains(preferredDoc, a) ) { - return -1; - } - if ( b === doc || contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } - - // Not directly comparable, sort on existence of method - return a.compareDocumentPosition ? -1 : 1; - } : - function( a, b ) { - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Parentless nodes are either documents or disconnected - } else if ( !aup || !bup ) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - // rbuggyQSA always contains :focus, so no need for an existence check - if ( support.matchesSelector && documentIsHTML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch(e) {} - } - - return Sizzle( expr, document, null, [elem] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - var val; - - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - if ( documentIsHTML ) { - name = name.toLowerCase(); - } - if ( (val = Expr.attrHandle[ name ]) ) { - return val( elem ); - } - if ( !documentIsHTML || support.attributes ) { - return elem.getAttribute( name ); - } - return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? - name : - val && val.specified ? val.value : null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -// Document sorting and removing duplicates -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - // Compensate for sort limitations - recompare = !support.sortDetached; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - return results; -}; - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns Returns -1 if a precedes b, 1 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -// Returns a function to use in pseudos for input types -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -// Returns a function to use in pseudos for buttons -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -// Returns a function to use in pseudos for positionals -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - for ( ; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (see #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[5] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[4] ) { - match[2] = match[4]; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeName ) { - if ( nodeName === "*" ) { - return function() { return true; }; - } - - nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - // Use previously-cached element index if available - } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { - diff = cache[1]; - - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { - // Cache the index of each encountered element - if ( useCache ) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), - // not comment, processing instructions, or others - // Thanks to Diego Perini for the nodeName shortcut - // Greater than "@" means alpha characters (specifically not starting with "#" or "?") - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( tokens = [] ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push( { - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -} - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var data, cache, outerCache, - dirkey = dirruns + " " + doneName; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { - if ( (data = cache[1]) === true || data === cachedruns ) { - return data === true; - } - } else { - cache = outerCache[ dir ] = [ dirkey ]; - cache[1] = matcher( elem, context, xml ) || cachedruns; - if ( cache[1] === true ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - // A counter to specify which element is currently being matched - var matcherCachedRuns = 0, - bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, expandContext ) { - var elem, j, matcher, - setMatched = [], - matchedCount = 0, - i = "0", - unmatched = seed && [], - outermost = expandContext != null, - contextBackup = outermostContext, - // We must always have either seed elements or context - elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); - - if ( outermost ) { - outermostContext = context !== document && context; - cachedruns = matcherCachedRuns; - } - - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - for ( ; (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - cachedruns = ++matcherCachedRuns; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !group ) { - group = tokenize( selector ); - } - i = group.length; - while ( i-- ) { - cached = matcherFromTokens( group[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - } - return cached; -}; - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function select( selector, context, results, seed ) { - var i, tokens, token, type, find, - match = tokenize( selector ); - - if ( !seed ) { - // Try to minimize operations if there is only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && context.parentNode || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - } - - // Compile and execute a filtering function - // Provide `match` to avoid retokenization if we modified the selector above - compile( selector, match )( - seed, - context, - !documentIsHTML, - results, - rsibling.test( selector ) - ); - return results; -} - -// Deprecated -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -// Check sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Initialize with the default document -setDocument(); - -// Always assume the presence of duplicates if sort doesn't -// pass them to our comparison function (as in Google Chrome). -[0, 0].sort( sortOrder ); -support.detectDuplicates = hasDuplicate; - -/* -// EXPOSE -if ( typeof define === "function" && define.amd ) { - define(function() { return Sizzle; }); -} else { - window.Sizzle = Sizzle; -} -*/ - -// EXPOSE -return Sizzle; -}); - -// Included from: js/tinymce/classes/dom/DomQuery.js - -/** - * DomQuery.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - * - * Some of this logic is based on jQuery code that is released under - * MIT license that grants us to sublicense it under LGPL. - * - * @ignore-file - */ - -/** - * @class tinymce.dom.DomQuery - */ -define("tinymce/dom/DomQuery", [ - "tinymce/dom/EventUtils", - "tinymce/dom/Sizzle" -], function(EventUtils, Sizzle) { - var doc = document, push = Array.prototype.push, slice = Array.prototype.slice; - var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/; - var Event = EventUtils.Event; - - function isDefined(obj) { - return typeof obj !== "undefined"; - } - - function isString(obj) { - return typeof obj === "string"; - } - - function createFragment(html) { - var frag, node, container; - - container = doc.createElement("div"); - frag = doc.createDocumentFragment(); - container.innerHTML = html; - - while ((node = container.firstChild)) { - frag.appendChild(node); - } - - return frag; - } - - function domManipulate(targetNodes, sourceItem, callback) { - var i; - - if (typeof sourceItem === "string") { - sourceItem = createFragment(sourceItem); - } else if (sourceItem.length) { - for (i = 0; i < sourceItem.length; i++) { - domManipulate(targetNodes, sourceItem[i], callback); - } - - return targetNodes; - } - - i = targetNodes.length; - while (i--) { - callback.call(targetNodes[i], sourceItem.parentNode ? sourceItem : sourceItem); - } - - return targetNodes; - } - - function hasClass(node, className) { - return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1; - } - - /** - * Makes a map object out of a string that gets separated by a delimiter. - * - * @method makeMap - * @param {String} items Item string to split. - * @param {Object} map Optional object to add items to. - * @return {Object} name/value object with items as keys. - */ - function makeMap(items, map) { - var i; - - items = items || []; - - if (typeof(items) == "string") { - items = items.split(' '); - } - - map = map || {}; - - i = items.length; - while (i--) { - map[items[i]] = {}; - } - - return map; - } - - var numericCssMap = makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom'); - - function DomQuery(selector, context) { - /*eslint new-cap:0 */ - return new DomQuery.fn.init(selector, context); - } - - /** - * Extends the specified object with another object. - * - * @method extend - * @param {Object} target Object to extend. - * @param {Object..} obj Multiple objects to extend with. - * @return {Object} Same as target, the extended object. - */ - function extend(target) { - var args = arguments, arg, i, key; - - for (i = 1; i < args.length; i++) { - arg = args[i]; - - for (key in arg) { - target[key] = arg[key]; - } - } - - return target; - } - - /** - * Converts the specified object into a real JavaScript array. - * - * @method toArray - * @param {Object} obj Object to convert into array. - * @return {Array} Array object based in input. - */ - function toArray(obj) { - var array = [], i, l; - - for (i = 0, l = obj.length; i < l; i++) { - array[i] = obj[i]; - } - - return array; - } - - /** - * Returns the index of the specified item inside the array. - * - * @method inArray - * @param {Object} item Item to look for. - * @param {Array} array Array to look for item in. - * @return {Number} Index of the item or -1. - */ - function inArray(item, array) { - var i; - - if (array.indexOf) { - return array.indexOf(item); - } - - i = array.length; - while (i--) { - if (array[i] === item) { - return i; - } - } - - return -1; - } - - /** - * Returns true/false if the specified object is an array. - * - * @method isArray - * @param {Object} obj Object to check if it's an array. - * @return {Boolean} true/false if the input object is array or not. - */ - var isArray = Array.isArray || function(obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; - - var whiteSpaceRegExp = /^\s*|\s*$/g; - - function trim(str) { - return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); - } - - /** - * Executes the callback function for each item in array/object. If you return false in the - * callback it will break the loop. - * - * @method each - * @param {Object} obj Object to iterate. - * @param {function} callback Callback function to execute for each item. - */ - function each(obj, callback) { - var length, key, i, undef, value; - - if (obj) { - length = obj.length; - - if (length === undef) { - // Loop object items - for (key in obj) { - if (obj.hasOwnProperty(key)) { - value = obj[key]; - if (callback.call(value, value, key) === false) { - break; - } - } - } - } else { - // Loop array items - for (i = 0; i < length; i++) { - value = obj[i]; - if (callback.call(value, value, key) === false) { - break; - } - } - } - } - - return obj; - } - - DomQuery.fn = DomQuery.prototype = { - constructor: DomQuery, - selector: "", - length: 0, - - init: function(selector, context) { - var self = this, match, node; - - if (!selector) { - return self; - } - - if (selector.nodeType) { - self.context = self[0] = selector; - self.length = 1; - - return self; - } - - if (isString(selector)) { - if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { - match = [null, selector, null]; - } else { - match = rquickExpr.exec(selector); - } - - if (match) { - if (match[1]) { - node = createFragment(selector).firstChild; - while (node) { - this.add(node); - node = node.nextSibling; - } - } else { - node = doc.getElementById(match[2]); - - if (node.id !== match[2]) { - return self.find(selector); - } - - self.length = 1; - self[0] = node; - } - } else { - return DomQuery(context || document).find(selector); - } - } else { - this.add(selector); - } - - return self; - }, - - toArray: function() { - return toArray(this); - }, - - add: function(items) { - var self = this; - - // Force single item into array - if (!isArray(items)) { - if (items instanceof DomQuery) { - self.add(items.toArray()); - } else { - push.call(self, items); - } - } else { - push.apply(self, items); - } - - return self; - }, - - attr: function(name, value) { - var self = this; - - if (typeof name === "object") { - each(name, function(value, name) { - self.attr(name, value); - }); - } else if (isDefined(value)) { - this.each(function() { - if (this.nodeType === 1) { - this.setAttribute(name, value); - } - }); - } else { - return self[0] && self[0].nodeType === 1 ? self[0].getAttribute(name) : undefined; - } - - return self; - }, - - css: function(name, value) { - var self = this; - - if (typeof name === "object") { - each(name, function(value, name) { - self.css(name, value); - }); - } else { - // Camelcase it, if needed - name = name.replace(/-(\D)/g, function(a, b) { - return b.toUpperCase(); - }); - - if (isDefined(value)) { - // Default px suffix on these - if (typeof(value) === 'number' && !numericCssMap[name]) { - value += 'px'; - } - - self.each(function() { - var style = this.style; - - // IE specific opacity - if (name === "opacity" && this.runtimeStyle && typeof(this.runtimeStyle.opacity) === "undefined") { - style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")"; - } - - try { - style[name] = value; - } catch (ex) { - // Ignore - } - }); - } else { - return self[0] ? self[0].style[name] : undefined; - } - } - - return self; - }, - - remove: function() { - var self = this, node, i = this.length; - - while (i--) { - node = self[i]; - Event.clean(node); - - if (node.parentNode) { - node.parentNode.removeChild(node); - } - } - - return this; - }, - - empty: function() { - var self = this, node, i = this.length; - - while (i--) { - node = self[i]; - while (node.firstChild) { - node.removeChild(node.firstChild); - } - } - - return this; - }, - - html: function(value) { - var self = this, i; - - if (isDefined(value)) { - i = self.length; - while (i--) { - self[i].innerHTML = value; - } - - return self; - } - - return self[0] ? self[0].innerHTML : ''; - }, - - text: function(value) { - var self = this, i; - - if (isDefined(value)) { - i = self.length; - while (i--) { - self[i].innerText = self[0].textContent = value; - } - - return self; - } - - return self[0] ? self[0].innerText || self[0].textContent : ''; - }, - - append: function() { - return domManipulate(this, arguments, function(node) { - if (this.nodeType === 1) { - this.appendChild(node); - } - }); - }, - - prepend: function() { - return domManipulate(this, arguments, function(node) { - if (this.nodeType === 1) { - this.insertBefore(node, this.firstChild); - } - }); - }, - - before: function() { - var self = this; - - if (self[0] && self[0].parentNode) { - return domManipulate(self, arguments, function(node) { - this.parentNode.insertBefore(node, this.nextSibling); - }); - } - - return self; - }, - - after: function() { - var self = this; - - if (self[0] && self[0].parentNode) { - return domManipulate(self, arguments, function(node) { - this.parentNode.insertBefore(node, this); - }); - } - - return self; - }, - - appendTo: function(val) { - DomQuery(val).append(this); - - return this; - }, - - addClass: function(className) { - return this.toggleClass(className, true); - }, - - removeClass: function(className) { - return this.toggleClass(className, false); - }, - - toggleClass: function(className, state) { - var self = this; - - if (className.indexOf(' ') !== -1) { - each(className.split(' '), function() { - self.toggleClass(this, state); - }); - } else { - self.each(function(node) { - var existingClassName; - - if (hasClass(node, className) !== state) { - existingClassName = node.className; - - if (state) { - node.className += existingClassName ? ' ' + className : className; - } else { - node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' ')); - } - } - }); - } - - return self; - }, - - hasClass: function(className) { - return hasClass(this[0], className); - }, - - each: function(callback) { - return each(this, callback); - }, - - on: function(name, callback) { - return this.each(function() { - Event.bind(this, name, callback); - }); - }, - - off: function(name, callback) { - return this.each(function() { - Event.unbind(this, name, callback); - }); - }, - - show: function() { - return this.css('display', ''); - }, - - hide: function() { - return this.css('display', 'none'); - }, - - slice: function() { - return new DomQuery(slice.apply(this, arguments)); - }, - - eq: function(index) { - return index === -1 ? this.slice(index) : this.slice(index, +index + 1); - }, - - first: function() { - return this.eq(0); - }, - - last: function() { - return this.eq(-1); - }, - - replaceWith: function(content) { - var self = this; - - if (self[0]) { - self[0].parentNode.replaceChild(DomQuery(content)[0], self[0]); - } - - return self; - }, - - wrap: function(wrapper) { - wrapper = DomQuery(wrapper)[0]; - - return this.each(function() { - var self = this, newWrapper = wrapper.cloneNode(false); - self.parentNode.insertBefore(newWrapper, self); - newWrapper.appendChild(self); - }); - }, - - unwrap: function() { - return this.each(function() { - var self = this, node = self.firstChild, currentNode; - - while (node) { - currentNode = node; - node = node.nextSibling; - self.parentNode.insertBefore(currentNode, self); - } - }); - }, - - clone: function() { - var result = []; - - this.each(function() { - result.push(this.cloneNode(true)); - }); - - return DomQuery(result); - }, - - find: function(selector) { - var i, l, ret = []; - - for (i = 0, l = this.length; i < l; i++) { - DomQuery.find(selector, this[i], ret); - } - - return DomQuery(ret); - }, - - push: push, - sort: [].sort, - splice: [].splice - }; - - // Static members - extend(DomQuery, { - extend: extend, - toArray: toArray, - inArray: inArray, - isArray: isArray, - each: each, - trim: trim, - makeMap: makeMap, - - // Sizzle - find: Sizzle, - expr: Sizzle.selectors, - unique: Sizzle.uniqueSort, - text: Sizzle.getText, - isXMLDoc: Sizzle.isXML, - contains: Sizzle.contains, - filter: function(expr, elems, not) { - if (not) { - expr = ":not(" + expr + ")"; - } - - if (elems.length === 1) { - elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : []; - } else { - elems = DomQuery.find.matches(expr, elems); - } - - return elems; - } - }); - - function dir(el, prop, until) { - var matched = [], cur = el[prop]; - - while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !DomQuery(cur).is(until))) { - if (cur.nodeType === 1) { - matched.push(cur); - } - - cur = cur[prop]; - } - - return matched; - } - - function sibling(n, el, siblingName, nodeType) { - var r = []; - - for(; n; n = n[siblingName]) { - if ((!nodeType || n.nodeType === nodeType) && n !== el) { - r.push(n); - } - } - - return r; - } - - each({ - parent: function(node) { - var parent = node.parentNode; - - return parent && parent.nodeType !== 11 ? parent : null; - }, - - parents: function(node) { - return dir(node, "parentNode"); - }, - - parentsUntil: function(node, until) { - return dir(node, "parentNode", until); - }, - - next: function(node) { - return sibling(node, 'nextSibling', 1); - }, - - prev: function(node) { - return sibling(node, 'previousSibling', 1); - }, - - nextNodes: function(node) { - return sibling(node, 'nextSibling'); - }, - - prevNodes: function(node) { - return sibling(node, 'previousSibling'); - }, - - children: function(node) { - return sibling(node.firstChild, 'nextSibling', 1); - }, - - contents: function(node) { - return toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes); - } - }, function(name, fn){ - DomQuery.fn[name] = function(selector) { - var self = this, result; - - if (self.length > 1) { - throw new Error("DomQuery only supports traverse functions on a single node."); - } - - if (self[0]) { - result = fn(self[0], selector); - } - - result = DomQuery(result); - - if (selector && name !== "parentsUntil") { - return result.filter(selector); - } - - return result; - }; - }); - - DomQuery.fn.filter = function(selector) { - return DomQuery.filter(selector); - }; - - DomQuery.fn.is = function(selector) { - return !!selector && this.filter(selector).length > 0; - }; - - DomQuery.fn.init.prototype = DomQuery.fn; - - return DomQuery; -}); - -// Included from: js/tinymce/classes/html/Styles.js - -/** - * Styles.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class is used to parse CSS styles it also compresses styles to reduce the output size. - * - * @example - * var Styles = new tinymce.html.Styles({ - * url_converter: function(url) { - * return url; - * } - * }); - * - * styles = Styles.parse('border: 1px solid red'); - * styles.color = 'red'; - * - * console.log(new tinymce.html.StyleSerializer().serialize(styles)); - * - * @class tinymce.html.Styles - * @version 3.4 - */ -define("tinymce/html/Styles", [], function() { - return function(settings, schema) { - /*jshint maxlen:255 */ - /*eslint max-len:0 */ - var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, - urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, - styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, - trimRightRegExp = /\s+$/, - undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF'; - - settings = settings || {}; - - encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' '); - for (i = 0; i < encodingItems.length; i++) { - encodingLookup[encodingItems[i]] = invisibleChar + i; - encodingLookup[invisibleChar + i] = encodingItems[i]; - } - - function toHex(match, r, g, b) { - function hex(val) { - val = parseInt(val, 10).toString(16); - - return val.length > 1 ? val : '0' + val; // 0 -> 00 - } - - return '#' + hex(r) + hex(g) + hex(b); - } - - return { - /** - * Parses the specified RGB color value and returns a hex version of that color. - * - * @method toHex - * @param {String} color RGB string value like rgb(1,2,3) - * @return {String} Hex version of that RGB value like #FF00FF. - */ - toHex: function(color) { - return color.replace(rgbRegExp, toHex); - }, - - /** - * Parses the specified style value into an object collection. This parser will also - * merge and remove any redundant items that browsers might have added. It will also convert non hex - * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. - * - * @method parse - * @param {String} css Style value to parse for example: border:1px solid red;. - * @return {Object} Object representation of that style like {border: '1px solid red'} - */ - parse: function(css) { - var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter; - var urlConverterScope = settings.url_converter_scope || this; - - function compress(prefix, suffix, noJoin) { - var top, right, bottom, left; - - top = styles[prefix + '-top' + suffix]; - if (!top) { - return; - } - - right = styles[prefix + '-right' + suffix]; - if (!right) { - return; - } - - bottom = styles[prefix + '-bottom' + suffix]; - if (!bottom) { - return; - } - - left = styles[prefix + '-left' + suffix]; - if (!left) { - return; - } - - var box = [top, right, bottom, left]; - i = box.length - 1; - while (i--) { - if (box[i] !== box[i + 1]) { - break; - } - } - - if (i > -1 && noJoin) { - return; - } - - styles[prefix + suffix] = i == -1 ? box[0] : box.join(' '); - delete styles[prefix + '-top' + suffix]; - delete styles[prefix + '-right' + suffix]; - delete styles[prefix + '-bottom' + suffix]; - delete styles[prefix + '-left' + suffix]; - } - - /** - * Checks if the specific style can be compressed in other words if all border-width are equal. - */ - function canCompress(key) { - var value = styles[key], i; - - if (!value) { - return; - } - - value = value.split(' '); - i = value.length; - while (i--) { - if (value[i] !== value[0]) { - return false; - } - } - - styles[key] = value[0]; - - return true; - } - - /** - * Compresses multiple styles into one style. - */ - function compress2(target, a, b, c) { - if (!canCompress(a)) { - return; - } - - if (!canCompress(b)) { - return; - } - - if (!canCompress(c)) { - return; - } - - // Compress - styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; - delete styles[a]; - delete styles[b]; - delete styles[c]; - } - - // Encodes the specified string by replacing all \" \' ; : with _abcabc123
would produceabc
abc123
. - * - * @method split - * @param {Element} parentElm Parent element to split. - * @param {Element} splitElm Element to split at. - * @param {Element} replacementElm Optional replacement element to replace the split element with. - * @return {Element} Returns the split element or the replacement element if that is specified. - */ - split: function(parentElm, splitElm, replacementElm) { - var self = this, r = self.createRng(), bef, aft, pa; - - // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense - // but we don't want that in our code since it serves no purpose for the end user - // For example splitting this html at the bold element: - //text 1CHOPtext 2
- // would produce: - //text 1
CHOPtext 2
- // this function will then trim off empty edges and produce: - //text 1
CHOPtext 2
- function trimNode(node) { - var i, children = node.childNodes, type = node.nodeType; - - function surroundedBySpans(node) { - var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; - var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; - return previousIsSpan && nextIsSpan; - } - - if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') { - return; - } - - for (i = children.length - 1; i >= 0; i--) { - trimNode(children[i]); - } - - if (type != 9) { - // Keep non whitespace text nodes - if (type == 3 && node.nodeValue.length > 0) { - // If parent element isn't a block or there isn't any useful contents for example "" - // Also keep text nodes with only spaces if surrounded by spans. - // eg. "
a b
" should keep space between a and b - var trimmedLength = trim(node.nodeValue).length; - if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) { - return; - } - } else if (type == 1) { - // If the only child is a bookmark then move it up - children = node.childNodes; - - // TODO fix this complex if - if (children.length == 1 && children[0] && children[0].nodeType == 1 && - children[0].getAttribute('data-mce-type') == 'bookmark') { - node.parentNode.insertBefore(children[0], node); - } - - // Keep non empty elements or img, hr etc - if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) { - return; - } - } - - self.remove(node); - } - - return node; - } - - if (parentElm && splitElm) { - // Get before chunk - r.setStart(parentElm.parentNode, self.nodeIndex(parentElm)); - r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm)); - bef = r.extractContents(); - - // Get after chunk - r = self.createRng(); - r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1); - r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1); - aft = r.extractContents(); - - // Insert before chunk - pa = parentElm.parentNode; - pa.insertBefore(trimNode(bef), parentElm); - - // Insert middle chunk - if (replacementElm) { - pa.replaceChild(replacementElm, splitElm); - } else { - pa.insertBefore(splitElm, parentElm); - } - - // Insert after chunk - pa.insertBefore(trimNode(aft), parentElm); - self.remove(parentElm); - - return replacementElm || splitElm; - } - }, - - /** - * Adds an event handler to the specified object. - * - * @method bind - * @param {Element/Document/Window/Array} target Target element to bind events to. - * handler to or an array of elements/ids/documents. - * @param {String} name Name of event handler to add, for example: click. - * @param {function} func Function to execute when the event occurs. - * @param {Object} scope Optional scope to execute the function in. - * @return {function} Function callback handler the same as the one passed in. - */ - bind: function(target, name, func, scope) { - var self = this; - - if (Tools.isArray(target)) { - var i = target.length; - - while (i--) { - target[i] = self.bind(target[i], name, func, scope); - } - - return target; - } - - // Collect all window/document events bound by editor instance - if (self.settings.collect && (target === self.doc || target === self.win)) { - self.boundEvents.push([target, name, func, scope]); - } - - return self.events.bind(target, name, func, scope || self); - }, - - /** - * Removes the specified event handler by name and function from an element or collection of elements. - * - * @method unbind - * @param {Element/Document/Window/Array} target Target element to unbind events on. - * @param {String} name Event handler name, for example: "click" - * @param {function} func Function to remove. - * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements - * were passed in. - */ - unbind: function(target, name, func) { - var self = this, i; - - if (Tools.isArray(target)) { - i = target.length; - - while (i--) { - target[i] = self.unbind(target[i], name, func); - } - - return target; - } - - // Remove any bound events matching the input - if (self.boundEvents && (target === self.doc || target === self.win)) { - i = self.boundEvents.length; - - while (i--) { - var item = self.boundEvents[i]; - - if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) { - this.events.unbind(item[0], item[1], item[2]); - } - } - } - - return this.events.unbind(target, name, func); - }, - - /** - * Fires the specified event name with object on target. - * - * @method fire - * @param {Node/Document/Window} target Target element or object to fire event on. - * @param {String} name Name of the event to fire. - * @param {Object} evt Event object to send. - * @return {Event} Event object. - */ - fire: function(target, name, evt) { - return this.events.fire(target, name, evt); - }, - - // Returns the content editable state of a node - getContentEditable: function(node) { - var contentEditable; - - // Check type - if (node.nodeType != 1) { - return null; - } - - // Check for fake content editable - contentEditable = node.getAttribute("data-mce-contenteditable"); - if (contentEditable && contentEditable !== "inherit") { - return contentEditable; - } - - // Check for real content editable - return node.contentEditable !== "inherit" ? node.contentEditable : null; - }, - - /** - * Destroys all internal references to the DOM to solve IE leak issues. - * - * @method destroy - */ - destroy: function() { - var self = this; - - // Unbind all events bound to window/document by editor instance - if (self.boundEvents) { - var i = self.boundEvents.length; - - while (i--) { - var item = self.boundEvents[i]; - this.events.unbind(item[0], item[1], item[2]); - } - - self.boundEvents = null; - } - - // Restore sizzle document to window.document - // Since the current document might be removed producing "Permission denied" on IE see #6325 - if (Sizzle.setDocument) { - Sizzle.setDocument(); - } - - self.win = self.doc = self.root = self.events = self.frag = null; - }, - - // #ifdef debug - - dumpRng: function(r) { - return ( - 'startContainer: ' + r.startContainer.nodeName + - ', startOffset: ' + r.startOffset + - ', endContainer: ' + r.endContainer.nodeName + - ', endOffset: ' + r.endOffset - ); - }, - - // #endif - - _findSib: function(node, selector, name) { - var self = this, func = selector; - - if (node) { - // If expression make a function of it using is - if (typeof(func) == 'string') { - func = function(node) { - return self.is(node, selector); - }; - } - - // Loop all siblings - for (node = node[name]; node; node = node[name]) { - if (func(node)) { - return node; - } - } - } - - return null; - } - }; - - /** - * Instance of DOMUtils for the current document. - * - * @static - * @property DOM - * @type tinymce.dom.DOMUtils - * @example - * // Example of how to add a class to some element by id - * tinymce.DOM.addClass('someid', 'someclass'); - */ - DOMUtils.DOM = new DOMUtils(document); - - return DOMUtils; -}); - -// Included from: js/tinymce/classes/dom/ScriptLoader.js - -/** - * ScriptLoader.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*globals console*/ - -/** - * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks - * when various items gets loaded. This class is useful to load external JavaScript files. - * - * @class tinymce.dom.ScriptLoader - * @example - * // Load a script from a specific URL using the global script loader - * tinymce.ScriptLoader.load('somescript.js'); - * - * // Load a script using a unique instance of the script loader - * var scriptLoader = new tinymce.dom.ScriptLoader(); - * - * scriptLoader.load('somescript.js'); - * - * // Load multiple scripts - * var scriptLoader = new tinymce.dom.ScriptLoader(); - * - * scriptLoader.add('somescript1.js'); - * scriptLoader.add('somescript2.js'); - * scriptLoader.add('somescript3.js'); - * - * scriptLoader.loadQueue(function() { - * alert('All scripts are now loaded.'); - * }); - */ -define("tinymce/dom/ScriptLoader", [ - "tinymce/dom/DOMUtils", - "tinymce/util/Tools" -], function(DOMUtils, Tools) { - var DOM = DOMUtils.DOM; - var each = Tools.each, grep = Tools.grep; - - function ScriptLoader() { - var QUEUED = 0, - LOADING = 1, - LOADED = 2, - states = {}, - queue = [], - scriptLoadedCallbacks = {}, - queueLoadedCallbacks = [], - loading = 0, - undef; - - /** - * Loads a specific script directly without adding it to the load queue. - * - * @method load - * @param {String} url Absolute URL to script to add. - * @param {function} callback Optional callback function to execute ones this script gets loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - function loadScript(url, callback) { - var dom = DOM, elm, id; - - // Execute callback when script is loaded - function done() { - dom.remove(id); - - if (elm) { - elm.onreadystatechange = elm.onload = elm = null; - } - - callback(); - } - - function error() { - /*eslint no-console:0 */ - - // Report the error so it's easier for people to spot loading errors - if (typeof(console) !== "undefined" && console.log) { - console.log("Failed to load: " + url); - } - - // We can't mark it as done if there is a load error since - // A) We don't want to produce 404 errors on the server and - // B) the onerror event won't fire on all browsers. - // done(); - } - - id = dom.uniqueId(); - - // Create new script element - elm = document.createElement('script'); - elm.id = id; - elm.type = 'text/javascript'; - elm.src = url; - - // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly - if ("onreadystatechange" in elm) { - elm.onreadystatechange = function() { - if (/loaded|complete/.test(elm.readyState)) { - done(); - } - }; - } else { - elm.onload = done; - } - - // Add onerror event will get fired on some browsers but not all of them - elm.onerror = error; - - // Add script to document - (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); - } - - /** - * Returns true/false if a script has been loaded or not. - * - * @method isDone - * @param {String} url URL to check for. - * @return {Boolean} true/false if the URL is loaded. - */ - this.isDone = function(url) { - return states[url] == LOADED; - }; - - /** - * Marks a specific script to be loaded. This can be useful if a script got loaded outside - * the script loader or to skip it from loading some script. - * - * @method markDone - * @param {string} u Absolute URL to the script to mark as loaded. - */ - this.markDone = function(url) { - states[url] = LOADED; - }; - - /** - * Adds a specific script to the load queue of the script loader. - * - * @method add - * @param {String} url Absolute URL to script to add. - * @param {function} callback Optional callback function to execute ones this script gets loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - this.add = this.load = function(url, callback, scope) { - var state = states[url]; - - // Add url to load queue - if (state == undef) { - queue.push(url); - states[url] = QUEUED; - } - - if (callback) { - // Store away callback for later execution - if (!scriptLoadedCallbacks[url]) { - scriptLoadedCallbacks[url] = []; - } - - scriptLoadedCallbacks[url].push({ - func: callback, - scope: scope || this - }); - } - }; - - /** - * Starts the loading of the queue. - * - * @method loadQueue - * @param {function} callback Optional callback to execute when all queued items are loaded. - * @param {Object} scope Optional scope to execute the callback in. - */ - this.loadQueue = function(callback, scope) { - this.loadScripts(queue, callback, scope); - }; - - /** - * Loads the specified queue of files and executes the callback ones they are loaded. - * This method is generally not used outside this class but it might be useful in some scenarios. - * - * @method loadScripts - * @param {Array} scripts Array of queue items to load. - * @param {function} callback Optional callback to execute ones all items are loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - this.loadScripts = function(scripts, callback, scope) { - var loadScripts; - - function execScriptLoadedCallbacks(url) { - // Execute URL callback functions - each(scriptLoadedCallbacks[url], function(callback) { - callback.func.call(callback.scope); - }); - - scriptLoadedCallbacks[url] = undef; - } - - queueLoadedCallbacks.push({ - func: callback, - scope: scope || this - }); - - loadScripts = function() { - var loadingScripts = grep(scripts); - - // Current scripts has been handled - scripts.length = 0; - - // Load scripts that needs to be loaded - each(loadingScripts, function(url) { - // Script is already loaded then execute script callbacks directly - if (states[url] == LOADED) { - execScriptLoadedCallbacks(url); - return; - } - - // Is script not loading then start loading it - if (states[url] != LOADING) { - states[url] = LOADING; - loading++; - - loadScript(url, function() { - states[url] = LOADED; - loading--; - - execScriptLoadedCallbacks(url); - - // Load more scripts if they where added by the recently loaded script - loadScripts(); - }); - } - }); - - // No scripts are currently loading then execute all pending queue loaded callbacks - if (!loading) { - each(queueLoadedCallbacks, function(callback) { - callback.func.call(callback.scope); - }); - - queueLoadedCallbacks.length = 0; - } - }; - - loadScripts(); - }; - } - - ScriptLoader.ScriptLoader = new ScriptLoader(); - - return ScriptLoader; -}); - -// Included from: js/tinymce/classes/AddOnManager.js - -/** - * AddOnManager.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles the loading of themes/plugins or other add-ons and their language packs. - * - * @class tinymce.AddOnManager - */ -define("tinymce/AddOnManager", [ - "tinymce/dom/ScriptLoader", - "tinymce/util/Tools" -], function(ScriptLoader, Tools) { - var each = Tools.each; - - function AddOnManager() { - var self = this; - - self.items = []; - self.urls = {}; - self.lookup = {}; - } - - AddOnManager.prototype = { - /** - * Returns the specified add on by the short name. - * - * @method get - * @param {String} name Add-on to look for. - * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined. - */ - get: function(name) { - if (this.lookup[name]) { - return this.lookup[name].instance; - } else { - return undefined; - } - }, - - dependencies: function(name) { - var result; - - if (this.lookup[name]) { - result = this.lookup[name].dependencies; - } - - return result || []; - }, - - /** - * Loads a language pack for the specified add-on. - * - * @method requireLangPack - * @param {String} name Short name of the add-on. - * @param {String} languages Optional comma or space separated list of languages to check if it matches the name. - */ - requireLangPack: function(name, languages) { - var language = AddOnManager.language; - - if (language && AddOnManager.languageLoad !== false) { - if (languages) { - languages = ',' + languages + ','; - - // Load short form sv.js or long form sv_SE.js - if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) { - language = language.substr(0, 2); - } else if (languages.indexOf(',' + language + ',') == -1) { - return; - } - } - - ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js'); - } - }, - - /** - * Adds a instance of the add-on by it's short name. - * - * @method add - * @param {String} id Short name/id for the add-on. - * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add. - * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in. - * @example - * // Create a simple plugin - * tinymce.create('tinymce.plugins.TestPlugin', { - * TestPlugin: function(ed, url) { - * ed.on('click', function(e) { - * ed.windowManager.alert('Hello World!'); - * }); - * } - * }); - * - * // Register plugin using the add method - * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin); - * - * // Initialize TinyMCE - * tinymce.init({ - * ... - * plugins: '-test' // Init the plugin but don't try to load it - * }); - */ - add: function(id, addOn, dependencies) { - this.items.push(addOn); - this.lookup[id] = {instance: addOn, dependencies: dependencies}; - - return addOn; - }, - - createUrl: function(baseUrl, dep) { - if (typeof dep === "object") { - return dep; - } else { - return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; - } - }, - - /** - * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. - * This should be used in development mode. A new compressor/javascript munger process will ensure that the - * components are put together into the plugin.js file and compressed correctly. - * - * @method addComponents - * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins). - * @param {Array} scripts Array containing the names of the scripts to load. - */ - addComponents: function(pluginName, scripts) { - var pluginUrl = this.urls[pluginName]; - - each(scripts, function(script) { - ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script); - }); - }, - - /** - * Loads an add-on from a specific url. - * - * @method load - * @param {String} name Short name of the add-on that gets loaded. - * @param {String} addOnUrl URL to the add-on that will get loaded. - * @param {function} callback Optional callback to execute ones the add-on is loaded. - * @param {Object} scope Optional scope to execute the callback in. - * @example - * // Loads a plugin from an external URL - * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js'); - * - * // Initialize TinyMCE - * tinymce.init({ - * ... - * plugins: '-myplugin' // Don't try to load it again - * }); - */ - load: function(name, addOnUrl, callback, scope) { - var self = this, url = addOnUrl; - - function loadDependencies() { - var dependencies = self.dependencies(name); - - each(dependencies, function(dep) { - var newUrl = self.createUrl(addOnUrl, dep); - - self.load(newUrl.resource, newUrl, undefined, undefined); - }); - - if (callback) { - if (scope) { - callback.call(scope); - } else { - callback.call(ScriptLoader); - } - } - } - - if (self.urls[name]) { - return; - } - - if (typeof addOnUrl === "object") { - url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix; - } - - if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) { - url = AddOnManager.baseURL + '/' + url; - } - - self.urls[name] = url.substring(0, url.lastIndexOf('/')); - - if (self.lookup[name]) { - loadDependencies(); - } else { - ScriptLoader.ScriptLoader.add(url, loadDependencies, scope); - } - } - }; - - AddOnManager.PluginManager = new AddOnManager(); - AddOnManager.ThemeManager = new AddOnManager(); - - return AddOnManager; -}); - -/** - * TinyMCE theme class. - * - * @class tinymce.Theme - */ - -/** - * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc. - * - * @method renderUI - * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance. - * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight. - */ - -/** - * 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. - * - * @class tinymce.Plugin - * @example - * tinymce.PluginManager.add('example', function(editor, url) { - * // Add a button that opens a window - * editor.addButton('example', { - * text: 'My button', - * icon: false, - * onclick: function() { - * // Open window - * editor.windowManager.open({ - * title: 'Example plugin', - * body: [ - * {type: 'textbox', name: 'title', label: 'Title'} - * ], - * onsubmit: function(e) { - * // Insert content when the window form is submitted - * editor.insertContent('Title: ' + e.data.title); - * } - * }); - * } - * }); - * - * // Adds a menu item to the tools menu - * editor.addMenuItem('example', { - * text: 'Example plugin', - * context: 'tools', - * onclick: function() { - * // Open window with a specific url - * editor.windowManager.open({ - * title: 'TinyMCE site', - * url: 'http://www.tinymce.com', - * width: 800, - * height: 600, - * buttons: [{ - * text: 'Close', - * onclick: 'close' - * }] - * }); - * } - * }); - * }); - */ - -// Included from: js/tinymce/classes/html/Node.js - -/** - * Node.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class is a minimalistic implementation of a DOM like node used by the DomParser class. - * - * @example - * var node = new tinymce.html.Node('strong', 1); - * someRoot.append(node); - * - * @class tinymce.html.Node - * @version 3.4 - */ -define("tinymce/html/Node", [], function() { - var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { - '#text': 3, - '#comment': 8, - '#cdata': 4, - '#pi': 7, - '#doctype': 10, - '#document-fragment': 11 - }; - - // Walks the tree left/right - function walk(node, root_node, prev) { - var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; - - // Walk into nodes if it has a start - if (node[startName]) { - return node[startName]; - } - - // Return the sibling if it has one - if (node !== root_node) { - sibling = node[siblingName]; - - if (sibling) { - return sibling; - } - - // Walk up the parents to look for siblings - for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { - sibling = parent[siblingName]; - - if (sibling) { - return sibling; - } - } - } - } - - /** - * Constructs a new Node instance. - * - * @constructor - * @method Node - * @param {String} name Name of the node type. - * @param {Number} type Numeric type representing the node. - */ - function Node(name, type) { - this.name = name; - this.type = type; - - if (type === 1) { - this.attributes = []; - this.attributes.map = {}; - } - } - - Node.prototype = { - /** - * Replaces the current node with the specified one. - * - * @example - * someNode.replace(someNewNode); - * - * @method replace - * @param {tinymce.html.Node} node Node to replace the current node with. - * @return {tinymce.html.Node} The old node that got replaced. - */ - replace: function(node) { - var self = this; - - if (node.parent) { - node.remove(); - } - - self.insert(node, self); - self.remove(); - - return self; - }, - - /** - * Gets/sets or removes an attribute by name. - * - * @example - * someNode.attr("name", "value"); // Sets an attribute - * console.log(someNode.attr("name")); // Gets an attribute - * someNode.attr("name", null); // Removes an attribute - * - * @method attr - * @param {String} name Attribute name to set or get. - * @param {String} value Optional value to set. - * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation. - */ - attr: function(name, value) { - var self = this, attrs, i, undef; - - if (typeof name !== "string") { - for (i in name) { - self.attr(i, name[i]); - } - - return self; - } - - if ((attrs = self.attributes)) { - if (value !== undef) { - // Remove attribute - if (value === null) { - if (name in attrs.map) { - delete attrs.map[name]; - - i = attrs.length; - while (i--) { - if (attrs[i].name === name) { - attrs = attrs.splice(i, 1); - return self; - } - } - } - - return self; - } - - // Set attribute - if (name in attrs.map) { - // Set attribute - i = attrs.length; - while (i--) { - if (attrs[i].name === name) { - attrs[i].value = value; - break; - } - } - } else { - attrs.push({name: name, value: value}); - } - - attrs.map[name] = value; - - return self; - } else { - return attrs.map[name]; - } - } - }, - - /** - * Does a shallow clones the node into a new node. It will also exclude id attributes since - * there should only be one id per document. - * - * @example - * var clonedNode = node.clone(); - * - * @method clone - * @return {tinymce.html.Node} New copy of the original node. - */ - clone: function() { - var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; - - // Clone element attributes - if ((selfAttrs = self.attributes)) { - cloneAttrs = []; - cloneAttrs.map = {}; - - for (i = 0, l = selfAttrs.length; i < l; i++) { - selfAttr = selfAttrs[i]; - - // Clone everything except id - if (selfAttr.name !== 'id') { - cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; - cloneAttrs.map[selfAttr.name] = selfAttr.value; - } - } - - clone.attributes = cloneAttrs; - } - - clone.value = self.value; - clone.shortEnded = self.shortEnded; - - return clone; - }, - - /** - * Wraps the node in in another node. - * - * @example - * node.wrap(wrapperNode); - * - * @method wrap - */ - wrap: function(wrapper) { - var self = this; - - self.parent.insert(wrapper, self); - wrapper.append(self); - - return self; - }, - - /** - * Unwraps the node in other words it removes the node but keeps the children. - * - * @example - * node.unwrap(); - * - * @method unwrap - */ - unwrap: function() { - var self = this, node, next; - - for (node = self.firstChild; node; ) { - next = node.next; - self.insert(node, self, true); - node = next; - } - - self.remove(); - }, - - /** - * Removes the node from it's parent. - * - * @example - * node.remove(); - * - * @method remove - * @return {tinymce.html.Node} Current node that got removed. - */ - remove: function() { - var self = this, parent = self.parent, next = self.next, prev = self.prev; - - if (parent) { - if (parent.firstChild === self) { - parent.firstChild = next; - - if (next) { - next.prev = null; - } - } else { - prev.next = next; - } - - if (parent.lastChild === self) { - parent.lastChild = prev; - - if (prev) { - prev.next = null; - } - } else { - next.prev = prev; - } - - self.parent = self.next = self.prev = null; - } - - return self; - }, - - /** - * Appends a new node as a child of the current node. - * - * @example - * node.append(someNode); - * - * @method append - * @param {tinymce.html.Node} node Node to append as a child of the current one. - * @return {tinymce.html.Node} The node that got appended. - */ - append: function(node) { - var self = this, last; - - if (node.parent) { - node.remove(); - } - - last = self.lastChild; - if (last) { - last.next = node; - node.prev = last; - self.lastChild = node; - } else { - self.lastChild = self.firstChild = node; - } - - node.parent = self; - - return node; - }, - - /** - * Inserts a node at a specific position as a child of the current node. - * - * @example - * parentNode.insert(newChildNode, oldChildNode); - * - * @method insert - * @param {tinymce.html.Node} node Node to insert as a child of the current node. - * @param {tinymce.html.Node} ref_node Reference node to set node before/after. - * @param {Boolean} before Optional state to insert the node before the reference node. - * @return {tinymce.html.Node} The node that got inserted. - */ - insert: function(node, ref_node, before) { - var parent; - - if (node.parent) { - node.remove(); - } - - parent = ref_node.parent || this; - - if (before) { - if (ref_node === parent.firstChild) { - parent.firstChild = node; - } else { - ref_node.prev.next = node; - } - - node.prev = ref_node.prev; - node.next = ref_node; - ref_node.prev = node; - } else { - if (ref_node === parent.lastChild) { - parent.lastChild = node; - } else { - ref_node.next.prev = node; - } - - node.next = ref_node.next; - node.prev = ref_node; - ref_node.next = node; - } - - node.parent = parent; - - return node; - }, - - /** - * Get all children by name. - * - * @method getAll - * @param {String} name Name of the child nodes to collect. - * @return {Array} Array with child nodes matchin the specified name. - */ - getAll: function(name) { - var self = this, node, collection = []; - - for (node = self.firstChild; node; node = walk(node, self)) { - if (node.name === name) { - collection.push(node); - } - } - - return collection; - }, - - /** - * Removes all children of the current node. - * - * @method empty - * @return {tinymce.html.Node} The current node that got cleared. - */ - empty: function() { - var self = this, nodes, i, node; - - // Remove all children - if (self.firstChild) { - nodes = []; - - // Collect the children - for (node = self.firstChild; node; node = walk(node, self)) { - nodes.push(node); - } - - // Remove the children - i = nodes.length; - while (i--) { - node = nodes[i]; - node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; - } - } - - self.firstChild = self.lastChild = null; - - return self; - }, - - /** - * Returns true/false if the node is to be considered empty or not. - * - * @example - * node.isEmpty({img: true}); - * @method isEmpty - * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements. - * @return {Boolean} true/false if the node is empty or not. - */ - isEmpty: function(elements) { - var self = this, node = self.firstChild, i, name; - - if (node) { - do { - if (node.type === 1) { - // Ignore bogus elements - if (node.attributes.map['data-mce-bogus']) { - continue; - } - - // Keep empty elements like - if (elements[node.name]) { - return false; - } - - // Keep elements with data attributes or name attribute like - i = node.attributes.length; - while (i--) { - name = node.attributes[i].name; - if (name === "name" || name.indexOf('data-mce-') === 0) { - return false; - } - } - } - - // Keep comments - if (node.type === 8) { - return false; - } - - // Keep non whitespace text nodes - if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) { - return false; - } - } while ((node = walk(node, self))); - } - - return true; - }, - - /** - * Walks to the next or previous node and returns that node or null if it wasn't found. - * - * @method walk - * @param {Boolean} prev Optional previous node state defaults to false. - * @return {tinymce.html.Node} Node that is next to or previous of the current node. - */ - walk: function(prev) { - return walk(this, null, prev); - } - }; - - /** - * Creates a node of a specific type. - * - * @static - * @method create - * @param {String} name Name of the node type to create for example "b" or "#text". - * @param {Object} attrs Name/value collection of attributes that will be applied to elements. - */ - Node.create = function(name, attrs) { - var node, attrName; - - // Create node - node = new Node(name, typeLookup[name] || 1); - - // Add attributes if needed - if (attrs) { - for (attrName in attrs) { - node.attr(attrName, attrs[attrName]); - } - } - - return node; - }; - - return Node; -}); - -// Included from: js/tinymce/classes/html/Schema.js - -/** - * Schema.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * Schema validator class. - * - * @class tinymce.html.Schema - * @example - * if (tinymce.activeEditor.schema.isValidChild('p', 'span')) - * alert('span is valid child of p.'); - * - * if (tinymce.activeEditor.schema.getElementRule('p')) - * alert('P is a valid element.'); - * - * @class tinymce.html.Schema - * @version 3.4 - */ -define("tinymce/html/Schema", [ - "tinymce/util/Tools" -], function(Tools) { - var mapCache = {}; - var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray; - - function split(items, delim) { - return items ? items.split(delim || ' ') : []; - } - - /** - * Builds a schema lookup table - * - * @private - * @param {String} type html4, html5 or html5-strict schema type. - * @return {Object} Schema lookup table. - */ - function compileSchema(type) { - var schema = {}, globalAttributes, blockContent; - var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent; - - function add(name, attributes, children) { - var ni, i, attributesOrder, args = arguments; - - function arrayToMap(array) { - var map = {}, i, l; - - for (i = 0, l = array.length; i < l; i++) { - map[array[i]] = {}; - } - - return map; - } - - children = children || []; - attributes = attributes || ""; - - if (typeof(children) === "string") { - children = split(children); - } - - // Split string children - for (i = 3; i < args.length; i++) { - if (typeof(args[i]) === "string") { - args[i] = split(args[i]); - } - - children.push.apply(children, args[i]); - } - - name = split(name); - ni = name.length; - while (ni--) { - attributesOrder = [].concat(globalAttributes, split(attributes)); - schema[name[ni]] = { - attributes: arrayToMap(attributesOrder), - attributesOrder: attributesOrder, - children: arrayToMap(children) - }; - } - } - - function addAttrs(name, attributes) { - var ni, schemaItem, i, l; - - name = split(name); - ni = name.length; - attributes = split(attributes); - while (ni--) { - schemaItem = schema[name[ni]]; - for (i = 0, l = attributes.length; i < l; i++) { - schemaItem.attributes[attributes[i]] = {}; - schemaItem.attributesOrder.push(attributes[i]); - } - } - } - - // Use cached schema - if (mapCache[type]) { - return mapCache[type]; - } - - // Attributes present on all elements - globalAttributes = split("id accesskey class dir lang style tabindex title"); - - // Event attributes can be opt-in/opt-out - /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " + - "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " + - "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " + - "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " + - "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " + - "onwaiting" - );*/ - - // Block content elements - blockContent = split( - "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul" - ); - - // Phrasing content elements from the HTML5 spec (inline) - phrasingContent = split( - "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " + - "label map noscript object q s samp script select small span strong sub sup " + - "textarea u var #text #comment" - ); - - // Add HTML5 items to globalAttributes, blockContent, phrasingContent - if (type != "html4") { - globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " + - "hidden spellcheck translate")); - blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav")); - phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " + - "video ruby bdi keygen")); - } - - // Add HTML4 elements unless it's html5-strict - if (type != "html5-strict") { - globalAttributes.push("xml:lang"); - - html4PhrasingContent = split("acronym applet basefont big font strike tt"); - phrasingContent.push.apply(phrasingContent, html4PhrasingContent); - - each(html4PhrasingContent, function(name) { - add(name, "", phrasingContent); - }); - - html4BlockContent = split("center dir isindex noframes"); - blockContent.push.apply(blockContent, html4BlockContent); - - // Flow content elements from the HTML5 spec (block+inline) - flowContent = [].concat(blockContent, phrasingContent); - - each(html4BlockContent, function(name) { - add(name, "", flowContent); - }); - } - - // Flow content elements from the HTML5 spec (block+inline) - flowContent = flowContent || [].concat(blockContent, phrasingContent); - - // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement - // Schema itemsa
b
c will becomea
b
c
- * - * @example - * var parser = new tinymce.html.DomParser({validate: true}, schema); - * var rootNode = parser.parse('x
->x
- function trim(rootBlockNode) { - if (rootBlockNode) { - node = rootBlockNode.firstChild; - if (node && node.type == 3) { - node.value = node.value.replace(startWhiteSpaceRegExp, ''); - } - - node = rootBlockNode.lastChild; - if (node && node.type == 3) { - node.value = node.value.replace(endWhiteSpaceRegExp, ''); - } - } - } - - // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root - if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) { - return; - } - - while (node) { - next = node.next; - - if (node.type == 3 || (node.type == 1 && node.name !== 'p' && - !blockElements[node.name] && !node.attr('data-mce-type'))) { - if (!rootBlockNode) { - // Create a new root block element - rootBlockNode = createNode(rootBlockName, 1); - rootBlockNode.attr(settings.forced_root_block_attrs); - rootNode.insert(rootBlockNode, node); - rootBlockNode.append(node); - } else { - rootBlockNode.append(node); - } - } else { - trim(rootBlockNode); - rootBlockNode = null; - } - - node = next; - } - - trim(rootBlockNode); - } - - function createNode(name, type) { - var node = new Node(name, type), list; - - if (name in nodeFilters) { - list = matchedNodes[name]; - - if (list) { - list.push(node); - } else { - matchedNodes[name] = [node]; - } - } - - return node; - } - - function removeWhitespaceBefore(node) { - var textNode, textVal, sibling; - - for (textNode = node.prev; textNode && textNode.type === 3; ) { - textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); - - if (textVal.length > 0) { - textNode.value = textVal; - textNode = textNode.prev; - } else { - sibling = textNode.prev; - textNode.remove(); - textNode = sibling; - } - } - } - - function cloneAndExcludeBlocks(input) { - var name, output = {}; - - for (name in input) { - if (name !== 'li' && name != 'p') { - output[name] = input[name]; - } - } - - return output; - } - - parser = new SaxParser({ - validate: validate, - allow_script_urls: settings.allow_script_urls, - allow_conditional_comments: settings.allow_conditional_comments, - - // Exclude P and LI from DOM parsing since it's treated better by the DOM parser - self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), - - cdata: function(text) { - node.append(createNode('#cdata', 4)).value = text; - }, - - text: function(text, raw) { - var textNode; - - // Trim all redundant whitespace on non white space elements - if (!isInWhiteSpacePreservedElement) { - text = text.replace(allWhiteSpaceRegExp, ' '); - - if (node.lastChild && blockElements[node.lastChild.name]) { - text = text.replace(startWhiteSpaceRegExp, ''); - } - } - - // Do we need to create the node - if (text.length !== 0) { - textNode = createNode('#text', 3); - textNode.raw = !!raw; - node.append(textNode).value = text; - } - }, - - comment: function(text) { - node.append(createNode('#comment', 8)).value = text; - }, - - pi: function(name, text) { - node.append(createNode(name, 7)).value = text; - removeWhitespaceBefore(node); - }, - - doctype: function(text) { - var newNode; - - newNode = node.append(createNode('#doctype', 10)); - newNode.value = text; - removeWhitespaceBefore(node); - }, - - start: function(name, attrs, empty) { - var newNode, attrFiltersLen, elementRule, attrName, parent; - - elementRule = validate ? schema.getElementRule(name) : {}; - if (elementRule) { - newNode = createNode(elementRule.outputName || name, 1); - newNode.attributes = attrs; - newNode.shortEnded = empty; - - node.append(newNode); - - // Check if node is valid child of the parent node is the child is - // unknown we don't collect it since it's probably a custom element - parent = children[node.name]; - if (parent && children[newNode.name] && !parent[newNode.name]) { - invalidChildren.push(newNode); - } - - attrFiltersLen = attributeFilters.length; - while (attrFiltersLen--) { - attrName = attributeFilters[attrFiltersLen].name; - - if (attrName in attrs.map) { - list = matchedAttributes[attrName]; - - if (list) { - list.push(newNode); - } else { - matchedAttributes[attrName] = [newNode]; - } - } - } - - // Trim whitespace before block - if (blockElements[name]) { - removeWhitespaceBefore(newNode); - } - - // Change current node if the element wasn't empty i.e nota
- lastParent = node; - while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) { - lastParent = parent; - - if (blockElements[parent.name]) { - break; - } - - parent = parent.parent; - } - - if (lastParent === parent) { - textNode = new Node('#text', 3); - textNode.value = '\u00a0'; - node.replace(textNode); - } - } - } - }); - } - - // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. - if (!settings.allow_html_in_named_anchor) { - self.addAttributeFilter('id,name', function(nodes) { - var i = nodes.length, sibling, prevSibling, parent, node; - - while (i--) { - node = nodes[i]; - if (node.name === 'a' && node.firstChild && !node.attr('href')) { - parent = node.parent; - - // Move children after current node - sibling = node.lastChild; - do { - prevSibling = sibling.prev; - parent.insert(sibling, node); - sibling = prevSibling; - } while (sibling); - } - } - }); - } - }; -}); - -// Included from: js/tinymce/classes/html/Writer.js - -/** - * Writer.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser. - * - * @class tinymce.html.Writer - * @example - * var writer = new tinymce.html.Writer({indent: true}); - * var parser = new tinymce.html.SaxParser(writer).parse('
.
- *
- * @method start
- * @param {String} name Name of the element.
- * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
- * @param {Boolean} empty Optional empty state if the tag should end like
.
- */
- start: function(name, attrs, empty) {
- var i, l, attr, value;
-
- if (indent && indentBefore[name] && html.length > 0) {
- value = html[html.length - 1];
-
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
-
- html.push('<', name);
-
- if (attrs) {
- for (i = 0, l = attrs.length; i < l; i++) {
- attr = attrs[i];
- html.push(' ', attr.name, '="', encode(attr.value, true), '"');
- }
- }
-
- if (!empty || htmlOutput) {
- html[html.length] = '>';
- } else {
- html[html.length] = ' />';
- }
-
- if (empty && indent && indentAfter[name] && html.length > 0) {
- value = html[html.length - 1];
-
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
- },
-
- /**
- * Writes the a end element such as
text
')); - * @class tinymce.html.Serializer - * @version 3.4 - */ -define("tinymce/html/Serializer", [ - "tinymce/html/Writer", - "tinymce/html/Schema" -], function(Writer, Schema) { - /** - * Constructs a new Serializer instance. - * - * @constructor - * @method Serializer - * @param {Object} settings Name/value settings object. - * @param {tinymce.html.Schema} schema Schema instance to use. - */ - return function(settings, schema) { - var self = this, writer = new Writer(settings); - - settings = settings || {}; - settings.validate = "validate" in settings ? settings.validate : true; - - self.schema = schema = schema || new Schema(); - self.writer = writer; - - /** - * Serializes the specified node into a string. - * - * @example - * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('text
')); - * @method serialize - * @param {tinymce.html.Node} node Node instance to serialize. - * @return {String} String with HTML based on DOM tree. - */ - self.serialize = function(node) { - var handlers, validate; - - validate = settings.validate; - - handlers = { - // #text - 3: function(node) { - writer.text(node.value, node.raw); - }, - - // #comment - 8: function(node) { - writer.comment(node.value); - }, - - // Processing instruction - 7: function(node) { - writer.pi(node.name, node.value); - }, - - // Doctype - 10: function(node) { - writer.doctype(node.value); - }, - - // CDATA - 4: function(node) { - writer.cdata(node.value); - }, - - // Document fragment - 11: function(node) { - if ((node = node.firstChild)) { - do { - walk(node); - } while ((node = node.next)); - } - } - }; - - writer.reset(); - - function walk(node) { - var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; - - if (!handler) { - name = node.name; - isEmpty = node.shortEnded; - attrs = node.attributes; - - // Sort attributes - if (validate && attrs && attrs.length > 1) { - sortedAttrs = []; - sortedAttrs.map = {}; - - elementRule = schema.getElementRule(node.name); - for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { - attrName = elementRule.attributesOrder[i]; - - if (attrName in attrs.map) { - attrValue = attrs.map[attrName]; - sortedAttrs.map[attrName] = attrValue; - sortedAttrs.push({name: attrName, value: attrValue}); - } - } - - for (i = 0, l = attrs.length; i < l; i++) { - attrName = attrs[i].name; - - if (!(attrName in sortedAttrs.map)) { - attrValue = attrs.map[attrName]; - sortedAttrs.map[attrName] = attrValue; - sortedAttrs.push({name: attrName, value: attrValue}); - } - } - - attrs = sortedAttrs; - } - - writer.start(node.name, attrs, isEmpty); - - if (!isEmpty) { - if ((node = node.firstChild)) { - do { - walk(node); - } while ((node = node.next)); - } - - writer.end(name); - } - } else { - handler(node); - } - } - - // Serialize element and treat all non elements as fragments - if (node.type == 1 && !settings.inner) { - walk(node); - } else { - handlers[11](node); - } - - return writer.getContent(); - }; - }; -}); - -// Included from: js/tinymce/classes/dom/Serializer.js - -/** - * Serializer.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for - * more details and examples on how to use this class. - * - * @class tinymce.dom.Serializer - */ -define("tinymce/dom/Serializer", [ - "tinymce/dom/DOMUtils", - "tinymce/html/DomParser", - "tinymce/html/Entities", - "tinymce/html/Serializer", - "tinymce/html/Node", - "tinymce/html/Schema", - "tinymce/Env", - "tinymce/util/Tools" -], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) { - var each = Tools.each, trim = Tools.trim; - var DOM = DOMUtils.DOM; - - /** - * Constructs a new DOM serializer class. - * - * @constructor - * @method Serializer - * @param {Object} settings Serializer settings object. - * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from. - */ - return function(settings, editor) { - var dom, schema, htmlParser; - - if (editor) { - dom = editor.dom; - schema = editor.schema; - } - - // Default DOM and Schema if they are undefined - dom = dom || DOM; - schema = schema || new Schema(settings); - settings.entity_encoding = settings.entity_encoding || 'named'; - settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; - - htmlParser = new DomParser(settings, schema); - - // Convert tabindex back to elements when serializing contents - htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - node.attr('tabindex', node.attributes.map['data-mce-tabindex']); - node.attr(name, null); - } - }); - - // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed - htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { - var i = nodes.length, node, value, internalName = 'data-mce-' + name; - var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; - - while (i--) { - node = nodes[i]; - - value = node.attributes.map[internalName]; - if (value !== undef) { - // Set external name to internal value and remove internal - node.attr(name, value.length > 0 ? value : null); - node.attr(internalName, null); - } else { - // No internal attribute found then convert the value we have in the DOM - value = node.attributes.map[name]; - - if (name === "style") { - value = dom.serializeStyle(dom.parseStyle(value), node.name); - } else if (urlConverter) { - value = urlConverter.call(urlConverterScope, value, name, node.name); - } - - node.attr(name, value.length > 0 ? value : null); - } - } - }); - - // Remove internal classes mceItem<..> or mceSelected - htmlParser.addAttributeFilter('class', function(nodes) { - var i = nodes.length, node, value; - - while (i--) { - node = nodes[i]; - value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, ''); - node.attr('class', value.length > 0 ? value : null); - } - }); - - // Remove bookmark elements - htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - - if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) { - node.remove(); - } - } - }); - - // Remove expando attributes - htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) { - var i = nodes.length; - - while (i--) { - nodes[i].attr(name, null); - } - }); - - htmlParser.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i].firstChild; - - if (node) { - node.value = Entities.decode(node.value); - } - } - }); - - // Force script into CDATA sections and remove the mce- prefix also add comments around styles - htmlParser.addNodeFilter('script,style', function(nodes, name) { - var i = nodes.length, node, value; - - function trim(value) { - /*jshint maxlen:255 */ - /*eslint max-len:0 */ - return value.replace(/()/g, '\n') - .replace(/^[\r\n]*|[\r\n]*$/g, '') - .replace(/^\s*(()?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); - } - - while (i--) { - node = nodes[i]; - value = node.firstChild ? node.firstChild.value : ''; - - if (name === "script") { - // Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5) - var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''); - node.attr('type', type === 'text/javascript' ? null : type); - - if (value.length > 0) { - node.firstChild.value = '// '; - } - } else { - if (value.length > 0) { - node.firstChild.value = ''; - } - } - } - }); - - // Convert comments to cdata and handle protected comments - htmlParser.addNodeFilter('#comment', function(nodes) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - - if (node.value.indexOf('[CDATA[') === 0) { - node.name = '#cdata'; - node.type = 4; - node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); - } else if (node.value.indexOf('mce:protected ') === 0) { - node.name = "#text"; - node.type = 3; - node.raw = true; - node.value = unescape(node.value).substr(14); - } - } - }); - - htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { - var i = nodes.length, node; - - while (i--) { - node = nodes[i]; - if (node.type === 7) { - node.remove(); - } else if (node.type === 1) { - if (name === "input" && !("type" in node.attributes.map)) { - node.attr('type', 'text'); - } - } - } - }); - - // Fix list elements, TODO: Replace this later - if (settings.fix_list_elements) { - htmlParser.addNodeFilter('ul,ol', function(nodes) { - var i = nodes.length, node, parentNode; - - while (i--) { - node = nodes[i]; - parentNode = node.parent; - - if (parentNode.name === 'ul' || parentNode.name === 'ol') { - if (node.prev && node.prev.name === 'li') { - node.prev.append(node); - } - } - } - }); - } - - // Remove internal data attributes - htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) { - var i = nodes.length; - - while (i--) { - nodes[i].attr(name, null); - } - }); - - // Return public methods - return { - /** - * Schema instance that was used to when the Serializer was constructed. - * - * @field {tinymce.html.Schema} schema - */ - schema: schema, - - /** - * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name - * and then execute the callback ones it has finished parsing the document. - * - * @example - * parser.addNodeFilter('p,h1', function(nodes, name) { - * for (var i = 0; i < nodes.length; i++) { - * console.log(nodes[i].name); - * } - * }); - * @method addNodeFilter - * @method {String} name Comma separated list of nodes to collect. - * @param {function} callback Callback function to execute once it has collected nodes. - */ - addNodeFilter: htmlParser.addNodeFilter, - - /** - * Adds a attribute filter function to the parser used by the serializer, the parser will - * collect nodes that has the specified attributes - * and then execute the callback ones it has finished parsing the document. - * - * @example - * parser.addAttributeFilter('src,href', function(nodes, name) { - * for (var i = 0; i < nodes.length; i++) { - * console.log(nodes[i].name); - * } - * }); - * @method addAttributeFilter - * @method {String} name Comma separated list of nodes to collect. - * @param {function} callback Callback function to execute once it has collected nodes. - */ - addAttributeFilter: htmlParser.addAttributeFilter, - - /** - * Serializes the specified browser DOM node into a HTML string. - * - * @method serialize - * @param {DOMNode} node DOM node to serialize. - * @param {Object} args Arguments option that gets passed to event handlers. - */ - serialize: function(node, args) { - var self = this, impl, doc, oldDoc, htmlSerializer, content; - - // Explorer won't clone contents of script and style and the - // selected index of select elements are cleared on a clone operation. - if (Env.ie && dom.select('script,style,select,map').length > 0) { - content = node.innerHTML; - node = node.cloneNode(false); - dom.setHTML(node, content); - } else { - node = node.cloneNode(true); - } - - // Nodes needs to be attached to something in WebKit/Opera - // This fix will make DOM ranges and make Sizzle happy! - impl = node.ownerDocument.implementation; - if (impl.createHTMLDocument) { - // Create an empty HTML document - doc = impl.createHTMLDocument(""); - - // Add the element or it's children if it's a body element to the new document - each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { - doc.body.appendChild(doc.importNode(node, true)); - }); - - // Grab first child or body element for serialization - if (node.nodeName != 'BODY') { - node = doc.body.firstChild; - } else { - node = doc.body; - } - - // set the new document in DOMUtils so createElement etc works - oldDoc = dom.doc; - dom.doc = doc; - } - - args = args || {}; - args.format = args.format || 'html'; - - // Don't wrap content if we want selected html - if (args.selection) { - args.forced_root_block = ''; - } - - // Pre process - if (!args.no_events) { - args.node = node; - self.onPreProcess(args); - } - - // Setup serializer - htmlSerializer = new Serializer(settings, schema); - - // Parse and serialize HTML - args.content = htmlSerializer.serialize( - htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) - ); - - // Replace all BOM characters for now until we can find a better solution - if (!args.cleanup) { - args.content = args.content.replace(/\uFEFF/g, ''); - } - - // Post process - if (!args.no_events) { - self.onPostProcess(args); - } - - // Restore the old document if it was changed - if (oldDoc) { - dom.doc = oldDoc; - } - - args.node = null; - - return args.content; - }, - - /** - * Adds valid elements rules to the serializers schema instance this enables you to specify things - * like what elements should be outputted and what attributes specific elements might have. - * Consult the Wiki for more details on this format. - * - * @method addRules - * @param {String} rules Valid elements rules string to add to schema. - */ - addRules: function(rules) { - schema.addValidElements(rules); - }, - - /** - * Sets the valid elements rules to the serializers schema instance this enables you to specify things - * like what elements should be outputted and what attributes specific elements might have. - * Consult the Wiki for more details on this format. - * - * @method setRules - * @param {String} rules Valid elements rules string. - */ - setRules: function(rules) { - schema.setValidElements(rules); - }, - - onPreProcess: function(args) { - if (editor) { - editor.fire('PreProcess', args); - } - }, - - onPostProcess: function(args) { - if (editor) { - editor.fire('PostProcess', args); - } - } - }; - }; -}); - -// Included from: js/tinymce/classes/dom/TridentSelection.js - -/** - * TridentSelection.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * Selection class for old explorer versions. This one fakes the - * native selection object available on modern browsers. - * - * @class tinymce.dom.TridentSelection - */ -define("tinymce/dom/TridentSelection", [], function() { - function Selection(selection) { - var self = this, dom = selection.dom, FALSE = false; - - function getPosition(rng, start) { - var checkRng, startIndex = 0, endIndex, inside, - children, child, offset, index, position = -1, parent; - - // Setup test range, collapse it and get the parent - checkRng = rng.duplicate(); - checkRng.collapse(start); - parent = checkRng.parentElement(); - - // Check if the selection is within the right document - if (parent.ownerDocument !== selection.dom.doc) { - return; - } - - // IE will report non editable elements as it's parent so look for an editable one - while (parent.contentEditable === "false") { - parent = parent.parentNode; - } - - // If parent doesn't have any children then return that we are inside the element - if (!parent.hasChildNodes()) { - return {node: parent, inside: 1}; - } - - // Setup node list and endIndex - children = parent.children; - endIndex = children.length - 1; - - // Perform a binary search for the position - while (startIndex <= endIndex) { - index = Math.floor((startIndex + endIndex) / 2); - - // Move selection to node and compare the ranges - child = children[index]; - checkRng.moveToElementText(child); - position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); - - // Before/after or an exact match - if (position > 0) { - endIndex = index - 1; - } else if (position < 0) { - startIndex = index + 1; - } else { - return {node: child}; - } - } - - // Check if child position is before or we didn't find a position - if (position < 0) { - // No element child was found use the parent element and the offset inside that - if (!child) { - checkRng.moveToElementText(parent); - checkRng.collapse(true); - child = parent; - inside = true; - } else { - checkRng.collapse(false); - } - - // Walk character by character in text node until we hit the selected range endpoint, - // hit the end of document or parent isn't the right one - // We need to walk char by char since rng.text or rng.htmlText will trim line endings - offset = 0; - while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { - if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { - break; - } - - offset++; - } - } else { - // Child position is after the selection endpoint - checkRng.collapse(true); - - // Walk character by character in text node until we hit the selected range endpoint, hit - // the end of document or parent isn't the right one - offset = 0; - while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { - if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { - break; - } - - offset++; - } - } - - return {node: child, position: position, offset: offset, inside: inside}; - } - - // Returns a W3C DOM compatible range object by using the IE Range API - function getRange() { - var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark; - - // If selection is outside the current document just return an empty range - element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); - if (element.ownerDocument != dom.doc) { - return domRange; - } - - collapsed = selection.isCollapsed(); - - // Handle control selection - if (ieRange.item) { - domRange.setStart(element.parentNode, dom.nodeIndex(element)); - domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); - - return domRange; - } - - function findEndPoint(start) { - var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; - - container = endPoint.node; - offset = endPoint.offset; - - if (endPoint.inside && !container.hasChildNodes()) { - domRange[start ? 'setStart' : 'setEnd'](container, 0); - return; - } - - if (offset === undef) { - domRange[start ? 'setStartBefore' : 'setEndAfter'](container); - return; - } - - if (endPoint.position < 0) { - sibling = endPoint.inside ? container.firstChild : container.nextSibling; - - if (!sibling) { - domRange[start ? 'setStartAfter' : 'setEndAfter'](container); - return; - } - - if (!offset) { - if (sibling.nodeType == 3) { - domRange[start ? 'setStart' : 'setEnd'](sibling, 0); - } else { - domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); - } - - return; - } - - // Find the text node and offset - while (sibling) { - nodeValue = sibling.nodeValue; - textNodeOffset += nodeValue.length; - - // We are at or passed the position we where looking for - if (textNodeOffset >= offset) { - container = sibling; - textNodeOffset -= offset; - textNodeOffset = nodeValue.length - textNodeOffset; - break; - } - - sibling = sibling.nextSibling; - } - } else { - // Find the text node and offset - sibling = container.previousSibling; - - if (!sibling) { - return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); - } - - // If there isn't any text to loop then use the first position - if (!offset) { - if (container.nodeType == 3) { - domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); - } else { - domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); - } - - return; - } - - while (sibling) { - textNodeOffset += sibling.nodeValue.length; - - // We are at or passed the position we where looking for - if (textNodeOffset >= offset) { - container = sibling; - textNodeOffset -= offset; - break; - } - - sibling = sibling.previousSibling; - } - } - - domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); - } - - try { - // Find start point - findEndPoint(true); - - // Find end point if needed - if (!collapsed) { - findEndPoint(); - } - } catch (ex) { - // IE has a nasty bug where text nodes might throw "invalid argument" when you - // access the nodeValue or other properties of text nodes. This seems to happend when - // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. - if (ex.number == -2147024809) { - // Get the current selection - bookmark = self.getBookmark(2); - - // Get start element - tmpRange = ieRange.duplicate(); - tmpRange.collapse(true); - element = tmpRange.parentElement(); - - // Get end element - if (!collapsed) { - tmpRange = ieRange.duplicate(); - tmpRange.collapse(false); - element2 = tmpRange.parentElement(); - element2.innerHTML = element2.innerHTML; - } - - // Remove the broken elements - element.innerHTML = element.innerHTML; - - // Restore the selection - self.moveToBookmark(bookmark); - - // Since the range has moved we need to re-get it - ieRange = selection.getRng(); - - // Find start point - findEndPoint(true); - - // Find end point if needed - if (!collapsed) { - findEndPoint(); - } - } else { - throw ex; // Throw other errors - } - } - - return domRange; - } - - this.getBookmark = function(type) { - var rng = selection.getRng(), bookmark = {}; - - function getIndexes(node) { - var parent, root, children, i, indexes = []; - - parent = node.parentNode; - root = dom.getRoot().parentNode; - - while (parent != root && parent.nodeType !== 9) { - children = parent.children; - - i = children.length; - while (i--) { - if (node === children[i]) { - indexes.push(i); - break; - } - } - - node = parent; - parent = parent.parentNode; - } - - return indexes; - } - - function getBookmarkEndPoint(start) { - var position; - - position = getPosition(rng, start); - if (position) { - return { - position: position.position, - offset: position.offset, - indexes: getIndexes(position.node), - inside: position.inside - }; - } - } - - // Non ubstructive bookmark - if (type === 2) { - // Handle text selection - if (!rng.item) { - bookmark.start = getBookmarkEndPoint(true); - - if (!selection.isCollapsed()) { - bookmark.end = getBookmarkEndPoint(); - } - } else { - bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))}; - } - } - - return bookmark; - }; - - this.moveToBookmark = function(bookmark) { - var rng, body = dom.doc.body; - - function resolveIndexes(indexes) { - var node, i, idx, children; - - node = dom.getRoot(); - for (i = indexes.length - 1; i >= 0; i--) { - children = node.children; - idx = indexes[i]; - - if (idx <= children.length - 1) { - node = children[idx]; - } - } - - return node; - } - - function setBookmarkEndPoint(start) { - var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset; - - if (endPoint) { - moveLeft = endPoint.position > 0; - - moveRng = body.createTextRange(); - moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); - - offset = endPoint.offset; - if (offset !== undef) { - moveRng.collapse(endPoint.inside || moveLeft); - moveRng.moveStart('character', moveLeft ? -offset : offset); - } else { - moveRng.collapse(start); - } - - rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); - - if (start) { - rng.collapse(true); - } - } - } - - if (bookmark.start) { - if (bookmark.start.ctrl) { - rng = body.createControlRange(); - rng.addElement(resolveIndexes(bookmark.start.indexes)); - rng.select(); - } else { - rng = body.createTextRange(); - setBookmarkEndPoint(true); - setBookmarkEndPoint(); - rng.select(); - } - } - }; - - this.addRange = function(rng) { - var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, - doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; - - function setEndPoint(start) { - var container, offset, marker, tmpRng, nodes; - - marker = dom.create('a'); - container = start ? startContainer : endContainer; - offset = start ? startOffset : endOffset; - tmpRng = ieRng.duplicate(); - - if (container == doc || container == doc.documentElement) { - container = body; - offset = 0; - } - - if (container.nodeType == 3) { - container.parentNode.insertBefore(marker, container); - tmpRng.moveToElementText(marker); - tmpRng.moveStart('character', offset); - dom.remove(marker); - ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); - } else { - nodes = container.childNodes; - - if (nodes.length) { - if (offset >= nodes.length) { - dom.insertAfter(marker, nodes[nodes.length - 1]); - } else { - container.insertBefore(marker, nodes[offset]); - } - - tmpRng.moveToElementText(marker); - } else if (container.canHaveHTML) { - // Empty node selection for example|
would become this:|
- sibling = startContainer.previousSibling; - if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { - sibling.innerHTML = ''; - } else { - sibling = null; - } - - startContainer.innerHTML = ''; - ieRng.moveToElementText(startContainer.lastChild); - ieRng.select(); - dom.doc.selection.clear(); - startContainer.innerHTML = ''; - - if (sibling) { - sibling.innerHTML = ''; - } - return; - } else { - startOffset = dom.nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - } - - if (startOffset == endOffset - 1) { - try { - ctrlElm = startContainer.childNodes[startOffset]; - ctrlRng = body.createControlRange(); - ctrlRng.addElement(ctrlElm); - ctrlRng.select(); - - // Check if the range produced is on the correct element and is a control range - // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 - nativeRng = selection.getRng(); - if (nativeRng.item && ctrlElm === nativeRng.item(0)) { - return; - } - } catch (ex) { - // Ignore - } - } - } - - // Set start/end point of selection - setEndPoint(true); - setEndPoint(); - - // Select the new range and scroll it into view - ieRng.select(); - }; - - // Expose range method - this.getRangeAt = getRange; - } - - return Selection; -}); - -// Included from: js/tinymce/classes/util/VK.js - -/** - * VK.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This file exposes a set of the common KeyCodes for use. Please grow it as needed. - */ -define("tinymce/util/VK", [ - "tinymce/Env" -], function(Env) { - return { - BACKSPACE: 8, - DELETE: 46, - DOWN: 40, - ENTER: 13, - LEFT: 37, - RIGHT: 39, - SPACEBAR: 32, - TAB: 9, - UP: 38, - - modifierPressed: function(e) { - return e.shiftKey || e.ctrlKey || e.altKey; - }, - - metaKeyPressed: function(e) { - // Check if ctrl or meta key is pressed also check if alt is false for Polish users - return (Env.mac ? e.metaKey : e.ctrlKey) && !e.altKey; - } - }; -}); - -// Included from: js/tinymce/classes/dom/ControlSelection.js - -/** - * ControlSelection.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles control selection of elements. Controls are elements - * that can be resized and needs to be selected as a whole. It adds custom resize handles - * to all browser engines that support properly disabling the built in resize logic. - * - * @class tinymce.dom.ControlSelection - */ -define("tinymce/dom/ControlSelection", [ - "tinymce/util/VK", - "tinymce/util/Tools", - "tinymce/Env" -], function(VK, Tools, Env) { - return function(selection, editor) { - var dom = editor.dom, each = Tools.each; - var selectedElm, selectedElmGhost, resizeHandles, selectedHandle, lastMouseDownEvent; - var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted; - var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11; - - // Details about each resize handle how to scale etc - resizeHandles = { - // Name: x multiplier, y multiplier, delta size x, delta size y - n: [0.5, 0, 0, -1], - e: [1, 0.5, 1, 0], - s: [0.5, 1, 0, 1], - w: [0, 0.5, -1, 0], - nw: [0, 0, -1, -1], - ne: [1, 0, 1, -1], - se: [1, 1, 1, 1], - sw: [0, 1, -1, 1] - }; - - // Add CSS for resize handles, cloned element and selected - var rootClass = '.mce-content-body'; - editor.contentStyles.push( - rootClass + ' div.mce-resizehandle {' + - 'position: absolute;' + - 'border: 1px solid black;' + - 'background: #FFF;' + - 'width: 5px;' + - 'height: 5px;' + - 'z-index: 10000' + - '}' + - rootClass + ' .mce-resizehandle:hover {' + - 'background: #000' + - '}' + - rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' + - 'outline: 1px solid black;' + - 'resize: none' + // Have been talks about implementing this in browsers - '}' + - rootClass + ' .mce-clonedresizable {' + - 'position: absolute;' + - (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing - 'opacity: .5;' + - 'filter: alpha(opacity=50);' + - 'z-index: 10000' + - '}' - ); - - function isResizable(elm) { - var selector = editor.settings.object_resizing; - - if (selector === false || Env.iOS) { - return false; - } - - if (typeof selector != 'string') { - selector = 'table,img,div'; - } - - if (elm.getAttribute('data-mce-resize') === 'false') { - return false; - } - - return editor.dom.is(elm, selector); - } - - function resizeGhostElement(e) { - var deltaX, deltaY; - - // Calc new width/height - deltaX = e.screenX - startX; - deltaY = e.screenY - startY; - - // Calc new size - width = deltaX * selectedHandle[2] + startW; - height = deltaY * selectedHandle[3] + startH; - - // Never scale down lower than 5 pixels - width = width < 5 ? 5 : width; - height = height < 5 ? 5 : height; - - // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image - if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) { - width = Math.round(height / ratio); - height = Math.round(width * ratio); - } - - // Update ghost size - dom.setStyles(selectedElmGhost, { - width: width, - height: height - }); - - // Update ghost X position if needed - if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { - dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); - } - - // Update ghost Y position if needed - if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { - dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); - } - - if (!resizeStarted) { - editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH}); - resizeStarted = true; - } - } - - function endGhostResize() { - resizeStarted = false; - - function setSizeProp(name, value) { - if (value) { - // Resize by using style or attribute - if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { - dom.setStyle(selectedElm, name, value); - } else { - dom.setAttrib(selectedElm, name, value); - } - } - } - - // Set width/height properties - setSizeProp('width', width); - setSizeProp('height', height); - - dom.unbind(editableDoc, 'mousemove', resizeGhostElement); - dom.unbind(editableDoc, 'mouseup', endGhostResize); - - if (rootDocument != editableDoc) { - dom.unbind(rootDocument, 'mousemove', resizeGhostElement); - dom.unbind(rootDocument, 'mouseup', endGhostResize); - } - - // Remove ghost and update resize handle positions - dom.remove(selectedElmGhost); - - if (!isIE || selectedElm.nodeName == "TABLE") { - showResizeRect(selectedElm); - } - - editor.fire('ObjectResized', {target: selectedElm, width: width, height: height}); - editor.nodeChanged(); - } - - function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) { - var position, targetWidth, targetHeight, e, rect, offsetParent = editor.getBody(); - - unbindResizeHandleEvents(); - - // Get position and size of target - position = dom.getPos(targetElm, offsetParent); - selectedElmX = position.x; - selectedElmY = position.y; - rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption - targetWidth = rect.width || (rect.right - rect.left); - targetHeight = rect.height || (rect.bottom - rect.top); - - // Reset width/height if user selects a new image/table - if (selectedElm != targetElm) { - detachResizeStartListener(); - selectedElm = targetElm; - width = height = 0; - } - - // Makes it possible to disable resizing - e = editor.fire('ObjectSelected', {target: targetElm}); - - if (isResizable(targetElm) && !e.isDefaultPrevented()) { - each(resizeHandles, function(handle, name) { - var handleElm, handlerContainerElm; - - function startDrag(e) { - startX = e.screenX; - startY = e.screenY; - startW = selectedElm.clientWidth; - startH = selectedElm.clientHeight; - ratio = startH / startW; - selectedHandle = handle; - - selectedElmGhost = selectedElm.cloneNode(true); - dom.addClass(selectedElmGhost, 'mce-clonedresizable'); - selectedElmGhost.contentEditable = false; // Hides IE move layer cursor - selectedElmGhost.unSelectabe = true; - dom.setStyles(selectedElmGhost, { - left: selectedElmX, - top: selectedElmY, - margin: 0 - }); - - selectedElmGhost.removeAttribute('data-mce-selected'); - editor.getBody().appendChild(selectedElmGhost); - - dom.bind(editableDoc, 'mousemove', resizeGhostElement); - dom.bind(editableDoc, 'mouseup', endGhostResize); - - if (rootDocument != editableDoc) { - dom.bind(rootDocument, 'mousemove', resizeGhostElement); - dom.bind(rootDocument, 'mouseup', endGhostResize); - } - } - - if (mouseDownHandleName) { - // Drag started by IE native resizestart - if (name == mouseDownHandleName) { - startDrag(mouseDownEvent); - } - - return; - } - - // Get existing or render resize handle - handleElm = dom.get('mceResizeHandle' + name); - if (!handleElm) { - handlerContainerElm = editor.getBody(); - - handleElm = dom.add(handlerContainerElm, 'div', { - id: 'mceResizeHandle' + name, - 'data-mce-bogus': true, - 'class': 'mce-resizehandle', - unselectable: true, - style: 'cursor:' + name + '-resize; margin:0; padding:0' - }); - - // Hides IE move layer cursor - // If we set it on Chrome we get this wounderful bug: #6725 - if (Env.ie) { - handleElm.contentEditable = false; - } - } else { - dom.show(handleElm); - } - - if (!handle.elm) { - dom.bind(handleElm, 'mousedown', function(e) { - e.stopImmediatePropagation(); - e.preventDefault(); - startDrag(e); - }); - - handle.elm = handleElm; - } - - /* - var halfHandleW = handleElm.offsetWidth / 2; - var halfHandleH = handleElm.offsetHeight / 2; - - // Position element - dom.setStyles(handleElm, { - left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)), - top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH)) - }); - */ - - // Position element - dom.setStyles(handleElm, { - left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), - top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) - }); - }); - } else { - hideResizeRect(); - } - - selectedElm.setAttribute('data-mce-selected', '1'); - } - - function hideResizeRect() { - var name, handleElm; - - unbindResizeHandleEvents(); - - if (selectedElm) { - selectedElm.removeAttribute('data-mce-selected'); - } - - for (name in resizeHandles) { - handleElm = dom.get('mceResizeHandle' + name); - if (handleElm) { - dom.unbind(handleElm); - dom.remove(handleElm); - } - } - } - - function updateResizeRect(e) { - var controlElm; - - function isChildOrEqual(node, parent) { - if (node) { - do { - if (node === parent) { - return true; - } - } while ((node = node.parentNode)); - } - } - - // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v - each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) { - img.removeAttribute('data-mce-selected'); - }); - - controlElm = e.type == 'mousedown' ? e.target : selection.getNode(); - controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr'); - - if (isChildOrEqual(controlElm, editor.getBody())) { - disableGeckoResize(); - - if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) { - if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) { - showResizeRect(controlElm); - return; - } - } - } - - hideResizeRect(); - } - - function attachEvent(elm, name, func) { - if (elm && elm.attachEvent) { - elm.attachEvent('on' + name, func); - } - } - - function detachEvent(elm, name, func) { - if (elm && elm.detachEvent) { - elm.detachEvent('on' + name, func); - } - } - - function resizeNativeStart(e) { - var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY; - - pos = target.getBoundingClientRect(); - relativeX = lastMouseDownEvent.clientX - pos.left; - relativeY = lastMouseDownEvent.clientY - pos.top; - - // Figure out what corner we are draging on - for (name in resizeHandles) { - corner = resizeHandles[name]; - - cornerX = target.offsetWidth * corner[0]; - cornerY = target.offsetHeight * corner[1]; - - if (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) { - selectedHandle = corner; - break; - } - } - - // Remove native selection and let the magic begin - resizeStarted = true; - editor.getDoc().selection.empty(); - showResizeRect(target, name, lastMouseDownEvent); - } - - function nativeControlSelect(e) { - var target = e.srcElement; - - if (target != selectedElm) { - detachResizeStartListener(); - - if (target.id.indexOf('mceResizeHandle') === 0) { - e.returnValue = false; - return; - } - - if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') { - hideResizeRect(); - selectedElm = target; - attachEvent(target, 'resizestart', resizeNativeStart); - } - } - } - - function detachResizeStartListener() { - detachEvent(selectedElm, 'resizestart', resizeNativeStart); - } - - function unbindResizeHandleEvents() { - for (var name in resizeHandles) { - var handle = resizeHandles[name]; - - if (handle.elm) { - dom.unbind(handle.elm); - delete handle.elm; - } - } - } - - function disableGeckoResize() { - try { - // Disable object resizing on Gecko - editor.getDoc().execCommand('enableObjectResizing', false, false); - } catch (ex) { - // Ignore - } - } - - function controlSelect(elm) { - var ctrlRng; - - if (!isIE) { - return; - } - - ctrlRng = editableDoc.body.createControlRange(); - - try { - ctrlRng.addElement(elm); - ctrlRng.select(); - return true; - } catch (ex) { - // Ignore since the element can't be control selected for example a P tag - } - } - - editor.on('init', function() { - if (isIE) { - // Hide the resize rect on resize and reselect the image - editor.on('ObjectResized', function(e) { - if (e.target.nodeName != 'TABLE') { - hideResizeRect(); - controlSelect(e.target); - } - }); - - attachEvent(editor.getBody(), 'controlselect', nativeControlSelect); - - editor.on('mousedown', function(e) { - lastMouseDownEvent = e; - }); - } else { - disableGeckoResize(); - - if (Env.ie >= 11) { - // TODO: Drag/drop doesn't work - editor.on('mouseup', function(e) { - var nodeName = e.target.nodeName; - - if (/^(TABLE|IMG|HR)$/.test(nodeName)) { - editor.selection.select(e.target, nodeName == 'TABLE'); - editor.nodeChanged(); - } - }); - - editor.dom.bind(editor.getBody(), 'mscontrolselect', function(e) { - if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) { - e.preventDefault(); - - // This moves the selection from being a control selection to a text like selection like in WebKit #6753 - // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections. - if (e.target.tagName == 'IMG') { - window.setTimeout(function() { - editor.selection.select(e.target); - }, 0); - } - } - }); - } - } - - editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect); - - // Update resize rect while typing in a table - editor.on('keydown keyup', function(e) { - if (selectedElm && selectedElm.nodeName == "TABLE") { - updateResizeRect(e); - } - }); - - // Hide rect on focusout since it would float on top of windows otherwise - //editor.on('focusout', hideResizeRect); - }); - - editor.on('remove', unbindResizeHandleEvents); - - function destroy() { - selectedElm = selectedElmGhost = null; - - if (isIE) { - detachResizeStartListener(); - detachEvent(editor.getBody(), 'controlselect', nativeControlSelect); - } - } - - return { - isResizable: isResizable, - showResizeRect: showResizeRect, - hideResizeRect: hideResizeRect, - updateResizeRect: updateResizeRect, - controlSelect: controlSelect, - destroy: destroy - }; - }; -}); - -// Included from: js/tinymce/classes/dom/RangeUtils.js - -/** - * Range.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * RangeUtils - * - * @class tinymce.dom.RangeUtils - * @private - */ -define("tinymce/dom/RangeUtils", [ - "tinymce/util/Tools", - "tinymce/dom/TreeWalker" -], function(Tools, TreeWalker) { - var each = Tools.each; - - function RangeUtils(dom) { - /** - * Walks the specified range like object and executes the callback for each sibling collection it finds. - * - * @method walk - * @param {Object} rng Range like object. - * @param {function} callback Callback function to execute for each sibling collection. - */ - this.walk = function(rng, callback) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset, - ancestor, startPoint, - endPoint, node, parent, siblings, nodes; - - // Handle table cell selection the table plugin enables - // you to fake select table cells and perform formatting actions on them - nodes = dom.select('td.mce-item-selected,th.mce-item-selected'); - if (nodes.length > 0) { - each(nodes, function(node) { - callback([node]); - }); - - return; - } - - /** - * Excludes start/end text node if they are out side the range - * - * @private - * @param {Array} nodes Nodes to exclude items from. - * @return {Array} Array with nodes excluding the start/end container if needed. - */ - function exclude(nodes) { - var node; - - // First node is excluded - node = nodes[0]; - if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { - nodes.splice(0, 1); - } - - // Last node is excluded - node = nodes[nodes.length - 1]; - if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { - nodes.splice(nodes.length - 1, 1); - } - - return nodes; - } - - /** - * Collects siblings - * - * @private - * @param {Node} node Node to collect siblings from. - * @param {String} name Name of the sibling to check for. - * @return {Array} Array of collected siblings. - */ - function collectSiblings(node, name, end_node) { - var siblings = []; - - for (; node && node != end_node; node = node[name]) { - siblings.push(node); - } - - return siblings; - } - - /** - * Find an end point this is the node just before the common ancestor root. - * - * @private - * @param {Node} node Node to start at. - * @param {Node} root Root/ancestor element to stop just before. - * @return {Node} Node just before the root element. - */ - function findEndPoint(node, root) { - do { - if (node.parentNode == root) { - return node; - } - - node = node.parentNode; - } while(node); - } - - function walkBoundary(start_node, end_node, next) { - var siblingName = next ? 'nextSibling' : 'previousSibling'; - - for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { - parent = node.parentNode; - siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); - - if (siblings.length) { - if (!next) { - siblings.reverse(); - } - - callback(exclude(siblings)); - } - } - } - - // If index based start position then resolve it - if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { - startContainer = startContainer.childNodes[startOffset]; - } - - // If index based end position then resolve it - if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { - endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; - } - - // Same container - if (startContainer == endContainer) { - return callback(exclude([startContainer])); - } - - // Find common ancestor and end points - ancestor = dom.findCommonAncestor(startContainer, endContainer); - - // Process left side - for (node = startContainer; node; node = node.parentNode) { - if (node === endContainer) { - return walkBoundary(startContainer, ancestor, true); - } - - if (node === ancestor) { - break; - } - } - - // Process right side - for (node = endContainer; node; node = node.parentNode) { - if (node === startContainer) { - return walkBoundary(endContainer, ancestor); - } - - if (node === ancestor) { - break; - } - } - - // Find start/end point - startPoint = findEndPoint(startContainer, ancestor) || startContainer; - endPoint = findEndPoint(endContainer, ancestor) || endContainer; - - // Walk left leaf - walkBoundary(startContainer, startPoint, true); - - // Walk the middle from start to end point - siblings = collectSiblings( - startPoint == startContainer ? startPoint : startPoint.nextSibling, - 'nextSibling', - endPoint == endContainer ? endPoint.nextSibling : endPoint - ); - - if (siblings.length) { - callback(exclude(siblings)); - } - - // Walk right leaf - walkBoundary(endContainer, endPoint); - }; - - /** - * Splits the specified range at it's start/end points. - * - * @private - * @param {Range/RangeObject} rng Range to split. - * @return {Object} Range position object. - */ - this.split = function(rng) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset; - - function splitText(node, offset) { - return node.splitText(offset); - } - - // Handle single text node - if (startContainer == endContainer && startContainer.nodeType == 3) { - if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { - endContainer = splitText(startContainer, startOffset); - startContainer = endContainer.previousSibling; - - if (endOffset > startOffset) { - endOffset = endOffset - startOffset; - startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; - endOffset = endContainer.nodeValue.length; - startOffset = 0; - } else { - endOffset = 0; - } - } - } else { - // Split startContainer text node if needed - if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { - startContainer = splitText(startContainer, startOffset); - startOffset = 0; - } - - // Split endContainer text node if needed - if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { - endContainer = splitText(endContainer, endOffset).previousSibling; - endOffset = endContainer.nodeValue.length; - } - } - - return { - startContainer: startContainer, - startOffset: startOffset, - endContainer: endContainer, - endOffset: endOffset - }; - }; - - /** - * Normalizes the specified range by finding the closest best suitable caret location. - * - * @private - * @param {Range} rng Range to normalize. - * @return {Boolean} True/false if the specified range was normalized or not. - */ - this.normalize = function(rng) { - var normalized, collapsed; - - function normalizeEndPoint(start) { - var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap; - var directionLeft, isAfterNode; - - function hasBrBeforeAfter(node, left) { - var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); - - while ((node = walker[left ? 'prev' : 'next']())) { - if (node.nodeName === "BR") { - return true; - } - } - } - - function isPrevNode(node, name) { - return node.previousSibling && node.previousSibling.nodeName == name; - } - - // Walks the dom left/right to find a suitable text node to move the endpoint into - // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG - function findTextNodeRelative(left, startNode) { - var walker, lastInlineElement, parentBlockContainer; - - startNode = startNode || container; - parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body; - - // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680 - // This:
|
|
x|
]
- rng.moveToElementText(rng2.parentElement()); - if (rng.compareEndPoints('StartToEnd', rng2) === 0) { - rng2.move('character', -1); - } - - rng2.pasteHTML('' + chr + ''); - } - } catch (ex) { - // IE might throw unspecified error so lets ignore it - return null; - } - } else { - // Control selection - element = rng.item(0); - name = element.nodeName; - - return {name: name, index: findIndex(name, element)}; - } - } else { - element = self.getNode(); - name = element.nodeName; - if (name == 'IMG') { - return {name: name, index: findIndex(name, element)}; - } - - // W3C method - rng2 = normalizeTableCellSelection(rng.cloneRange()); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr)); - } - - rng = normalizeTableCellSelection(rng); - rng.collapse(true); - rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr)); - } - - self.moveToBookmark({id: id, keep: 1}); - - return {id: id}; - }, - - /** - * Restores the selection to the specified bookmark. - * - * @method moveToBookmark - * @param {Object} bookmark Bookmark to restore selection from. - * @return {Boolean} true/false if it was successful or not. - * @example - * // Stores a bookmark of the current selection - * var bm = tinymce.activeEditor.selection.getBookmark(); - * - * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); - * - * // Restore the selection bookmark - * tinymce.activeEditor.selection.moveToBookmark(bm); - */ - moveToBookmark: function(bookmark) { - var self = this, dom = self.dom, rng, root, startContainer, endContainer, startOffset, endOffset; - - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; - - if (point) { - offset = point[0]; - - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) { - children = node.childNodes; - - if (point[i] > children.length - 1) { - return; - } - - node = children[point[i]]; - } - - // Move text offset to best suitable location - if (node.nodeType === 3) { - offset = Math.min(point[0], node.nodeValue.length); - } - - // Move element offset to best suitable location - if (node.nodeType === 1) { - offset = Math.min(point[0], node.childNodes.length); - } - - // Set offset within container node - if (start) { - rng.setStart(node, offset); - } else { - rng.setEnd(node, offset); - } - } - - return true; - } - - function restoreEndPoint(suffix) { - var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; - - if (marker) { - node = marker.parentNode; - - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } - - startContainer = endContainer = node; - startOffset = endOffset = idx; - } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } - - endContainer = node; - endOffset = idx; - } - - if (!keep) { - prev = marker.previousSibling; - next = marker.nextSibling; - - // Remove all marker text nodes - each(grep(marker.childNodes), function(node) { - if (node.nodeType == 3) { - node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); - } - }); - - // Remove marker but keep children if for example contents where inserted into the marker - // Also remove duplicated instances of the marker for example by a - // split operation or by WebKit auto split on paste feature - while ((marker = dom.get(bookmark.id + '_' + suffix))) { - dom.remove(marker, 1); - } - - // If siblings are text nodes then merge them unless it's Opera since it some how removes the node - // and we are sniffing since adding a lot of detection code for a browser with 3% of the market - // isn't worth the effort. Sorry, Opera but it's just a fact - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) { - idx = prev.nodeValue.length; - prev.appendData(next.nodeValue); - dom.remove(next); - - if (suffix == 'start') { - startContainer = endContainer = prev; - startOffset = endOffset = idx; - } else { - endContainer = prev; - endOffset = idx; - } - } - } - } - } - - function addBogus(node) { - // Adds a bogus BR element for empty block elements - if (dom.isBlock(node) && !node.innerHTML && !isIE) { - node.innerHTML = '*texttext*
! - // This will reduce the number of wrapper elements that needs to be created - // Move start point up the tree - if (format[0].inline || format[0].block_expand) { - if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { - startContainer = findParentContainer(true); - } - - if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { - endContainer = findParentContainer(); - } - } - - // Expand start/end container to matching selector - if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { - // Find new startContainer/endContainer if there is better one - startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); - endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); - } - - // Expand start/end container to matching block element or text node - if (format[0].block || format[0].selector) { - // Find new startContainer/endContainer if there is better one - startContainer = findBlockEndPoint(startContainer, 'previousSibling'); - endContainer = findBlockEndPoint(endContainer, 'nextSibling'); - - // Non block element then try to expand up the leaf - if (format[0].block) { - if (!isBlock(startContainer)) { - startContainer = findParentContainer(true); - } - - if (!isBlock(endContainer)) { - endContainer = findParentContainer(); - } - } - } - - // Setup index for startContainer - if (startContainer.nodeType == 1) { - startOffset = nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - - // Setup index for endContainer - if (endContainer.nodeType == 1) { - endOffset = nodeIndex(endContainer) + 1; - endContainer = endContainer.parentNode; - } - - // Return new range like object - return { - startContainer: startContainer, - startOffset: startOffset, - endContainer: endContainer, - endOffset: endOffset - }; - } - - /** - * Removes the specified format for the specified node. It will also remove the node if it doesn't have - * any attributes if the format specifies it to do so. - * - * @private - * @param {Object} format Format object with items to remove from node. - * @param {Object} vars Name/value object with variables to apply to format. - * @param {Node} node Node to remove the format styles on. - * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node. - * @return {Boolean} True/false if the node was removed or not. - */ - function removeFormat(format, vars, node, compare_node) { - var i, attrs, stylesModified; - - // Check if node matches format - if (!matchName(node, format)) { - return FALSE; - } - - // Should we compare with format attribs and styles - if (format.remove != 'all') { - // Remove styles - each(format.styles, function(value, name) { - value = normalizeStyleValue(replaceVars(value, vars), name); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(getStyle(compare_node, name), value)) { - dom.setStyle(node, name, ''); - } - - stylesModified = 1; - }); - - // Remove style attribute if it's empty - if (stylesModified && dom.getAttrib(node, 'style') === '') { - node.removeAttribute('style'); - node.removeAttribute('data-mce-style'); - } - - // Remove attributes - each(format.attributes, function(value, name) { - var valueOut; - - value = replaceVars(value, vars); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { - // Keep internal classes - if (name == 'class') { - value = dom.getAttrib(node, name); - if (value) { - // Build new class value where everything is removed except the internal prefixed classes - valueOut = ''; - each(value.split(/\s+/), function(cls) { - if (/mce\w+/.test(cls)) { - valueOut += (valueOut ? ' ' : '') + cls; - } - }); - - // We got some internal classes left - if (valueOut) { - dom.setAttrib(node, name, valueOut); - return; - } - } - } - - // IE6 has a bug where the attribute doesn't get removed correctly - if (name == "class") { - node.removeAttribute('className'); - } - - // Remove mce prefixed attributes - if (MCE_ATTR_RE.test(name)) { - node.removeAttribute('data-mce-' + name); - } - - node.removeAttribute(name); - } - }); - - // Remove classes - each(format.classes, function(value) { - value = replaceVars(value, vars); - - if (!compare_node || dom.hasClass(compare_node, value)) { - dom.removeClass(node, value); - } - }); - - // Check for non internal attributes - attrs = dom.getAttribs(node); - for (i = 0; i < attrs.length; i++) { - if (attrs[i].nodeName.indexOf('_') !== 0) { - return FALSE; - } - } - } - - // Remove the inline child if it's empty for example or - if (format.remove != 'none') { - removeNode(node, format); - return TRUE; - } - } - - /** - * Removes the node and wrap it's children in paragraphs before doing so or - * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. - * - * If the div in the node below gets removed: - * text|
- formatNode.parentNode.replaceChild(caretContainer, formatNode); - } else { - // Insert caret container after the formated node - dom.insertAfter(caretContainer, formatNode); - } - - // Move selection to text node - selection.setCursorLocation(node, 1); - - // If the formatNode is empty, we can remove it safely. - if (dom.isEmpty(formatNode)) { - dom.remove(formatNode); - } - } - } - - // Checks if the parent caret container node isn't empty if that is the case it - // will remove the bogus state on all children that isn't empty - function unmarkBogusCaretParents() { - var caretContainer; - - caretContainer = getParentCaretContainer(selection.getStart()); - if (caretContainer && !dom.isEmpty(caretContainer)) { - walk(caretContainer, function(node) { - if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { - dom.setAttrib(node, 'data-mce-bogus', null); - } - }, 'childNodes'); - } - } - - // Only bind the caret events once - if (!ed._hasCaretEvents) { - // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements - markCaretContainersBogus = function() { - var nodes = [], i; - - if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { - // Mark children - i = nodes.length; - while (i--) { - dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); - } - } - }; - - disableCaretContainer = function(e) { - var keyCode = e.keyCode; - - removeCaretContainer(); - - // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys - if (keyCode == 8 || keyCode == 37 || keyCode == 39) { - removeCaretContainer(getParentCaretContainer(selection.getStart())); - } - - unmarkBogusCaretParents(); - }; - - // Remove bogus state if they got filled by contents using editor.selection.setContent - ed.on('SetContent', function(e) { - if (e.selection) { - unmarkBogusCaretParents(); - } - }); - ed._hasCaretEvents = true; - } - - // Do apply or remove caret format - if (type == "apply") { - applyCaretFormat(); - } else { - removeCaretFormat(); - } - } - - /** - * Moves the start to the first suitable text node. - */ - function moveStart(rng) { - var container = rng.startContainer, - offset = rng.startOffset, isAtEndOfText, - walker, node, nodes, tmpNode; - - // Convert text node into index if possible - if (container.nodeType == 3 && offset >= container.nodeValue.length) { - // Get the parent container location and walk from there - offset = nodeIndex(container); - container = container.parentNode; - isAtEndOfText = true; - } - - // Move startContainer/startOffset in to a suitable node - if (container.nodeType == 1) { - nodes = container.childNodes; - container = nodes[Math.min(offset, nodes.length - 1)]; - walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); - - // If offset is at end of the parent node walk to the next one - if (offset > nodes.length - 1 || isAtEndOfText) { - walker.next(); - } - - for (node = walker.current(); node; node = walker.next()) { - if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { - // IE has a "neat" feature where it moves the start node into the closest element - // we can avoid this by inserting an element before it and then remove it after we set the selection - tmpNode = dom.create('a', null, INVISIBLE_CHAR); - node.parentNode.insertBefore(tmpNode, node); - - // Set selection and remove tmpNode - rng.setStart(node, 0); - selection.setRng(rng); - dom.remove(tmpNode); - - return; - } - } - } - } - }; -}); - -// Included from: js/tinymce/classes/UndoManager.js - -/** - * UndoManager.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * 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. - * - * @class tinymce.UndoManager - */ -define("tinymce/UndoManager", [ - "tinymce/Env", - "tinymce/util/Tools" -], function(Env, Tools) { - var trim = Tools.trim, trimContentRegExp; - - trimContentRegExp = new RegExp([ - ']+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers - 'x
becomes this:x
- function trimInlineElementsOnLeftSideOfBlock(block) { - var node = block, firstChilds = [], i; - - // Find inner most first child ex:*
- while ((node = node.firstChild)) { - if (dom.isBlock(node)) { - return; - } - - if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { - firstChilds.push(node); - } - } - - i = firstChilds.length; - while (i--) { - node = firstChilds[i]; - if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { - dom.remove(node); - } else { - // Remove see #5381 - if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { - dom.remove(node); - } - } - } - } - - // Moves the caret to a suitable position within the root for example in the first non - // pure whitespace text node or before an image - function moveToCaretPosition(root) { - var walker, node, rng, lastNode = root, tempElm; - - function firstNonWhiteSpaceNodeSibling(node) { - while (node) { - if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) { - return node; - } - - node = node.nextSibling; - } - } - - // Old IE versions doesn't properly render blocks with br elements in them - // For exampletext|
text|text2
|
- rng = selection.getRng(); - var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); - var body = editor.getBody(); - if (caretElement === body && selection.isCollapsed()) { - if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { - rng = dom.createRng(); - rng.setStart(body.firstChild, 0); - rng.setEnd(body.firstChild, 0); - selection.setRng(rng); - } - } - - // Insert node maker where we will insert the new HTML and get it's parent - if (!selection.isCollapsed()) { - editor.getDoc().execCommand('Delete', false, null); - } - - parentNode = selection.getNode(); - - // Parse the fragment within the context of the parent node - var parserArgs = {context: parentNode.nodeName.toLowerCase()}; - fragment = parser.parse(value, parserArgs); - - // Move the caret to a more suitable location - node = fragment.lastChild; - if (node.attr('id') == 'mce_marker') { - marker = node; - - for (node = node.prev; node; node = node.walk(true)) { - if (node.type == 3 || !dom.isBlock(node.name)) { - node.parent.insert(marker, node, node.name === 'br'); - break; - } - } - } - - // If parser says valid we can insert the contents into that parent - if (!parserArgs.invalid) { - value = serializer.serialize(fragment); - - // Check if parent is empty or only has one BR element then set the innerHTML of that parent - node = parentNode.firstChild; - node2 = parentNode.lastChild; - if (!node || (node === node2 && node.nodeName === 'BR')) { - dom.setHTML(parentNode, value); - } else { - selection.setContent(value); - } - } else { - // If the fragment was invalid within that context then we need - // to parse and process the parent it's inserted into - - // Insert bookmark node and get the parent - selection.setContent(bookmarkHtml); - parentNode = selection.getNode(); - rootNode = editor.getBody(); - - // Opera will return the document node when selection is in root - if (parentNode.nodeType == 9) { - parentNode = node = rootNode; - } else { - node = parentNode; - } - - // Find the ancestor just before the root element - while (node !== rootNode) { - parentNode = node; - node = node.parentNode; - } - - // Get the outer/inner HTML depending on if we are in the root and parser and serialize that - value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); - value = serializer.serialize( - parser.parse( - // Need to replace by using a function since $ in the contents would otherwise be a problem - value.replace(//i, function() { - return serializer.serialize(fragment); - }) - ) - ); - - // Set the inner/outer HTML depending on if we are in the root or not - if (parentNode == rootNode) { - dom.setHTML(rootNode, value); - } else { - dom.setOuterHTML(parentNode, value); - } - } - - marker = dom.get('mce_marker'); - selection.scrollIntoView(marker); - - // Move selection before marker and remove it - rng = dom.createRng(); - - // If previous sibling is a text node set the selection to the end of that node - node = marker.previousSibling; - if (node && node.nodeType == 3) { - rng.setStart(node, node.nodeValue.length); - - // TODO: Why can't we normalize on IE - if (!isIE) { - node2 = marker.nextSibling; - if (node2 && node2.nodeType == 3) { - node.appendData(node2.data); - node2.parentNode.removeChild(node2); - } - } - } else { - // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node - rng.setStartBefore(marker); - rng.setEndBefore(marker); - } - - // Remove the marker node and set the new range - dom.remove(marker); - selection.setRng(rng); - - // Dispatch after event and add any visual elements needed - editor.fire('SetContent', args); - editor.addVisual(); - }, - - mceInsertRawHTML: function(command, ui, value) { - selection.setContent('tiny_mce_marker'); - editor.setContent( - editor.getContent().replace(/tiny_mce_marker/g, function() { - return value; - }) - ); - }, - - mceToggleFormat: function(command, ui, value) { - toggleFormat(value); - }, - - mceSetContent: function(command, ui, value) { - editor.setContent(value); - }, - - 'Indent,Outdent': function(command) { - var intentValue, indentUnit, value; - - // Setup indent level - intentValue = settings.indentation; - indentUnit = /[a-z%]+$/i.exec(intentValue); - intentValue = parseInt(intentValue, 10); - - if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { - // If forced_root_blocks is set to false we don't have a block to indent so lets create a div - if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { - formatter.apply('div'); - } - - each(selection.getSelectedBlocks(), function(element) { - if (element.nodeName != "LI") { - var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding'; - - indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left'; - - if (command == 'outdent') { - value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue); - dom.setStyle(element, indentStyleName, value ? value + indentUnit : ''); - } else { - value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit; - dom.setStyle(element, indentStyleName, value); - } - } - }); - } else { - execNativeCommand(command); - } - }, - - mceRepaint: function() { - if (isGecko) { - try { - storeSelection(TRUE); - - if (selection.getSel()) { - selection.getSel().selectAllChildren(editor.getBody()); - } - - selection.collapse(TRUE); - restoreSelection(); - } catch (ex) { - // Ignore - } - } - }, - - InsertHorizontalRule: function() { - editor.execCommand('mceInsertContent', false, '|
- rng = selection.getRng(); - if (!rng.item) { - rng.moveToElementText(root); - rng.select(); - } - } - }, - - "delete": function() { - execNativeCommand("Delete"); - - // Check if body is empty after the delete call if so then set the contents - // to an empty string and move the caret to any block produced by that operation - // this fixes the issue with root blocks not being properly produced after a delete call on IE - var body = editor.getBody(); - - if (dom.isEmpty(body)) { - editor.setContent(''); - - if (body.firstChild && dom.isBlock(body.firstChild)) { - editor.selection.setCursorLocation(body.firstChild, 0); - } else { - editor.selection.setCursorLocation(body, 0); - } - } - }, - - mceNewDocument: function() { - editor.setContent(''); - } - }); - - // Add queryCommandState overrides - addCommands({ - // Override justify commands - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { - var name = 'align' + command.substring(7); - var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); - var matches = map(nodes, function(node) { - return !!formatter.matchNode(node, name); - }); - return inArray(matches, TRUE) !== -1; - }, - - 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { - return isFormatMatch(command); - }, - - mceBlockQuote: function() { - return isFormatMatch('blockquote'); - }, - - Outdent: function() { - var node; - - if (settings.inline_styles) { - if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { - return TRUE; - } - - if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { - return TRUE; - } - } - - return ( - queryCommandState('InsertUnorderedList') || - queryCommandState('InsertOrderedList') || - (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')) - ); - }, - - 'InsertUnorderedList,InsertOrderedList': function(command) { - var list = dom.getParent(selection.getNode(), 'ul,ol'); - - return list && - ( - command === 'insertunorderedlist' && list.tagName === 'UL' || - command === 'insertorderedlist' && list.tagName === 'OL' - ); - } - }, 'state'); - - // Add queryCommandValue overrides - addCommands({ - 'FontSize,FontName': function(command) { - var value = 0, parent; - - if ((parent = dom.getParent(selection.getNode(), 'span'))) { - if (command == 'fontsize') { - value = parent.style.fontSize; - } else { - value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); - } - } - - return value; - } - }, 'value'); - - // Add undo manager logic - addCommands({ - Undo: function() { - editor.undoManager.undo(); - }, - - Redo: function() { - editor.undoManager.redo(); - } - }); - }; -}); - -// Included from: js/tinymce/classes/util/URI.js - -/** - * URI.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * This class handles parsing, modification and serialization of URI/URL strings. - * @class tinymce.util.URI - */ -define("tinymce/util/URI", [ - "tinymce/util/Tools" -], function(Tools) { - var each = Tools.each, trim = Tools.trim; - - /** - * Constructs a new URI instance. - * - * @constructor - * @method URI - * @param {String} url URI string to parse. - * @param {Object} settings Optional settings object. - */ - function URI(url, settings) { - var self = this, baseUri, base_url; - - // Trim whitespace - url = trim(url); - - // Default settings - settings = self.settings = settings || {}; - - // Strange app protocol that isn't http/https or local anchor - // For example: mailto,skype,tel etc. - if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { - self.source = url; - return; - } - - var isProtocolRelative = url.indexOf('//') === 0; - - // Absolute path with no host, fake host and protocol - if (url.indexOf('/') === 0 && !isProtocolRelative) { - url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url; - } - - // Relative path http:// or protocol relative //path - if (!/^[\w\-]*:?\/\//.test(url)) { - base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; - if (settings.base_uri.protocol === "") { - url = '//mce_host' + self.toAbsPath(base_url, url); - } else { - url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url); - } - } - - // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) - url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something - - /*jshint maxlen: 255 */ - /*eslint max-len: 0 */ - url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); - - each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { - var part = url[i]; - - // Zope 3 workaround, they use @@something - if (part) { - part = part.replace(/\(mce_at\)/g, '@@'); - } - - self[v] = part; - }); - - baseUri = settings.base_uri; - if (baseUri) { - if (!self.protocol) { - self.protocol = baseUri.protocol; - } - - if (!self.userInfo) { - self.userInfo = baseUri.userInfo; - } - - if (!self.port && self.host === 'mce_host') { - self.port = baseUri.port; - } - - if (!self.host || self.host === 'mce_host') { - self.host = baseUri.host; - } - - self.source = ''; - } - - if (isProtocolRelative) { - self.protocol = ''; - } - - //t.path = t.path || '/'; - } - - URI.prototype = { - /** - * Sets the internal path part of the URI. - * - * @method setPath - * @param {string} path Path string to set. - */ - setPath: function(path) { - var self = this; - - path = /^(.*?)\/?(\w+)?$/.exec(path); - - // Update path parts - self.path = path[0]; - self.directory = path[1]; - self.file = path[2]; - - // Rebuild source - self.source = ''; - self.getURI(); - }, - - /** - * Converts the specified URI into a relative URI based on the current URI instance location. - * - * @method toRelative - * @param {String} uri URI to convert into a relative path/URI. - * @return {String} Relative URI from the point specified in the current URI instance. - * @example - * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm - * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); - */ - toRelative: function(uri) { - var self = this, output; - - if (uri === "./") { - return uri; - } - - uri = new URI(uri, {base_uri: self}); - - // Not on same domain/port or protocol - if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || - (self.protocol != uri.protocol && uri.protocol !== "")) { - return uri.getURI(); - } - - var tu = self.getURI(), uu = uri.getURI(); - - // Allow usage of the base_uri when relative_urls = true - if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { - return tu; - } - - output = self.toRelPath(self.path, uri.path); - - // Add query - if (uri.query) { - output += '?' + uri.query; - } - - // Add anchor - if (uri.anchor) { - output += '#' + uri.anchor; - } - - return output; - }, - - /** - * Converts the specified URI into a absolute URI based on the current URI instance location. - * - * @method toAbsolute - * @param {String} uri URI to convert into a relative path/URI. - * @param {Boolean} noHost No host and protocol prefix. - * @return {String} Absolute URI from the point specified in the current URI instance. - * @example - * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm - * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); - */ - toAbsolute: function(uri, noHost) { - uri = new URI(uri, {base_uri: this}); - - return uri.getURI(this.host == uri.host && this.protocol == uri.protocol ? noHost : 0); - }, - - /** - * Converts a absolute path into a relative path. - * - * @method toRelPath - * @param {String} base Base point to convert the path from. - * @param {String} path Absolute path to convert into a relative path. - */ - toRelPath: function(base, path) { - var items, breakPoint = 0, out = '', i, l; - - // Split the paths - base = base.substring(0, base.lastIndexOf('/')); - base = base.split('/'); - items = path.split('/'); - - if (base.length >= items.length) { - for (i = 0, l = base.length; i < l; i++) { - if (i >= items.length || base[i] != items[i]) { - breakPoint = i + 1; - break; - } - } - } - - if (base.length < items.length) { - for (i = 0, l = items.length; i < l; i++) { - if (i >= base.length || base[i] != items[i]) { - breakPoint = i + 1; - break; - } - } - } - - if (breakPoint === 1) { - return path; - } - - for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { - out += "../"; - } - - for (i = breakPoint - 1, l = items.length; i < l; i++) { - if (i != breakPoint - 1) { - out += "/" + items[i]; - } else { - out += items[i]; - } - } - - return out; - }, - - /** - * Converts a relative path into a absolute path. - * - * @method toAbsPath - * @param {String} base Base point to convert the path from. - * @param {String} path Relative path to convert into an absolute path. - */ - toAbsPath: function(base, path) { - var i, nb = 0, o = [], tr, outPath; - - // Split paths - tr = /\/$/.test(path) ? '/' : ''; - base = base.split('/'); - path = path.split('/'); - - // Remove empty chunks - each(base, function(k) { - if (k) { - o.push(k); - } - }); - - base = o; - - // Merge relURLParts chunks - for (i = path.length - 1, o = []; i >= 0; i--) { - // Ignore empty or . - if (path[i].length === 0 || path[i] === ".") { - continue; - } - - // Is parent - if (path[i] === '..') { - nb++; - continue; - } - - // Move up - if (nb > 0) { - nb--; - continue; - } - - o.push(path[i]); - } - - i = base.length - nb; - - // If /a/b/c or / - if (i <= 0) { - outPath = o.reverse().join('/'); - } else { - outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); - } - - // Add front / if it's needed - if (outPath.indexOf('/') !== 0) { - outPath = '/' + outPath; - } - - // Add traling / if it's needed - if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { - outPath += tr; - } - - return outPath; - }, - - /** - * Returns the full URI of the internal structure. - * - * @method getURI - * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. - */ - getURI: function(noProtoHost) { - var s, self = this; - - // Rebuild source - if (!self.source || noProtoHost) { - s = ''; - - if (!noProtoHost) { - if (self.protocol) { - s += self.protocol + '://'; - } else { - s += '//'; - } - - if (self.userInfo) { - s += self.userInfo + '@'; - } - - if (self.host) { - s += self.host; - } - - if (self.port) { - s += ':' + self.port; - } - } - - if (self.path) { - s += self.path; - } - - if (self.query) { - s += '?' + self.query; - } - - if (self.anchor) { - s += '#' + self.anchor; - } - - self.source = s; - } - - return self.source; - } - }; - - return URI; -}); - -// Included from: js/tinymce/classes/util/Class.js - -/** - * Class.js - * - * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved. - */ - -/** - * This utilitiy class is used for easier inheritage. - * - * Features: - * * Exposed super functions: this._super(); - * * Mixins - * * Dummy functions - * * Property functions: var value = object.value(); and object.value(newValue); - * * Static functions - * * Defaults settings - */ -define("tinymce/util/Class", [ - "tinymce/util/Tools" -], function(Tools) { - var each = Tools.each, extend = Tools.extend; - - var extendClass, initializing; - - function Class() { - } - - // Provides classical inheritance, based on code made by John Resig - Class.extend = extendClass = function(prop) { - var self = this, _super = self.prototype, prototype, name, member; - - // The dummy class constructor - function Class() { - var i, mixins, mixin, self = this; - - // All construction is actually done in the init method - if (!initializing) { - // Run class constuctor - if (self.init) { - self.init.apply(self, arguments); - } - - // Run mixin constructors - mixins = self.Mixins; - if (mixins) { - i = mixins.length; - while (i--) { - mixin = mixins[i]; - if (mixin.init) { - mixin.init.apply(self, arguments); - } - } - } - } - } - - // Dummy function, needs to be extended in order to provide functionality - function dummy() { - return this; - } - - // Creates a overloaded method for the class - // this enables you to use this._super(); to call the super function - function createMethod(name, fn) { - return function(){ - var self = this, tmp = self._super, ret; - - self._super = _super[name]; - ret = fn.apply(self, arguments); - self._super = tmp; - - return ret; - }; - } - - // Instantiate a base class (but only create the instance, - // don't run the init constructor) - initializing = true; - prototype = new self(); - initializing = false; - - // Add mixins - if (prop.Mixins) { - each(prop.Mixins, function(mixin) { - mixin = mixin; - - for (var name in mixin) { - if (name !== "init") { - prop[name] = mixin[name]; - } - } - }); - - if (_super.Mixins) { - prop.Mixins = _super.Mixins.concat(prop.Mixins); - } - } - - // Generate dummy methods - if (prop.Methods) { - each(prop.Methods.split(','), function(name) { - prop[name] = dummy; - }); - } - - // Generate property methods - if (prop.Properties) { - each(prop.Properties.split(','), function(name) { - var fieldName = '_' + name; - - prop[name] = function(value) { - var self = this, undef; - - // Set value - if (value !== undef) { - self[fieldName] = value; - - return self; - } - - // Get value - return self[fieldName]; - }; - }); - } - - // Static functions - if (prop.Statics) { - each(prop.Statics, function(func, name) { - Class[name] = func; - }); - } - - // Default settings - if (prop.Defaults && _super.Defaults) { - prop.Defaults = extend({}, _super.Defaults, prop.Defaults); - } - - // Copy the properties over onto the new prototype - for (name in prop) { - member = prop[name]; - - if (typeof member == "function" && _super[name]) { - prototype[name] = createMethod(name, member); - } else { - prototype[name] = member; - } - } - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.constructor = Class; - - // And make this class extendible - Class.extend = extendClass; - - return Class; - }; - - return Class; -}); - -// Included from: js/tinymce/classes/ui/Selector.js - -/** - * Selector.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*eslint no-nested-ternary:0 */ - -/** - * Selector engine, enables you to select controls by using CSS like expressions. - * We currently only support basic CSS expressions to reduce the size of the core - * and the ones we support should be enough for most cases. - * - * @example - * Supported expressions: - * element - * element#name - * element.class - * element[attr] - * element[attr*=value] - * element[attr~=value] - * element[attr!=value] - * element[attr^=value] - * element[attr$=value] - * element: bug on IE 8 #6178
- DOMUtils.DOM.setHTML(elm, html);
- }
- };
-});
-
-// Included from: js/tinymce/classes/ui/Control.js
-
-/**
- * Control.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/*eslint consistent-this:0 */
-
-/**
- * This is the base class for all controls and containers. All UI control instances inherit
- * from this one as it has the base logic needed by all of them.
- *
- * @class tinymce.ui.Control
- */
-define("tinymce/ui/Control", [
- "tinymce/util/Class",
- "tinymce/util/Tools",
- "tinymce/ui/Collection",
- "tinymce/ui/DomUtils"
-], function(Class, Tools, Collection, DomUtils) {
- "use strict";
-
- var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" +
- " mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu", " ");
-
- var elementIdCache = {};
- var hasMouseWheelEventSupport = "onmousewheel" in document;
- var hasWheelEventSupport = false;
- var classPrefix = "mce-";
-
- var Control = Class.extend({
- Statics: {
- elementIdCache: elementIdCache,
- classPrefix: classPrefix
- },
-
- isRtl: function() {
- return Control.rtl;
- },
-
- /**
- * Class/id prefix to use for all controls.
- *
- * @final
- * @field {String} classPrefix
- */
- classPrefix: classPrefix,
-
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} style Style CSS properties to add.
- * @setting {String} border Border box values example: 1 1 1 1
- * @setting {String} padding Padding box values example: 1 1 1 1
- * @setting {String} margin Margin box values example: 1 1 1 1
- * @setting {Number} minWidth Minimal width for the control.
- * @setting {Number} minHeight Minimal height for the control.
- * @setting {String} classes Space separated list of classes to add.
- * @setting {String} role WAI-ARIA role to use for control.
- * @setting {Boolean} hidden Is the control hidden by default.
- * @setting {Boolean} disabled Is the control disabled by default.
- * @setting {String} name Name of the control instance.
- */
- init: function(settings) {
- var self = this, classes, i;
-
- self.settings = settings = Tools.extend({}, self.Defaults, settings);
-
- // Initial states
- self._id = settings.id || DomUtils.id();
- self._text = self._name = '';
- self._width = self._height = 0;
- self._aria = {role: settings.role};
-
- // Setup classes
- classes = settings.classes;
- if (classes) {
- classes = classes.split(' ');
- classes.map = {};
- i = classes.length;
- while (i--) {
- classes.map[classes[i]] = true;
- }
- }
-
- self._classes = classes || [];
- self.visible(true);
-
- // Set some properties
- Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
- var value = settings[name], undef;
-
- if (value !== undef) {
- self[name](value);
- } else if (self['_' + name] === undef) {
- self['_' + name] = false;
- }
- });
-
- self.on('click', function() {
- if (self.disabled()) {
- return false;
- }
- });
-
- // TODO: Is this needed duplicate code see above?
- if (settings.classes) {
- Tools.each(settings.classes.split(' '), function(cls) {
- self.addClass(cls);
- });
- }
-
- /**
- * Name/value object with settings for the current control.
- *
- * @field {Object} settings
- */
- self.settings = settings;
-
- self._borderBox = self.parseBox(settings.border);
- self._paddingBox = self.parseBox(settings.padding);
- self._marginBox = self.parseBox(settings.margin);
-
- if (settings.hidden) {
- self.hide();
- }
- },
-
- // Will generate getter/setter methods for these properties
- Properties: 'parent,title,text,width,height,disabled,active,name,value',
-
- // Will generate empty dummy functions for these
- Methods: 'renderHtml',
-
- /**
- * Returns the root element to render controls into.
- *
- * @method getContainerElm
- * @return {Element} HTML DOM element to render into.
- */
- getContainerElm: function() {
- return document.body;
- },
-
- /**
- * Returns a control instance for the current DOM element.
- *
- * @method getParentCtrl
- * @param {Element} elm HTML dom element to get parent control from.
- * @return {tinymce.ui.Control} Control instance or undefined.
- */
- getParentCtrl: function(elm) {
- var ctrl, lookup = this.getRoot().controlIdLookup;
-
- while (elm && lookup) {
- ctrl = lookup[elm.id];
- if (ctrl) {
- break;
- }
-
- elm = elm.parentNode;
- }
-
- return ctrl;
- },
-
- /**
- * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
- *
- * @method parseBox
- * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
- * @return {Object} Object with top/right/bottom/left properties.
- * @private
- */
- parseBox: function(value) {
- var len, radix = 10;
-
- if (!value) {
- return;
- }
-
- if (typeof(value) === "number") {
- value = value || 0;
-
- return {
- top: value,
- left: value,
- bottom: value,
- right: value
- };
- }
-
- value = value.split(' ');
- len = value.length;
-
- if (len === 1) {
- value[1] = value[2] = value[3] = value[0];
- } else if (len === 2) {
- value[2] = value[0];
- value[3] = value[1];
- } else if (len === 3) {
- value[3] = value[1];
- }
-
- return {
- top: parseInt(value[0], radix) || 0,
- right: parseInt(value[1], radix) || 0,
- bottom: parseInt(value[2], radix) || 0,
- left: parseInt(value[3], radix) || 0
- };
- },
-
- borderBox: function() {
- return this._borderBox;
- },
-
- paddingBox: function() {
- return this._paddingBox;
- },
-
- marginBox: function() {
- return this._marginBox;
- },
-
- measureBox: function(elm, prefix) {
- function getStyle(name) {
- var defaultView = document.defaultView;
-
- if (defaultView) {
- // Remove camelcase
- name = name.replace(/[A-Z]/g, function(a) {
- return '-' + a;
- });
-
- return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
- }
-
- return elm.currentStyle[name];
- }
-
- function getSide(name) {
- var val = parseFloat(getStyle(name), 10);
-
- return isNaN(val) ? 0 : val;
- }
-
- return {
- top: getSide(prefix + "TopWidth"),
- right: getSide(prefix + "RightWidth"),
- bottom: getSide(prefix + "BottomWidth"),
- left: getSide(prefix + "LeftWidth")
- };
- },
-
- /**
- * Initializes the current controls layout rect.
- * This will be executed by the layout managers to determine the
- * default minWidth/minHeight etc.
- *
- * @method initLayoutRect
- * @return {Object} Layout rect instance.
- */
- initLayoutRect: function() {
- var self = this, settings = self.settings, borderBox, layoutRect;
- var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
- var startMinWidth, startMinHeight, initialSize;
-
- // Measure the current element
- borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
- self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
- self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
- initialSize = DomUtils.getSize(elm);
-
- // Setup minWidth/minHeight and width/height
- startMinWidth = settings.minWidth;
- startMinHeight = settings.minHeight;
- minWidth = startMinWidth || initialSize.width;
- minHeight = startMinHeight || initialSize.height;
- width = settings.width;
- height = settings.height;
- autoResize = settings.autoResize;
- autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
-
- width = width || minWidth;
- height = height || minHeight;
-
- var deltaW = borderBox.left + borderBox.right;
- var deltaH = borderBox.top + borderBox.bottom;
-
- var maxW = settings.maxWidth || 0xFFFF;
- var maxH = settings.maxHeight || 0xFFFF;
-
- // Setup initial layout rect
- self._layoutRect = layoutRect = {
- x: settings.x || 0,
- y: settings.y || 0,
- w: width,
- h: height,
- deltaW: deltaW,
- deltaH: deltaH,
- contentW: width - deltaW,
- contentH: height - deltaH,
- innerW: width - deltaW,
- innerH: height - deltaH,
- startMinWidth: startMinWidth || 0,
- startMinHeight: startMinHeight || 0,
- minW: Math.min(minWidth, maxW),
- minH: Math.min(minHeight, maxH),
- maxW: maxW,
- maxH: maxH,
- autoResize: autoResize,
- scrollW: 0
- };
-
- self._lastLayoutRect = {};
-
- return layoutRect;
- },
-
- /**
- * Getter/setter for the current layout rect.
- *
- * @method layoutRect
- * @param {Object} [newRect] Optional new layout rect.
- * @return {tinymce.ui.Control/Object} Current control or rect object.
- */
- layoutRect: function(newRect) {
- var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
-
- // Initialize default layout rect
- if (!curRect) {
- curRect = self.initLayoutRect();
- }
-
- // Set new rect values
- if (newRect) {
- // Calc deltas between inner and outer sizes
- deltaWidth = curRect.deltaW;
- deltaHeight = curRect.deltaH;
-
- // Set x position
- if (newRect.x !== undef) {
- curRect.x = newRect.x;
- }
-
- // Set y position
- if (newRect.y !== undef) {
- curRect.y = newRect.y;
- }
-
- // Set minW
- if (newRect.minW !== undef) {
- curRect.minW = newRect.minW;
- }
-
- // Set minH
- if (newRect.minH !== undef) {
- curRect.minH = newRect.minH;
- }
-
- // Set new width and calculate inner width
- size = newRect.w;
- if (size !== undef) {
- size = size < curRect.minW ? curRect.minW : size;
- size = size > curRect.maxW ? curRect.maxW : size;
- curRect.w = size;
- curRect.innerW = size - deltaWidth;
- }
-
- // Set new height and calculate inner height
- size = newRect.h;
- if (size !== undef) {
- size = size < curRect.minH ? curRect.minH : size;
- size = size > curRect.maxH ? curRect.maxH : size;
- curRect.h = size;
- curRect.innerH = size - deltaHeight;
- }
-
- // Set new inner width and calculate width
- size = newRect.innerW;
- if (size !== undef) {
- size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
- size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
- curRect.innerW = size;
- curRect.w = size + deltaWidth;
- }
-
- // Set new height and calculate inner height
- size = newRect.innerH;
- if (size !== undef) {
- size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
- size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
- curRect.innerH = size;
- curRect.h = size + deltaHeight;
- }
-
- // Set new contentW
- if (newRect.contentW !== undef) {
- curRect.contentW = newRect.contentW;
- }
-
- // Set new contentH
- if (newRect.contentH !== undef) {
- curRect.contentH = newRect.contentH;
- }
-
- // Compare last layout rect with the current one to see if we need to repaint or not
- lastLayoutRect = self._lastLayoutRect;
- if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
- lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
- repaintControls = Control.repaintControls;
-
- if (repaintControls) {
- if (repaintControls.map && !repaintControls.map[self._id]) {
- repaintControls.push(self);
- repaintControls.map[self._id] = true;
- }
- }
-
- lastLayoutRect.x = curRect.x;
- lastLayoutRect.y = curRect.y;
- lastLayoutRect.w = curRect.w;
- lastLayoutRect.h = curRect.h;
- }
-
- return self;
- }
-
- return curRect;
- },
-
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
-
- // Use Math.round on all values on IE < 9
- round = !document.createRange ? Math.round : function(value) {
- return value;
- };
-
- style = self.getEl().style;
- rect = self._layoutRect;
- lastRepaintRect = self._lastRepaintRect || {};
-
- borderBox = self._borderBox;
- borderW = borderBox.left + borderBox.right;
- borderH = borderBox.top + borderBox.bottom;
-
- if (rect.x !== lastRepaintRect.x) {
- style.left = round(rect.x) + 'px';
- lastRepaintRect.x = rect.x;
- }
-
- if (rect.y !== lastRepaintRect.y) {
- style.top = round(rect.y) + 'px';
- lastRepaintRect.y = rect.y;
- }
-
- if (rect.w !== lastRepaintRect.w) {
- style.width = round(rect.w - borderW) + 'px';
- lastRepaintRect.w = rect.w;
- }
-
- if (rect.h !== lastRepaintRect.h) {
- style.height = round(rect.h - borderH) + 'px';
- lastRepaintRect.h = rect.h;
- }
-
- // Update body if needed
- if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
- bodyStyle = self.getEl('body').style;
- bodyStyle.width = round(rect.innerW) + 'px';
- lastRepaintRect.innerW = rect.innerW;
- }
-
- if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
- bodyStyle = bodyStyle || self.getEl('body').style;
- bodyStyle.height = round(rect.innerH) + 'px';
- lastRepaintRect.innerH = rect.innerH;
- }
-
- self._lastRepaintRect = lastRepaintRect;
- self.fire('repaint', {}, false);
- },
-
- /**
- * Binds a callback to the specified event. This event can both be
- * native browser events like "click" or custom ones like PostRender.
- *
- * The callback function will be passed a DOM event like object that enables yout do stop propagation.
- *
- * @method on
- * @param {String} name Name of the event to bind. For example "click".
- * @param {String/function} callback Callback function to execute ones the event occurs.
- * @return {tinymce.ui.Control} Current control object.
- */
- on: function(name, callback) {
- var self = this, bindings, handlers, names, i;
-
- function resolveCallbackName(name) {
- var callback, scope;
-
- return function(e) {
- if (!callback) {
- self.parentsAndSelf().each(function(ctrl) {
- var callbacks = ctrl.settings.callbacks;
-
- if (callbacks && (callback = callbacks[name])) {
- scope = ctrl;
- return false;
- }
- });
- }
-
- return callback.call(scope, e);
- };
- }
-
- if (callback) {
- if (typeof(callback) == 'string') {
- callback = resolveCallbackName(callback);
- }
-
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
-
- bindings = self._bindings;
- if (!bindings) {
- bindings = self._bindings = {};
- }
-
- handlers = bindings[name];
- if (!handlers) {
- handlers = bindings[name] = [];
- }
-
- handlers.push(callback);
-
- if (nativeEvents[name]) {
- if (!self._nativeEvents) {
- self._nativeEvents = {};
- }
-
- self._nativeEvents[name] = true;
-
- if (self._rendered) {
- self.bindPendingEvents();
- }
- }
- }
- }
-
- return self;
- },
-
- /**
- * Unbinds the specified event and optionally a specific callback. If you omit the name
- * parameter all event handlers will be removed. If you omit the callback all event handles
- * by the specified name will be removed.
- *
- * @method off
- * @param {String} [name] Name for the event to unbind.
- * @param {function} [callback] Callback function to unbind.
- * @return {mxex.ui.Control} Current control object.
- */
- off: function(name, callback) {
- var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi;
-
- if (bindings) {
- if (name) {
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- handlers = bindings[name];
-
- // Unbind all handlers
- if (!name) {
- for (bindingName in bindings) {
- bindings[bindingName].length = 0;
- }
-
- return self;
- }
-
- if (handlers) {
- // Unbind all by name
- if (!callback) {
- handlers.length = 0;
- } else {
- // Unbind specific ones
- hi = handlers.length;
- while (hi--) {
- if (handlers[hi] === callback) {
- handlers.splice(hi, 1);
- }
- }
- }
- }
- }
- } else {
- self._bindings = [];
- }
- }
-
- return self;
- },
-
- /**
- * Fires the specified event by name and arguments on the control. This will execute all
- * bound event handlers.
- *
- * @method fire
- * @param {String} name Name of the event to fire.
- * @param {Object} [args] Arguments to pass to the event.
- * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
- * @return {Object} Current arguments object.
- */
- fire: function(name, args, bubble) {
- var self = this, i, l, handlers, parentCtrl;
-
- name = name.toLowerCase();
-
- // Dummy function that gets replaced on the delegation state functions
- function returnFalse() {
- return false;
- }
-
- // Dummy function that gets replaced on the delegation state functions
- function returnTrue() {
- return true;
- }
-
- // Setup empty object if args is omited
- args = args || {};
-
- // Stick type into event object
- if (!args.type) {
- args.type = name;
- }
-
- // Stick control into event
- if (!args.control) {
- args.control = self;
- }
-
- // Add event delegation methods if they are missing
- if (!args.preventDefault) {
- // Add preventDefault method
- args.preventDefault = function() {
- args.isDefaultPrevented = returnTrue;
- };
-
- // Add stopPropagation
- args.stopPropagation = function() {
- args.isPropagationStopped = returnTrue;
- };
-
- // Add stopImmediatePropagation
- args.stopImmediatePropagation = function() {
- args.isImmediatePropagationStopped = returnTrue;
- };
-
- // Add event delegation states
- args.isDefaultPrevented = returnFalse;
- args.isPropagationStopped = returnFalse;
- args.isImmediatePropagationStopped = returnFalse;
- }
-
- if (self._bindings) {
- handlers = self._bindings[name];
-
- if (handlers) {
- for (i = 0, l = handlers.length; i < l; i++) {
- // Execute callback and break if the callback returns a false
- if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) {
- break;
- }
- }
- }
- }
-
- // Bubble event up to parent controls
- if (bubble !== false) {
- parentCtrl = self.parent();
- while (parentCtrl && !args.isPropagationStopped()) {
- parentCtrl.fire(name, args, false);
- parentCtrl = parentCtrl.parent();
- }
- }
-
- return args;
- },
-
- /**
- * Returns true/false if the specified event has any listeners.
- *
- * @method hasEventListeners
- * @param {String} name Name of the event to check for.
- * @return {Boolean} True/false state if the event has listeners.
- */
- hasEventListeners: function(name) {
- return name in this._bindings;
- },
-
- /**
- * Returns a control collection with all parent controls.
- *
- * @method parents
- * @param {String} selector Optional selector expression to find parents.
- * @return {tinymce.ui.Collection} Collection with all parent controls.
- */
- parents: function(selector) {
- var self = this, ctrl, parents = new Collection();
-
- // Add each parent to collection
- for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
- parents.add(ctrl);
- }
-
- // Filter away everything that doesn't match the selector
- if (selector) {
- parents = parents.filter(selector);
- }
-
- return parents;
- },
-
- /**
- * Returns the current control and it's parents.
- *
- * @method parentsAndSelf
- * @param {String} selector Optional selector expression to find parents.
- * @return {tinymce.ui.Collection} Collection with all parent controls.
- */
- parentsAndSelf: function(selector) {
- return new Collection(this).add(this.parents(selector));
- },
-
- /**
- * Returns the control next to the current control.
- *
- * @method next
- * @return {tinymce.ui.Control} Next control instance.
- */
- next: function() {
- var parentControls = this.parent().items();
-
- return parentControls[parentControls.indexOf(this) + 1];
- },
-
- /**
- * Returns the control previous to the current control.
- *
- * @method prev
- * @return {tinymce.ui.Control} Previous control instance.
- */
- prev: function() {
- var parentControls = this.parent().items();
-
- return parentControls[parentControls.indexOf(this) - 1];
- },
-
- /**
- * Find the common ancestor for two control instances.
- *
- * @method findCommonAncestor
- * @param {tinymce.ui.Control} ctrl1 First control.
- * @param {tinymce.ui.Control} ctrl2 Second control.
- * @return {tinymce.ui.Control} Ancestor control instance.
- */
- findCommonAncestor: function(ctrl1, ctrl2) {
- var parentCtrl;
-
- while (ctrl1) {
- parentCtrl = ctrl2;
-
- while (parentCtrl && ctrl1 != parentCtrl) {
- parentCtrl = parentCtrl.parent();
- }
-
- if (ctrl1 == parentCtrl) {
- break;
- }
-
- ctrl1 = ctrl1.parent();
- }
-
- return ctrl1;
- },
-
- /**
- * Returns true/false if the specific control has the specific class.
- *
- * @method hasClass
- * @param {String} cls Class to check for.
- * @param {String} [group] Sub element group name.
- * @return {Boolean} True/false if the control has the specified class.
- */
- hasClass: function(cls, group) {
- var classes = this._classes[group || 'control'];
-
- cls = this.classPrefix + cls;
-
- return classes && !!classes.map[cls];
- },
-
- /**
- * Adds the specified class to the control
- *
- * @method addClass
- * @param {String} cls Class to check for.
- * @param {String} [group] Sub element group name.
- * @return {tinymce.ui.Control} Current control object.
- */
- addClass: function(cls, group) {
- var self = this, classes, elm;
-
- cls = this.classPrefix + cls;
- classes = self._classes[group || 'control'];
-
- if (!classes) {
- classes = [];
- classes.map = {};
- self._classes[group || 'control'] = classes;
- }
-
- if (!classes.map[cls]) {
- classes.map[cls] = cls;
- classes.push(cls);
-
- if (self._rendered) {
- elm = self.getEl(group);
-
- if (elm) {
- elm.className = classes.join(' ');
- }
- }
- }
-
- return self;
- },
-
- /**
- * Removes the specified class from the control.
- *
- * @method removeClass
- * @param {String} cls Class to remove.
- * @param {String} [group] Sub element group name.
- * @return {tinymce.ui.Control} Current control object.
- */
- removeClass: function(cls, group) {
- var self = this, classes, i, elm;
-
- cls = this.classPrefix + cls;
- classes = self._classes[group || 'control'];
- if (classes && classes.map[cls]) {
- delete classes.map[cls];
-
- i = classes.length;
- while (i--) {
- if (classes[i] === cls) {
- classes.splice(i, 1);
- }
- }
- }
-
- if (self._rendered) {
- elm = self.getEl(group);
-
- if (elm) {
- elm.className = classes.join(' ');
- }
- }
-
- return self;
- },
-
- /**
- * Toggles the specified class on the control.
- *
- * @method toggleClass
- * @param {String} cls Class to remove.
- * @param {Boolean} state True/false state to add/remove class.
- * @param {String} [group] Sub element group name.
- * @return {tinymce.ui.Control} Current control object.
- */
- toggleClass: function(cls, state, group) {
- var self = this;
-
- if (state) {
- self.addClass(cls, group);
- } else {
- self.removeClass(cls, group);
- }
-
- return self;
- },
-
- /**
- * Returns the class string for the specified group name.
- *
- * @method classes
- * @param {String} [group] Group to get clases by.
- * @return {String} Classes for the specified group.
- */
- classes: function(group) {
- var classes = this._classes[group || 'control'];
-
- return classes ? classes.join(' ') : '';
- },
-
- /**
- * Sets the inner HTML of the control element.
- *
- * @method innerHtml
- * @param {String} html Html string to set as inner html.
- * @return {tinymce.ui.Control} Current control object.
- */
- innerHtml: function(html) {
- DomUtils.innerHtml(this.getEl(), html);
- return this;
- },
-
- /**
- * Returns the control DOM element or sub element.
- *
- * @method getEl
- * @param {String} [suffix] Suffix to get element by.
- * @param {Boolean} [dropCache] True if the cache for the element should be dropped.
- * @return {Element} HTML DOM element for the current control or it's children.
- */
- getEl: function(suffix, dropCache) {
- var elm, id = suffix ? this._id + '-' + suffix : this._id;
-
- elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id);
-
- return elm;
- },
-
- /**
- * Sets/gets the visible for the control.
- *
- * @method visible
- * @param {Boolean} state Value to set to control.
- * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
- */
- visible: function(state) {
- var self = this, parentCtrl;
-
- if (typeof(state) !== "undefined") {
- if (self._visible !== state) {
- if (self._rendered) {
- self.getEl().style.display = state ? '' : 'none';
- }
-
- self._visible = state;
-
- // Parent container needs to reflow
- parentCtrl = self.parent();
- if (parentCtrl) {
- parentCtrl._lastRect = null;
- }
-
- self.fire(state ? 'show' : 'hide');
- }
-
- return self;
- }
-
- return self._visible;
- },
-
- /**
- * Sets the visible state to true.
- *
- * @method show
- * @return {tinymce.ui.Control} Current control instance.
- */
- show: function() {
- return this.visible(true);
- },
-
- /**
- * Sets the visible state to false.
- *
- * @method hide
- * @return {tinymce.ui.Control} Current control instance.
- */
- hide: function() {
- return this.visible(false);
- },
-
- /**
- * Focuses the current control.
- *
- * @method focus
- * @return {tinymce.ui.Control} Current control instance.
- */
- focus: function() {
- try {
- this.getEl().focus();
- } catch (ex) {
- // Ignore IE error
- }
-
- return this;
- },
-
- /**
- * Blurs the current control.
- *
- * @method blur
- * @return {tinymce.ui.Control} Current control instance.
- */
- blur: function() {
- this.getEl().blur();
-
- return this;
- },
-
- /**
- * Sets the specified aria property.
- *
- * @method aria
- * @param {String} name Name of the aria property to set.
- * @param {String} value Value of the aria property.
- * @return {tinymce.ui.Control} Current control instance.
- */
- aria: function(name, value) {
- var self = this, elm = self.getEl(self.ariaTarget);
-
- if (typeof(value) === "undefined") {
- return self._aria[name];
- } else {
- self._aria[name] = value;
- }
-
- if (self._rendered) {
- elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
- }
-
- return self;
- },
-
- /**
- * Encodes the specified string with HTML entities. It will also
- * translate the string to different languages.
- *
- * @method encode
- * @param {String/Object/Array} text Text to entity encode.
- * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
- * @return {String} Encoded and possible traslated string.
- */
- encode: function(text, translate) {
- if (translate !== false) {
- text = this.translate(text);
- }
-
- return (text || '').replace(/[&<>"]/g, function(match) {
- return '' + match.charCodeAt(0) + ';';
- });
- },
-
- /**
- * Returns the translated string.
- *
- * @method translate
- * @param {String} text Text to translate.
- * @return {String} Translated string or the same as the input.
- */
- translate: function(text) {
- return Control.translate ? Control.translate(text) : text;
- },
-
- /**
- * Adds items before the current control.
- *
- * @method before
- * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
- * @return {tinymce.ui.Control} Current control instance.
- */
- before: function(items) {
- var self = this, parent = self.parent();
-
- if (parent) {
- parent.insert(items, parent.items().indexOf(self), true);
- }
-
- return self;
- },
-
- /**
- * Adds items after the current control.
- *
- * @method after
- * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
- * @return {tinymce.ui.Control} Current control instance.
- */
- after: function(items) {
- var self = this, parent = self.parent();
-
- if (parent) {
- parent.insert(items, parent.items().indexOf(self));
- }
-
- return self;
- },
-
- /**
- * Removes the current control from DOM and from UI collections.
- *
- * @method remove
- * @return {tinymce.ui.Control} Current control instance.
- */
- remove: function() {
- var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
-
- if (self.items) {
- var controls = self.items().toArray();
- i = controls.length;
- while (i--) {
- controls[i].remove();
- }
- }
-
- if (parent && parent.items) {
- newItems = [];
-
- parent.items().each(function(item) {
- if (item !== self) {
- newItems.push(item);
- }
- });
-
- parent.items().set(newItems);
- parent._lastRect = null;
- }
-
- if (self._eventsRoot && self._eventsRoot == self) {
- DomUtils.off(elm);
- }
-
- var lookup = self.getRoot().controlIdLookup;
- if (lookup) {
- delete lookup[self._id];
- }
-
- delete elementIdCache[self._id];
-
- if (elm && elm.parentNode) {
- var nodes = elm.getElementsByTagName('*');
-
- i = nodes.length;
- while (i--) {
- delete elementIdCache[nodes[i].id];
- }
-
- elm.parentNode.removeChild(elm);
- }
-
- self._rendered = false;
-
- return self;
- },
-
- /**
- * Renders the control before the specified element.
- *
- * @method renderBefore
- * @param {Element} elm Element to render before.
- * @return {tinymce.ui.Control} Current control instance.
- */
- renderBefore: function(elm) {
- var self = this;
-
- elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
- self.postRender();
-
- return self;
- },
-
- /**
- * Renders the control to the specified element.
- *
- * @method renderBefore
- * @param {Element} elm Element to render to.
- * @return {tinymce.ui.Control} Current control instance.
- */
- renderTo: function(elm) {
- var self = this;
-
- elm = elm || self.getContainerElm();
- elm.appendChild(DomUtils.createFragment(self.renderHtml()));
- self.postRender();
-
- return self;
- },
-
- /**
- * Post render method. Called after the control has been rendered to the target.
- *
- * @method postRender
- * @return {tinymce.ui.Control} Current control instance.
- */
- postRender: function() {
- var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
-
- // Bind on |ba
ab
|
- * - * Or: - *|
- if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { - isCollapsed = editor.selection.isCollapsed(); - body = editor.getBody(); - - // Selection is collapsed but the editor isn't empty - if (isCollapsed && !dom.isEmpty(body)) { - return; - } - - // Selection isn't collapsed but not all the contents is selected - if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { - return; - } - - // Manually empty the editor - e.preventDefault(); - editor.setContent(''); - - if (body.firstChild && dom.isBlock(body.firstChild)) { - editor.selection.setCursorLocation(body.firstChild, 0); - } else { - editor.selection.setCursorLocation(body, 0); - } - - editor.nodeChanged(); - } - }); - } - - /** - * WebKit doesn't select all the nodes in the body when you press Ctrl+A. - * IE selects more than the contents [a
] instead of[a]
see bug #6438 - * This selects the whole body so that backspace/delete logic will delete everything - */ - function selectAll() { - editor.on('keydown', function(e) { - if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) { - e.preventDefault(); - editor.execCommand('SelectAll'); - } - }); - } - - /** - * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. - * The IME on Mac doesn't initialize when it doesn't fire a proper focus event. - * - * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until - * you enter a character into the editor. - * - * It also happens when the first focus in made to the body. - * - * See: https://bugs.webkit.org/show_bug.cgi?id=83566 - */ - function inputMethodFocus() { - if (!editor.settings.content_editable) { - // Case 1 IME doesn't initialize if you focus the document - dom.bind(editor.getDoc(), 'focusin', function() { - selection.setRng(selection.getRng()); - }); - - // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event - dom.bind(editor.getDoc(), 'mousedown', function(e) { - if (e.target == editor.getDoc().documentElement) { - editor.getBody().focus(); - selection.setRng(selection.getRng()); - } - }); - } - } - - /** - * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the - * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is - * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js - * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other - * browsers. - * - * It also fixes a bug on Firefox where it's impossible to delete HR elements. - */ - function removeHrOnBackspace() { - editor.on('keydown', function(e) { - if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { - if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { - var node = selection.getNode(); - var previousSibling = node.previousSibling; - - if (node.nodeName == 'HR') { - dom.remove(node); - e.preventDefault(); - return; - } - - if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { - dom.remove(previousSibling); - e.preventDefault(); - } - } - } - }); - } - - /** - * Firefox 3.x has an issue where the body element won't get proper focus if you click out - * side it's rectangle. - */ - function focusBody() { - // Fix for a focus bug in FF 3.x where the body element - // wouldn't get proper focus if the user clicked on the HTML element - if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 - editor.on('mousedown', function(e) { - if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { - var body = editor.getBody(); - - // Blur the body it's focused but not correctly focused - body.blur(); - - // Refocus the body after a little while - setTimeout(function() { - body.focus(); - }, 0); - } - }); - } - } - - /** - * WebKit has a bug where it isn't possible to select image, hr or anchor elements - * by clicking on them so we need to fake that. - */ - function selectControlElements() { - editor.on('click', function(e) { - e = e.target; - - // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 - // WebKit can't even do simple things like selecting an image - // Needs tobe the setBaseAndExtend or it will fail to select floated images - if (/^(IMG|HR)$/.test(e.nodeName)) { - selection.getSel().setBaseAndExtent(e, 0, e, 1); - } - - if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) { - selection.select(e); - } - - editor.nodeChanged(); - }); - } - - /** - * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements. - * - * Fixes do backspace/delete on this: - *bla[ck
r]ed
- * - * Would become: - *bla|ed
- * - * Instead of: - *bla|ed
- */ - function removeStylesWhenDeletingAcrossBlockElements() { - function getAttributeApplyFunction() { - var template = dom.getAttribs(selection.getStart().cloneNode(false)); - - return function() { - var target = selection.getStart(); - - if (target !== editor.getBody()) { - dom.setAttrib(target, "style", null); - - each(template, function(attr) { - target.setAttributeNode(attr.cloneNode(true)); - }); - } - }; - } - - function isSelectionAcrossElements() { - return !selection.isCollapsed() && - dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); - } - - editor.on('keypress', function(e) { - var applyAttributes; - - if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { - applyAttributes = getAttributeApplyFunction(); - editor.getDoc().execCommand('delete', false, null); - applyAttributes(); - e.preventDefault(); - return false; - } - }); - - dom.bind(editor.getDoc(), 'cut', function(e) { - var applyAttributes; - - if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { - applyAttributes = getAttributeApplyFunction(); - - setTimeout(function() { - applyAttributes(); - }, 0); - } - }); - } - - /** - * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange - * event every 50ms since it would other wise update the UI when you type and it hogs the CPU. - */ - function selectionChangeNodeChanged() { - var lastRng, selectionTimer; - - editor.on('selectionchange', function() { - if (selectionTimer) { - clearTimeout(selectionTimer); - selectionTimer = 0; - } - - selectionTimer = window.setTimeout(function() { - if (editor.removed) { - return; - } - - var rng = selection.getRng(); - - // Compare the ranges to see if it was a real change or not - if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) { - editor.nodeChanged(); - lastRng = rng; - } - }, 50); - }); - } - - /** - * Screen readers on IE needs to have the role application set on the body. - */ - function ensureBodyHasRoleApplication() { - document.body.setAttribute("role", "application"); - } - - /** - * Backspacing into a table behaves differently depending upon browser type. - * Therefore, disable Backspace when cursor immediately follows a table. - */ - function disableBackspaceIntoATable() { - editor.on('keydown', function(e) { - if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { - if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { - var previousSibling = selection.getNode().previousSibling; - if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { - e.preventDefault(); - return false; - } - } - } - }); - } - - /** - * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this - * logic adds a \n before the BR so that it will get rendered. - */ - function addNewLinesBeforeBrInPre() { - // IE8+ rendering mode does the right thing with BR in PRE - if (getDocumentMode() > 7) { - return; - } - - // Enable display: none in area and add a specific class that hides all BR elements in PRE to - // avoid the caret from getting stuck at the BR elements while pressing the right arrow key - setEditorCommandState('RespectVisibilityInDesign', true); - editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); - dom.addClass(editor.getBody(), 'mceHideBrInPre'); - - // Adds a \n before all BR elements in PRE to get them visual - parser.addNodeFilter('pre', function(nodes) { - var i = nodes.length, brNodes, j, brElm, sibling; - - while (i--) { - brNodes = nodes[i].getAll('br'); - j = brNodes.length; - while (j--) { - brElm = brNodes[j]; - - // Add \n before BR in PRE elements on older IE:s so the new lines get rendered - sibling = brElm.prev; - if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { - sibling.value += '\n'; - } else { - brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n'; - } - } - } - }); - - // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible - serializer.addNodeFilter('pre', function(nodes) { - var i = nodes.length, brNodes, j, brElm, sibling; - - while (i--) { - brNodes = nodes[i].getAll('br'); - j = brNodes.length; - while (j--) { - brElm = brNodes[j]; - sibling = brElm.prev; - if (sibling && sibling.type == 3) { - sibling.value = sibling.value.replace(/\r?\n$/, ''); - } - } - } - }); - } - - /** - * Moves style width/height to attribute width/height when the user resizes an image on IE. - */ - function removePreSerializedStylesWhenSelectingControls() { - dom.bind(editor.getBody(), 'mouseup', function() { - var value, node = selection.getNode(); - - // Moved styles to attributes on IMG eements - if (node.nodeName == 'IMG') { - // Convert style width to width attribute - if ((value = dom.getStyle(node, 'width'))) { - dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); - dom.setStyle(node, 'width', ''); - } - - // Convert style height to height attribute - if ((value = dom.getStyle(node, 'height'))) { - dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); - dom.setStyle(node, 'height', ''); - } - } - }); - } - - /** - * Removes a blockquote when backspace is pressed at the beginning of it. - * - * For example: - *- * - * Becomes: - *|x
|x
- */ - function removeBlockQuoteOnBackSpace() { - // Add block quote deletion handler - editor.on('keydown', function(e) { - var rng, container, offset, root, parent; - - if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { - return; - } - - rng = selection.getRng(); - container = rng.startContainer; - offset = rng.startOffset; - root = dom.getRoot(); - parent = container; - - if (!rng.collapsed || offset !== 0) { - return; - } - - while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { - parent = parent.parentNode; - } - - // Is the cursor at the beginning of a blockquote? - if (parent.tagName === 'BLOCKQUOTE') { - // Remove the blockquote - editor.formatter.toggle('blockquote', null, parent); - - // Move the caret to the beginning of container - rng = dom.createRng(); - rng.setStart(container, 0); - rng.setEnd(container, 0); - selection.setRng(rng); - } - }); - } - - /** - * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc. - */ - function setGeckoEditingOptions() { - function setOpts() { - editor._refreshContentEditable(); - - setEditorCommandState("StyleWithCSS", false); - setEditorCommandState("enableInlineTableEditing", false); - - if (!settings.object_resizing) { - setEditorCommandState("enableObjectResizing", false); - } - } - - if (!settings.readonly) { - editor.on('BeforeExecCommand MouseDown', setOpts); - } - } - - /** - * Fixes a gecko link bug, when a link is placed at the end of block elements there is - * no way to move the caret behind the link. This fix adds a bogus br element after the link. - * - * For example this: - * - * - * Becomes this: - * - */ - function addBrAfterLastLinks() { - function fixLinks() { - each(dom.select('a'), function(node) { - var parentNode = node.parentNode, root = dom.getRoot(); - - if (parentNode.lastChild === node) { - while (parentNode && !dom.isBlock(parentNode)) { - if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { - return; - } - - parentNode = parentNode.parentNode; - } - - dom.add(parentNode, 'br', {'data-mce-bogus': 1}); - } - }); - } - - editor.on('SetContent ExecCommand', function(e) { - if (e.type == "setcontent" || e.command === 'mceInsertLink') { - fixLinks(); - } - }); - } - - /** - * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by - * default we want to change that behavior. - */ - function setDefaultBlockType() { - if (settings.forced_root_block) { - editor.on('init', function() { - setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); - }); - } - } - - /** - * Removes ghost selections from images/tables on Gecko. - */ - function removeGhostSelection() { - editor.on('Undo Redo SetContent', function(e) { - if (!e.initial) { - editor.execCommand('mceRepaint'); - } - }); - } - - /** - * Deletes the selected image on IE instead of navigating to previous page. - */ - function deleteControlItemOnBackSpace() { - editor.on('keydown', function(e) { - var rng; - - if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { - rng = editor.getDoc().selection.createRange(); - if (rng && rng.item) { - e.preventDefault(); - editor.undoManager.beforeChange(); - dom.remove(rng.item(0)); - editor.undoManager.add(); - } - } - }); - } - - /** - * IE10 doesn't properly render block elements with the right height until you add contents to them. - * This fixes that by adding a padding-right to all empty text block elements. - * See: https://connect.microsoft.com/IE/feedback/details/743881 - */ - function renderEmptyBlocksFix() { - var emptyBlocksCSS; - - // IE10+ - if (getDocumentMode() >= 10) { - emptyBlocksCSS = ''; - each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { - emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; - }); - - editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); - } - } - - /** - * Old IE versions can't retain contents within noscript elements so this logic will store the contents - * as a attribute and the insert that value as it's raw text when the DOM is serialized. - */ - function keepNoScriptContents() { - if (getDocumentMode() < 9) { - parser.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node, textNode; - - while (i--) { - node = nodes[i]; - textNode = node.firstChild; - - if (textNode) { - node.attr('data-mce-innertext', textNode.value); - } - } - }); - - serializer.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node, textNode, value; - - while (i--) { - node = nodes[i]; - textNode = nodes[i].firstChild; - - if (textNode) { - textNode.value = Entities.decode(textNode.value); - } else { - // Old IE can't retain noscript value so an attribute is used to store it - value = node.attributes.map['data-mce-innertext']; - if (value) { - node.attr('data-mce-innertext', null); - textNode = new Node('#text', 3); - textNode.value = value; - textNode.raw = true; - node.append(textNode); - } - } - } - }); - } - } - - /** - * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode. - */ - function fixCaretSelectionOfDocumentElementOnIe() { - var doc = dom.doc, body = doc.body, started, startRng, htmlElm; - - // Return range from point or null if it failed - function rngFromPoint(x, y) { - var rng = body.createTextRange(); - - try { - rng.moveToPoint(x, y); - } catch (ex) { - // IE sometimes throws and exception, so lets just ignore it - rng = null; - } - - return rng; - } - - // Fires while the selection is changing - function selectionChange(e) { - var pointRng; - - // Check if the button is down or not - if (e.button) { - // Create range from mouse position - pointRng = rngFromPoint(e.x, e.y); - - if (pointRng) { - // Check if pointRange is before/after selection then change the endPoint - if (pointRng.compareEndPoints('StartToStart', startRng) > 0) { - pointRng.setEndPoint('StartToStart', startRng); - } else { - pointRng.setEndPoint('EndToEnd', startRng); - } - - pointRng.select(); - } - } else { - endSelection(); - } - } - - // Removes listeners - function endSelection() { - var rng = doc.selection.createRange(); - - // If the range is collapsed then use the last start range - if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) { - startRng.select(); - } - - dom.unbind(doc, 'mouseup', endSelection); - dom.unbind(doc, 'mousemove', selectionChange); - startRng = started = 0; - } - - // Make HTML element unselectable since we are going to handle selection by hand - doc.documentElement.unselectable = true; - - // Detect when user selects outside BODY - dom.bind(doc, 'mousedown contextmenu', function(e) { - if (e.target.nodeName === 'HTML') { - if (started) { - endSelection(); - } - - // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML - htmlElm = doc.documentElement; - if (htmlElm.scrollHeight > htmlElm.clientHeight) { - return; - } - - started = 1; - // Setup start position - startRng = rngFromPoint(e.x, e.y); - if (startRng) { - // Listen for selection change events - dom.bind(doc, 'mouseup', endSelection); - dom.bind(doc, 'mousemove', selectionChange); - - dom.getRoot().focus(); - startRng.select(); - } - } - }); - } - - /** - * Fixes selection issues where the caret can be placed between two inline elements like a|b - * this fix will lean the caret right into the closest inline element. - */ - function normalizeSelection() { - // Normalize selection for example a|a becomes a|a except for Ctrl+A since it selects everything - editor.on('keyup focusin mouseup', function(e) { - if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { - selection.normalize(); - } - }, true); - } - - /** - * Forces Gecko to render a broken image icon if it fails to load an image. - */ - function showBrokenImageIcon() { - editor.contentStyles.push( - 'img:-moz-broken {' + - '-moz-force-broken-image-icon:1;' + - 'min-width:24px;' + - 'min-height:24px' + - '}' - ); - } - - /** - * iOS has a bug where it's impossible to type if the document has a touchstart event - * bound and the user touches the document while having the on screen keyboard visible. - * - * The touch event moves the focus to the parent document while having the caret inside the iframe - * this fix moves the focus back into the iframe document. - */ - function restoreFocusOnKeyDown() { - if (!editor.inline) { - editor.on('keydown', function() { - if (document.activeElement == document.body) { - editor.getWin().focus(); - } - }); - } - } - - /** - * IE 11 has an annoying issue where you can't move focus into the editor - * by clicking on the white area HTML element. We used to be able to to fix this with - * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection - * object it's not possible anymore. So we need to hack in a ungly CSS to force the - * body to be at least 150px. If the user clicks the HTML element out side this 150px region - * we simply move the focus into the first paragraph. Not ideal since you loose the - * positioning of the caret but goot enough for most cases. - */ - function bodyHeight() { - if (!editor.inline) { - editor.contentStyles.push('body {min-height: 150px}'); - editor.on('click', function(e) { - if (e.target.nodeName == 'HTML') { - editor.getBody().focus(); - editor.selection.normalize(); - editor.nodeChanged(); - } - }); - } - } - - /** - * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. - * You might then loose all your work so we need to block that behavior and replace it with our own. - */ - function blockCmdArrowNavigation() { - if (Env.mac) { - editor.on('keydown', function(e) { - if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) { - e.preventDefault(); - editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word'); - } - }); - } - } - - /** - * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin. - */ - function disableAutoUrlDetect() { - setEditorCommandState("AutoUrlDetect", false); - } - - /** - * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when - * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync - * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML - * but not as the lastChild of the body. However is we add a BR element to the body then remove it - * it doesn't seem to add these BR elements makes sence right?! - * - * Example of what happens: text becomes text]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
[\r\n]*)$/, '');
- });
- }
-
- self.load({initial: true, format: 'html'});
- self.startContent = self.getContent({format: 'raw'});
-
- /**
- * Is set to true after the editor instance has been initialized
- *
- * @property initialized
- * @type Boolean
- * @example
- * function isEditorInitialized(editor) {
- * return editor && editor.initialized;
- * }
- */
- self.initialized = true;
-
- each(self._pendingNativeEvents, function(name) {
- self.dom.bind(getEventTarget(self, name), name, function(e) {
- self.fire(e.type, e);
- });
- });
-
- self.fire('init');
- self.focus(true);
- self.nodeChanged({initial: true});
- self.execCallback('init_instance_callback', self);
-
- // Add editor specific CSS styles
- if (self.contentStyles.length > 0) {
- contentCssText = '';
-
- each(self.contentStyles, function(style) {
- contentCssText += style + "\r\n";
- });
-
- self.dom.addStyle(contentCssText);
- }
-
- // Load specified content CSS last
- each(self.contentCSS, function(cssUrl) {
- if (!self.loadedCSS[cssUrl]) {
- self.dom.loadCSS(cssUrl);
- self.loadedCSS[cssUrl] = true;
- }
- });
-
- // Handle auto focus
- if (settings.auto_focus) {
- setTimeout(function () {
- var ed = self.editorManager.get(settings.auto_focus);
-
- ed.selection.select(ed.getBody(), 1);
- ed.selection.collapse(1);
- ed.getBody().focus();
- ed.getWin().focus();
- }, 100);
- }
-
- // Clean up references for IE
- targetElm = doc = body = null;
- },
-
- /**
- * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
- * it will also place DOM focus inside the editor.
- *
- * @method focus
- * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
- */
- focus: function(skip_focus) {
- var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
- var controlElm, doc = self.getDoc(), body;
-
- if (!skip_focus) {
- // Get selected control element
- rng = selection.getRng();
- if (rng.item) {
- controlElm = rng.item(0);
- }
-
- self._refreshContentEditable();
-
- // Focus the window iframe
- if (!contentEditable) {
- // WebKit needs this call to fire focusin event properly see #5948
- // But Opera pre Blink engine will produce an empty selection so skip Opera
- if (!Env.opera) {
- self.getBody().focus();
- }
-
- self.getWin().focus();
- }
-
- // Focus the body as well since it's contentEditable
- if (isGecko || contentEditable) {
- body = self.getBody();
-
- // Check for setActive since it doesn't scroll to the element
- if (body.setActive) {
- // IE 11 sometimes throws "Invalid function" then fallback to focus
- try {
- body.setActive();
- } catch (ex) {
- body.focus();
- }
- } else {
- body.focus();
- }
-
- if (contentEditable) {
- selection.normalize();
- }
- }
-
- // Restore selected control element
- // This is needed when for example an image is selected within a
- // layer a call to focus will then remove the control selection
- if (controlElm && controlElm.ownerDocument == doc) {
- rng = doc.body.createControlRange();
- rng.addElement(controlElm);
- rng.select();
- }
- }
-
- if (self.editorManager.activeEditor != self) {
- if ((oed = self.editorManager.activeEditor)) {
- oed.fire('deactivate', {relatedTarget: self});
- }
-
- self.fire('activate', {relatedTarget: oed});
- }
-
- self.editorManager.activeEditor = self;
- },
-
- /**
- * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
- * There new event model is a better way to add callback so this method might be removed in the future.
- *
- * @method execCallback
- * @param {String} name Name of the callback to execute.
- * @return {Object} Return value passed from callback function.
- */
- execCallback: function(name) {
- var self = this, callback = self.settings[name], scope;
-
- if (!callback) {
- return;
- }
-
- // Look through lookup
- if (self.callbackLookup && (scope = self.callbackLookup[name])) {
- callback = scope.func;
- scope = scope.scope;
- }
-
- if (typeof(callback) === 'string') {
- scope = callback.replace(/\.\w+$/, '');
- scope = scope ? resolve(scope) : 0;
- callback = resolve(callback);
- self.callbackLookup = self.callbackLookup || {};
- self.callbackLookup[name] = {func: callback, scope: scope};
- }
-
- return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
- },
-
- /**
- * Translates the specified string by replacing variables with language pack items it will also check if there is
- * a key mathcin the input.
- *
- * @method translate
- * @param {String} text String to translate by the language pack data.
- * @return {String} Translated string.
- */
- translate: function(text) {
- var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
-
- if (!text) {
- return '';
- }
-
- return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
- return i18n.data[lang + '.' + b] || '{#' + b + '}';
- });
- },
-
- /**
- * Returns a language pack item by name/key.
- *
- * @method getLang
- * @param {String} name Name/key to get from the language pack.
- * @param {String} defaultVal Optional default value to retrive.
- */
- getLang: function(name, defaultVal) {
- return (
- this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
- (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
- );
- },
-
- /**
- * Returns a configuration parameter by name.
- *
- * @method getParam
- * @param {String} name Configruation parameter to retrive.
- * @param {String} defaultVal Optional default value to return.
- * @param {String} type Optional type parameter.
- * @return {String} Configuration parameter value or default value.
- * @example
- * // Returns a specific config value from the currently active editor
- * var someval = tinymce.activeEditor.getParam('myvalue');
- *
- * // Returns a specific config value from a specific editor instance by id
- * var someval2 = tinymce.get('my_editor').getParam('myvalue');
- */
- getParam: function(name, defaultVal, type) {
- var value = name in this.settings ? this.settings[name] : defaultVal, output;
-
- if (type === 'hash') {
- output = {};
-
- if (typeof(value) === 'string') {
- each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
- value = value.split('=');
-
- if (value.length > 1) {
- output[trim(value[0])] = trim(value[1]);
- } else {
- output[trim(value[0])] = trim(value);
- }
- });
- } else {
- output = value;
- }
-
- return output;
- }
-
- return value;
- },
-
- /**
- * Distpaches out a onNodeChange event to all observers. This method should be called when you
- * need to update the UI states or element path etc.
- *
- * @method nodeChanged
- */
- nodeChanged: function() {
- var self = this, selection = self.selection, node, parents, root;
-
- // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
- if (self.initialized && !self.settings.disable_nodechange && !self.settings.readonly) {
- // Get start node
- root = self.getBody();
- node = selection.getStart() || root;
- node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
-
- // Edge case for
|
- if (node.nodeName == 'IMG' && selection.isCollapsed()) { - node = node.parentNode; - } - - // Get parents and add them to object - parents = []; - self.dom.getParent(node, function(node) { - if (node === root) { - return true; - } - - parents.push(node); - }); - - self.fire('NodeChange', {element: node, parents: parents}); - } - }, - - /** - * Adds a button that later gets created by the theme in the editors toolbars. - * - * @method addButton - * @param {String} name Button name to add. - * @param {Object} settings Settings object with title, cmd etc. - * @example - * // Adds a custom button to the editor that inserts contents when clicked - * tinymce.init({ - * ... - * - * toolbar: 'example' - * - * setup: function(ed) { - * ed.addButton('example', { - * title: 'My title', - * image: '../js/tinymce/plugins/example/img/example.gif', - * onclick: function() { - * ed.insertContent('Hello world!!'); - * } - * }); - * } - * }); - */ - addButton: function(name, settings) { - var self = this; - - if (settings.cmd) { - settings.onclick = function() { - self.execCommand(settings.cmd); - }; - } - - if (!settings.text && !settings.icon) { - settings.icon = name; - } - - self.buttons = self.buttons || {}; - settings.tooltip = settings.tooltip || settings.title; - self.buttons[name] = settings; - }, - - /** - * Adds a menu item to be used in the menus of the theme. There might be multiple instances - * of this menu item for example it might be used in the main menus of the theme but also in - * the context menu so make sure that it's self contained and supports multiple instances. - * - * @method addMenuItem - * @param {String} name Menu item name to add. - * @param {Object} settings Settings object with title, cmd etc. - * @example - * // Adds a custom menu item to the editor that inserts contents when clicked - * // The context option allows you to add the menu item to an existing default menu - * tinymce.init({ - * ... - * - * setup: function(ed) { - * ed.addMenuItem('example', { - * text: 'My menu item', - * context: 'tools', - * onclick: function() { - * ed.insertContent('Hello world!!'); - * } - * }); - * } - * }); - */ - addMenuItem: function(name, settings) { - var self = this; - - if (settings.cmd) { - settings.onclick = function() { - self.execCommand(settings.cmd); - }; - } - - self.menuItems = self.menuItems || {}; - self.menuItems[name] = settings; - }, - - /** - * Adds a custom command to the editor, you can also override existing commands with this method. - * The command that you add can be executed with execCommand. - * - * @method addCommand - * @param {String} name Command name to add/override. - * @param {addCommandCallback} callback Function to execute when the command occurs. - * @param {Object} scope Optional scope to execute the function in. - * @example - * // Adds a custom command that later can be executed using execCommand - * tinymce.init({ - * ... - * - * setup: function(ed) { - * // Register example command - * ed.addCommand('mycommand', function(ui, v) { - * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'})); - * }); - * } - * }); - */ - addCommand: function(name, callback, scope) { - /** - * Callback function that gets called when a command is executed. - * - * @callback addCommandCallback - * @param {Boolean} ui Display UI state true/false. - * @param {Object} value Optional value for command. - * @return {Boolean} True/false state if the command was handled or not. - */ - this.execCommands[name] = {func: callback, scope: scope || this}; - }, - - /** - * Adds a custom query state command to the editor, you can also override existing commands with this method. - * The command that you add can be executed with queryCommandState function. - * - * @method addQueryStateHandler - * @param {String} name Command name to add/override. - * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs. - * @param {Object} scope Optional scope to execute the function in. - */ - addQueryStateHandler: function(name, callback, scope) { - /** - * Callback function that gets called when a queryCommandState is executed. - * - * @callback addQueryStateHandlerCallback - * @return {Boolean} True/false state if the command is enabled or not like is it bold. - */ - this.queryStateCommands[name] = {func: callback, scope: scope || this}; - }, - - /** - * Adds a custom query value command to the editor, you can also override existing commands with this method. - * The command that you add can be executed with queryCommandValue function. - * - * @method addQueryValueHandler - * @param {String} name Command name to add/override. - * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs. - * @param {Object} scope Optional scope to execute the function in. - */ - addQueryValueHandler: function(name, callback, scope) { - /** - * Callback function that gets called when a queryCommandValue is executed. - * - * @callback addQueryValueHandlerCallback - * @return {Object} Value of the command or undefined. - */ - this.queryValueCommands[name] = {func: callback, scope: scope || this}; - }, - - /** - * Adds a keyboard shortcut for some command or function. - * - * @method addShortcut - * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. - * @param {String} desc Text description for the command. - * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. - * @param {Object} sc Optional scope to execute the function in. - * @return {Boolean} true/false state if the shortcut was added or not. - */ - addShortcut: function(pattern, desc, cmdFunc, scope) { - this.shortcuts.add(pattern, desc, cmdFunc, scope); - }, - - /** - * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or - * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. - * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these - * return true it will handle the command as a internal browser command. - * - * @method execCommand - * @param {String} cmd Command name to execute, for example mceLink or Bold. - * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. - * @param {mixed} value Optional command value, this can be anything. - * @param {Object} a Optional arguments object. - */ - execCommand: function(cmd, ui, value, args) { - var self = this, state = 0, cmdItem; - - if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) { - self.focus(); - } - - args = extend({}, args); - args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value}); - if (args.isDefaultPrevented()) { - return false; - } - - // Registred commands - if ((cmdItem = self.execCommands[cmd])) { - // Fall through on true - if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) { - self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); - return true; - } - } - - // Plugin commands - each(self.plugins, function(p) { - if (p.execCommand && p.execCommand(cmd, ui, value)) { - self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); - state = true; - return false; - } - }); - - if (state) { - return state; - } - - // Theme commands - if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) { - self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); - return true; - } - - // Editor commands - if (self.editorCommands.execCommand(cmd, ui, value)) { - self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); - return true; - } - - // Browser commands - self.getDoc().execCommand(cmd, ui, value); - self.fire('ExecCommand', {command: cmd, ui: ui, value: value}); - }, - - /** - * Returns a command specific state, for example if bold is enabled or not. - * - * @method queryCommandState - * @param {string} cmd Command to query state from. - * @return {Boolean} Command specific state, for example if bold is enabled or not. - */ - queryCommandState: function(cmd) { - var self = this, queryItem, returnVal; - - // Is hidden then return undefined - if (self._isHidden()) { - return; - } - - // Registred commands - if ((queryItem = self.queryStateCommands[cmd])) { - returnVal = queryItem.func.call(queryItem.scope); - - // Fall though on true - if (returnVal !== true) { - return returnVal; - } - } - - // Editor commands - returnVal = self.editorCommands.queryCommandState(cmd); - if (returnVal !== -1) { - return returnVal; - } - - // Browser commands - try { - return self.getDoc().queryCommandState(cmd); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - /** - * Returns a command specific value, for example the current font size. - * - * @method queryCommandValue - * @param {string} cmd Command to query value from. - * @return {Object} Command specific value, for example the current font size. - */ - queryCommandValue: function(cmd) { - var self = this, queryItem, returnVal; - - // Is hidden then return undefined - if (self._isHidden()) { - return; - } - - // Registred commands - if ((queryItem = self.queryValueCommands[cmd])) { - returnVal = queryItem.func.call(queryItem.scope); - - // Fall though on true - if (returnVal !== true) { - return returnVal; - } - } - - // Editor commands - returnVal = self.editorCommands.queryCommandValue(cmd); - if (returnVal !== undefined) { - return returnVal; - } - - // Browser commands - try { - return self.getDoc().queryCommandValue(cmd); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - /** - * Shows the editor and hides any textarea/div that the editor is supposed to replace. - * - * @method show - */ - show: function() { - var self = this; - - DOM.show(self.getContainer()); - DOM.hide(self.id); - self.load(); - self.fire('show'); - }, - - /** - * Hides the editor and shows any textarea/div that the editor is supposed to replace. - * - * @method hide - */ - hide: function() { - var self = this, doc = self.getDoc(); - - // Fixed bug where IE has a blinking cursor left from the editor - if (ie && doc && !self.inline) { - doc.execCommand('SelectAll'); - } - - // We must save before we hide so Safari doesn't crash - self.save(); - - // defer the call to hide to prevent an IE9 crash #4921 - DOM.hide(self.getContainer()); - DOM.setStyle(self.id, 'display', self.orgDisplay); - self.fire('hide'); - }, - - /** - * Returns true/false if the editor is hidden or not. - * - * @method isHidden - * @return {Boolean} True/false if the editor is hidden or not. - */ - isHidden: function() { - return !DOM.isHidden(this.id); - }, - - /** - * Sets the progress state, this will display a throbber/progess for the editor. - * This is ideal for asycronous operations like an AJAX save call. - * - * @method setProgressState - * @param {Boolean} state Boolean state if the progress should be shown or hidden. - * @param {Number} time Optional time to wait before the progress gets shown. - * @return {Boolean} Same as the input state. - * @example - * // Show progress for the active editor - * tinymce.activeEditor.setProgressState(true); - * - * // Hide progress for the active editor - * tinymce.activeEditor.setProgressState(false); - * - * // Show progress after 3 seconds - * tinymce.activeEditor.setProgressState(true, 3000); - */ - setProgressState: function(state, time) { - this.fire('ProgressState', {state: state, time: time}); - }, - - /** - * Loads contents from the textarea or div element that got converted into an editor instance. - * This method will move the contents from that textarea or div into the editor by using setContent - * so all events etc that method has will get dispatched as well. - * - * @method load - * @param {Object} args Optional content object, this gets passed around through the whole load process. - * @return {String} HTML string that got set into the editor. - */ - load: function(args) { - var self = this, elm = self.getElement(), html; - - if (elm) { - args = args || {}; - args.load = true; - - html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args); - args.element = elm; - - if (!args.no_events) { - self.fire('LoadContent', args); - } - - args.element = elm = null; - - return html; - } - }, - - /** - * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. - * This method will move the HTML contents from the editor into that textarea or div by getContent - * so all events etc that method has will get dispatched as well. - * - * @method save - * @param {Object} args Optional content object, this gets passed around through the whole save process. - * @return {String} HTML string that got set into the textarea/div. - */ - save: function(args) { - var self = this, elm = self.getElement(), html, form; - - if (!elm || !self.initialized) { - return; - } - - args = args || {}; - args.save = true; - - args.element = elm; - html = args.content = self.getContent(args); - - if (!args.no_events) { - self.fire('SaveContent', args); - } - - html = args.content; - - if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) { - // Update DIV element when not in inline mode - if (!self.inline) { - elm.innerHTML = html; - } - - // Update hidden form element - if ((form = DOM.getParent(self.id, 'form'))) { - each(form.elements, function(elm) { - if (elm.name == self.id) { - elm.value = html; - return false; - } - }); - } - } else { - elm.value = html; - } - - args.element = elm = null; - - if (args.set_dirty !== false) { - self.isNotDirty = true; - } - - return html; - }, - - /** - * Sets the specified content to the editor instance, this will cleanup the content before it gets set using - * the different cleanup rules options. - * - * @method setContent - * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. - * @param {Object} args Optional content object, this gets passed around through the whole set process. - * @return {String} HTML string that got set into the editor. - * @example - * // Sets the HTML contents of the activeEditor editor - * tinymce.activeEditor.setContent('some html'); - * - * // Sets the raw contents of the activeEditor editor - * tinymce.activeEditor.setContent('some html', {format: 'raw'}); - * - * // Sets the content of a specific editor (my_editor in this example) - * tinymce.get('my_editor').setContent(data); - * - * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added - * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'}); - */ - setContent: function(content, args) { - var self = this, body = self.getBody(), forcedRootBlockName; - - // Setup args object - args = args || {}; - args.format = args.format || 'html'; - args.set = true; - args.content = content; - - // Do preprocessing - if (!args.no_events) { - self.fire('BeforeSetContent', args); - } - - content = args.content; - - // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content - // It will also be impossible to place the caret in the editor unless there is a BR element present - if (content.length === 0 || /^\s+$/.test(content)) { - forcedRootBlockName = self.settings.forced_root_block; - - // Check if forcedRootBlock is configured and that the block is a valid child of the body - if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) { - // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly - content = ie && ie < 11 ? '' : '