// 4.0.17 (2014-02-xx) /** * 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*/ 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() { if (doc.readyState === "complete") { 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 === 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; // 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; 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 */ /* * 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) { 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; var trim = function(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() { var node = this, 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 + ")"; } return elems.length === 1 ? DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [] : DomQuery.find.matches(expr, 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 */ 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) { var top, right, bottom, left; // IE 11 will produce a border-image: none when getting the style attribute from // So lets asume it shouldn't be there if (styles['border-image'] === 'none') { delete styles['border-image']; } // Get values and check it it needs compressing top = styles[prefix + '-top' + suffix]; if (!top) { return; } right = styles[prefix + '-right' + suffix]; if (top != right) { return; } bottom = styles[prefix + '-bottom' + suffix]; if (right != bottom) { return; } left = styles[prefix + '-left' + suffix]; if (bottom != left) { return; } // Compress styles[prefix + suffix] = left; 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 || value.indexOf(' ') < 0) { 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() { // 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. */ requireLangPack: function(name) { if (AddOnManager.language && AddOnManager.languageLoad !== false) { ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + AddOnManager.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} n Short name of the add-on that gets loaded. * @param {String} u URL to the add-on that will get loaded. * @param {function} cb Optional callback to execute ones the add-on is loaded. * @param {Object} s 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(n, u, cb, s) { var t = this, url = u; function loadDependencies() { var dependencies = t.dependencies(n); each(dependencies, function(dep) { var newUrl = t.createUrl(u, dep); t.load(newUrl.resource, newUrl, undefined, undefined); }); if (cb) { if (s) { cb.call(s); } else { cb.call(ScriptLoader); } } } if (t.urls[n]) { return; } if (typeof u === "object") { url = u.prefix + u.resource + u.suffix; } if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) { url = AddOnManager.baseURL + '/' + url; } t.urls[n] = url.substring(0, url.lastIndexOf('/')); if (t.lookup[n]) { loadDependencies(); } else { ScriptLoader.ScriptLoader.add(url, loadDependencies, s); } } }; 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 likea
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. schema是一些策略, 有些合法, 一些不合法的 */ 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 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 */ 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.ctrlKey || 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) { if (editor.settings.object_resizing === false) { return false; } if (!/TABLE|IMG|DIV/.test(elm.nodeName)) { return false; } if (elm.getAttribute('data-mce-resize') === 'false') { return false; } return true; } 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(); // 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) { resizeStarted = true; 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', contentEditable: false, // Hides IE move layer cursor unSelectabe: true, style: 'cursor:' + name + '-resize; margin:0; padding:0' }); dom.bind(handleElm, 'mousedown', function(e) { e.preventDefault(); startDrag(e); }); } else { dom.show(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; 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) { 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 (controlElm) { 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 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(); } }); } } 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); }); function destroy() { selectedElm = selectedElmGhost = null; if (isIE) { detachResizeStartListener(); detachEvent(editor.getBody(), 'controlselect', nativeControlSelect); } } return { controlSelect: controlSelect, destroy: destroy }; }; }); // Included from: js/tinymce/classes/dom/Selection.js /** * Selection.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class handles text and control selection it's an crossbrowser utility class. * Consult the TinyMCE Wiki API for more details and examples on how to use this class. * * @class tinymce.dom.Selection * @example * // Getting the currently selected node for the active editor * alert(tinymce.activeEditor.selection.getNode().nodeName); */ define("tinymce/dom/Selection", [ "tinymce/dom/TreeWalker", "tinymce/dom/TridentSelection", "tinymce/dom/ControlSelection", "tinymce/Env", "tinymce/util/Tools" ], function(TreeWalker, TridentSelection, ControlSelection, Env, Tools) { var each = Tools.each, grep = Tools.grep, trim = Tools.trim; var isIE = Env.ie, isOpera = Env.opera; /** * Constructs a new selection instance. * * @constructor * @method Selection * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference. * @param {Window} win Window to bind the selection object to. * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent. */ function Selection(dom, win, serializer, editor) { var self = this; self.dom = dom; self.win = win; self.serializer = serializer; self.editor = editor; self.controlSelection = new ControlSelection(self, editor); // No W3C Range support if (!self.win.getSelection) { self.tridentSel = new TridentSelection(self); } } Selection.prototype = { /** * Move the selection cursor range to the specified node and offset. * * @method setCursorLocation * @param {Node} node Node to put the cursor in. * @param {Number} offset Offset from the start of the node to put the cursor at. */ setCursorLocation: function(node, offset) { var self = this, rng = self.dom.createRng(); rng.setStart(node, offset); rng.setEnd(node, offset); self.setRng(rng); self.collapse(false); }, /** * Returns the selected contents using the DOM serializer passed in to this class. * * @method getContent * @param {Object} s Optional settings class with for example output format text or html. * @return {String} Selected contents in for example HTML format. * @example * // Alerts the currently selected contents * alert(tinymce.activeEditor.selection.getContent()); * * // Alerts the currently selected contents as plain text * alert(tinymce.activeEditor.selection.getContent({format: 'text'})); */ getContent: function(args) { var self = this, rng = self.getRng(), tmpElm = self.dom.create("body"); var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment; args = args || {}; whiteSpaceBefore = whiteSpaceAfter = ''; args.get = true; args.format = args.format || 'html'; args.selection = true; self.editor.fire('BeforeGetContent', args); if (args.format == 'text') { return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : '')); } if (rng.cloneContents) { fragment = rng.cloneContents(); if (fragment) { tmpElm.appendChild(fragment); } } else if (rng.item !== undefined || rng.htmlText !== undefined) { // IE will produce invalid markup if elements are present that // it doesn't understand like custom elements or HTML5 elements. // Adding a BR in front of the contents and then remoiving it seems to fix it though. tmpElm.innerHTML = ']
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 = t.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)); } t.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 t = this, dom = t.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 = 'x|
*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 'return getEditorContent(); // 这个有XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, '')); } function addNonTypingUndoLevel() { self.typing = false; // log('addNonTypingUndoLevel') self.add(); } // Add initial undo level when the editor is initialized editor.on('init', function() { self.add(); }); // Get position before an execCommand is processed editor.on('BeforeExecCommand', function(e) { var cmd = e.command; if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { self.beforeChange(); } }); // Add undo level after an execCommand call was made editor.on('ExecCommand', function(e) { var cmd = e.command; if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { self.add(); } }); editor.on('ObjectResizeStart', function() { self.beforeChange(); }); editor.on('SaveContent ObjectResized', addNonTypingUndoLevel); editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); editor.dom.bind(editor.getBody(), 'focusout', function() { if (!editor.removed && self.typing) { addNonTypingUndoLevel(); } }); editor.on('KeyUp', function(e) { var keyCode = e.keyCode; // ctrl + shift + c, command + shift + c 代码 // life ace if((e.metaKey && e.shiftKey) || (e.ctrlKey && e.shiftKey)) { return; } // 在ace中回车也会加history if(keyCode == 13 && LeaAce.nowIsInAce()) { return; } if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { addNonTypingUndoLevel(); editor.nodeChanged(); } // 8 表示删除 if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) { editor.nodeChanged(); } // Fire a TypingUndo event on the first character entered if (isFirstTypedCharacter && self.typing) { // Make the it dirty if the content was changed after typing the first character if (!editor.isDirty()) { editor.isNotDirty = !data[0] || getContent() == data[0].content; // Fire initial change event if (!editor.isNotDirty) { editor.fire('change', {level: data[0], lastLevel: null}); } } editor.fire('TypingUndo'); isFirstTypedCharacter = false; editor.nodeChanged(); } }); editor.on('KeyDown', function(e) { var keyCode = e.keyCode; log('keyCode' + keyCode) // 在ace中回车也会加history if(keyCode == 13/* && LeaAce.nowIsInAce()*/) { return; } // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { if (self.typing) { addNonTypingUndoLevel(); } return; } // ctrl + shift + c, command + shift + c 代码 // life ace if((e.metaKey && e.shiftKey) || (e.ctrlKey || e.shiftKey)) { log("no add history"); return; } // ctrl + z if(e.metaKey && keyCode == 90 || (e.ctrlKey && keyCode == 90)) { log('ctrl + z'); return; } // If key isn't shift,ctrl,alt,capslock,metakey if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { self.beforeChange(); self.typing = true; self.add(); isFirstTypedCharacter = true; } }); editor.on('MouseDown', function() { if (self.typing) { addNonTypingUndoLevel(); } }); // Add keyboard shortcuts for undo/redo keys editor.addShortcut('ctrl+z', '', 'Undo'); editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo'); editor.on('AddUndo Undo Redo ClearUndos MouseUp', function(e) { if (!e.isDefaultPrevented()) { editor.nodeChanged(); } }); self = { // Explose for debugging reasons data: data, /** * State if the user is currently typing or not. This will add a typing operation into one undo * level instead of one new level for each keystroke. * * @field {Boolean} typing */ typing: false, /** * Stores away a bookmark to be used when performing an undo action so that the selection is before * the change has been made. * * @method beforeChange */ beforeChange: function() { if (!lock) { beforeBookmark = editor.selection.getBookmark(2, true); } }, setCanAdd: function(status) { canAdd = status; }, /** * Adds a new undo level/snapshot to the undo list. * * @method add * @param {Object} l Optional undo level object to add. * @return {Object} Undo level that got added or null it a level wasn't needed. */ add: function(level) { if(!canAdd) { log('cant add history') return; } log('add history') var i, settings = editor.settings, lastLevel; level = level || {}; level.content = getContent(); if (lock || editor.fire('BeforeAddUndo', {level: level}).isDefaultPrevented()) { return null; } // Add undo level if needed lastLevel = data[index]; if (lastLevel && lastLevel.content == level.content) { return null; } // Set before bookmark on previous level if (data[index]) { data[index].beforeBookmark = beforeBookmark; } // Time to compress if (settings.custom_undo_redo_levels) { if (data.length > settings.custom_undo_redo_levels) { for (i = 0; i < data.length - 1; i++) { data[i] = data[i + 1]; } data.length--; index = data.length; } } // Get a non intrusive normalized bookmark level.bookmark = editor.selection.getBookmark(2, true); // Crop array if needed if (index < data.length - 1) { data.length = index + 1; } data.push(level); index = data.length - 1; var args = {level: level, lastLevel: lastLevel}; editor.fire('AddUndo', args); if (index > 0) { editor.fire('change', args); editor.isNotDirty = false; } return level; }, /** * Undoes the last action. * * @method undo * @return {Object} Undo level or null if no undo was performed. */ undo: function() { var level; if (self.typing) { self.add(); self.typing = false; } if (index > 0) { level = data[--index]; // Undo to first index then set dirty state to false if (index === 0) { editor.isNotDirty = true; } editor.setContent(level.content, {format: 'raw'}); // undo后如果有pre, 那么要重新init下 // life ace // LeaAce.undo(editor); editor.selection.moveToBookmark(level.beforeBookmark); editor.fire('undo', {level: level}); } return level; }, /** * Redoes the last action. * * @method redo * @return {Object} Redo level or null if no redo was performed. */ redo: function() { var level; if (index < data.length - 1) { level = data[++index]; editor.setContent(level.content, {format: 'raw'}); editor.selection.moveToBookmark(level.bookmark); editor.fire('redo', {level: level}); } return level; }, /** * Removes all undo levels. * * @method clear */ clear: function() { data = []; index = 0; self.typing = false; editor.fire('ClearUndos'); }, /** * Returns true/false if the undo manager has any undo levels. * * @method hasUndo * @return {Boolean} true/false if the undo manager has any undo levels. */ hasUndo: function() { // Has undo levels or typing and content isn't the same as the initial level return index > 0 || (self.typing && data[0] && getContent() != data[0].content); }, /** * Returns true/false if the undo manager has any redo levels. * * @method hasRedo * @return {Boolean} true/false if the undo manager has any redo levels. */ hasRedo: function() { return index < data.length - 1 && !this.typing; }, /** * Executes the specified function in an undo transation. The selection * before the modification will be stored to the undo stack and if the DOM changes * it will add a new undo level. Any methods within the transation that adds undo levels will * be ignored. So a transation can include calls to execCommand or editor.insertContent. * * @method transact * @param {function} callback Function to execute dom manipulation logic in. */ transact: function(callback) { self.beforeChange(); lock = true; callback(); lock = false; self.add(); } }; return self; }; }); // Included from: js/tinymce/classes/EnterKey.js /** * EnterKey.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Contains logic for handling the enter key to split/generate block elements. */ define("tinymce/EnterKey", [ "tinymce/dom/TreeWalker", "tinymce/Env" ], function(TreeWalker, Env) { var isIE = Env.ie && Env.ie < 11; return function(editor) { var dom = editor.dom, selection = editor.selection, settings = editor.settings; var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(); function handleEnterKey(evt) { var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; // Returns true if the block can be split into two blocks or not function canSplitBlock(node) { return node && dom.isBlock(node) && !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && !/^(fixed|absolute)/i.test(node.style.position) && dom.getContentEditable(node) !== "true"; } // Renders empty block on IE function renderBlockOnIE(block) { var oldRng; if (dom.isBlock(block)) { oldRng = selection.getRng(); block.appendChild(dom.create('span', null, '\u00a0')); selection.select(block); block.lastChild.outerHTML = ''; selection.setRng(oldRng); } } // Remove the first empty inline element of the block so this: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; } } if (root.nodeName == 'LI') { var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild); if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) { root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild); } } rng = dom.createRng(); if (root.hasChildNodes()) { walker = new TreeWalker(root, root); while ((node = walker.current())) { if (node.nodeType == 3) { rng.setStart(node, 0); rng.setEnd(node, 0); break; } if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { rng.setStartBefore(node); rng.setEndBefore(node); break; } lastNode = node; node = walker.next(); } if (!node) { rng.setStart(lastNode, 0); rng.setEnd(lastNode, 0); } } else { if (root.nodeName == 'BR') { if (root.nextSibling && dom.isBlock(root.nextSibling)) { // Trick on older IE versions to render the caret before the BR between two lists if (!documentMode || documentMode < 9) { tempElm = dom.create('br'); root.parentNode.insertBefore(tempElm, root); } rng.setStartBefore(root); rng.setEndBefore(root); } else { rng.setStartAfter(root); rng.setEndAfter(root); } } else { rng.setStart(root, 0); rng.setEnd(root, 0); } } selection.setRng(rng); // Remove tempElm created for old IE:s dom.remove(tempElm); selection.scrollIntoView(root); } function setForcedBlockAttrs(node) { var forcedRootBlockName = settings.forced_root_block; if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) { dom.setAttribs(node, settings.forced_root_block_attrs); } } // Creates a new block element by cloning the current one or creating a new one if the name is specified // This function will also copy any text formatting from the parent block and add it to the new one function createNewBlock(name) { var node = container, block, clonedNode, caretNode; if (name || parentBlockName == "TABLE") { block = dom.create(name || newBlockName); setForcedBlockAttrs(block); } else { block = parentBlock.cloneNode(false); } caretNode = block; // Clone any parent styles if (settings.keep_styles !== false) { do { if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { // Never clone a caret containers if (node.id == '_mce_caret') { continue; } clonedNode = node.cloneNode(false); dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique if (block.hasChildNodes()) { clonedNode.appendChild(block.firstChild); block.appendChild(clonedNode); } else { caretNode = clonedNode; block.appendChild(clonedNode); } } } while ((node = node.parentNode)); } // BR is needed in empty blocks on non IE browsers if (!isIE) { caretNode.innerHTML = '
'; } return block; } // Returns true/false if the caret is at the start/end of the parent block element function isCaretAtStartOrEndOfBlock(start) { var walker, node, name; // Caret is in the middle of a text node like "a|b" if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { return false; } // If after the last element in block node edge case for #5091 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { return true; } // If the caret if before the first element in parentBlock if (start && container.nodeType == 1 && container == parentBlock.firstChild) { return true; } // Caret can be before/after a table if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); } // Walk the DOM and look for text nodes or non empty elements walker = new TreeWalker(container, parentBlock); // If caret is in beginning or end of a text block then jump to the next/previous node if (container.nodeType == 3) { if (start && offset === 0) { walker.prev(); } else if (!start && offset == container.nodeValue.length) { walker.next(); } } while ((node = walker.current())) { if (node.nodeType === 1) { // Ignore bogus elements if (!node.getAttribute('data-mce-bogus')) { // Keep empty elements likebut not trailing br:s like
text|
name = node.nodeName.toLowerCase(); if (nonEmptyElementsMap[name] && name !== 'br') { return false; } } } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { return false; } if (start) { walker.prev(); } else { walker.next(); } } return true; } // Wraps any text nodes or inline elements in the specified forced root block name function wrapSelfAndSiblingsInDefaultBlock(container, offset) { var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P'; // Not in a block element or in a table cell or caption parentBlock = dom.getParent(container, dom.isBlock); rootBlockName = editor.getBody().nodeName.toLowerCase(); if (!parentBlock || !canSplitBlock(parentBlock)) { parentBlock = parentBlock || editableRoot; if (!parentBlock.hasChildNodes()) { newBlock = dom.create(blockName); setForcedBlockAttrs(newBlock); parentBlock.appendChild(newBlock); rng.setStart(newBlock, 0); rng.setEnd(newBlock, 0); return newBlock; } // Find parent that is the first child of parentBlock node = container; while (node.parentNode != parentBlock) { node = node.parentNode; } // Loop left to find start node start wrapping at while (node && !dom.isBlock(node)) { startNode = node; node = node.previousSibling; } if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) { newBlock = dom.create(blockName); setForcedBlockAttrs(newBlock); startNode.parentNode.insertBefore(newBlock, startNode); // Start wrapping until we hit a block node = startNode; while (node && !dom.isBlock(node)) { next = node.nextSibling; newBlock.appendChild(node); node = next; } // Restore range to it's past location rng.setStart(container, offset); rng.setEnd(container, offset); } } return container; } // Inserts a block or br before/after or in the middle of a split list of the LI is empty function handleEmptyListItem() { function isFirstOrLastLi(first) { var node = containerBlock[first ? 'firstChild' : 'lastChild']; // Find first/last element since there might be whitespace there while (node) { if (node.nodeType == 1) { break; } node = node[first ? 'nextSibling' : 'previousSibling']; } return node === parentBlock; } function getContainerBlock() { var containerBlockParent = containerBlock.parentNode; if (containerBlockParent.nodeName == 'LI') { return containerBlockParent; } return containerBlock; } // Check if we are in an nested list var containerBlockParentName = containerBlock.parentNode.nodeName; if (/^(OL|UL|LI)$/.test(containerBlockParentName)) { newBlockName = 'LI'; } newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); if (isFirstOrLastLi(true) && isFirstOrLastLi()) { if (containerBlockParentName == 'LI') { // Nested list is inside a LI dom.insertAfter(newBlock, getContainerBlock()); } else { // Is first and last list item then replace the OL/UL with a text block dom.replace(newBlock, containerBlock); } } else if (isFirstOrLastLi(true)) { if (containerBlockParentName == 'LI') { // List nested in an LI then move the list to a new sibling LI dom.insertAfter(newBlock, getContainerBlock()); newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed newBlock.appendChild(containerBlock); } else { // First LI in list then remove LI and add text block before list containerBlock.parentNode.insertBefore(newBlock, containerBlock); } } else if (isFirstOrLastLi()) { // Last LI in list then remove LI and add text block after list dom.insertAfter(newBlock, getContainerBlock()); renderBlockOnIE(newBlock); } else { // Middle LI in list the split the list and insert a text block in the middle // Extract after fragment and insert it after the current block containerBlock = getContainerBlock(); tmpRng = rng.cloneRange(); tmpRng.setStartAfter(parentBlock); tmpRng.setEndAfter(containerBlock); fragment = tmpRng.extractContents(); if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') { newBlock = fragment.firstChild; dom.insertAfter(fragment, containerBlock); } else { dom.insertAfter(fragment, containerBlock); dom.insertAfter(newBlock, containerBlock); } } dom.remove(parentBlock); moveToCaretPosition(newBlock); undoManager.add(); } // Walks the parent block to the right and look for BR elements function hasRightSideContent() { var walker = new TreeWalker(container, parentBlock), node; while ((node = walker.next())) { if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { return true; } } } // Inserts a BR element if the forced_root_block option is set to false or empty string function insertBr() { var brElm, extraBr, marker; if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { // Insert extra BR element at the end block elements if (!isIE && !hasRightSideContent()) { brElm = dom.create('br'); rng.insertNode(brElm); rng.setStartAfter(brElm); rng.setEndAfter(brElm); extraBr = true; } } brElm = dom.create('br'); rng.insertNode(brElm); // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it if (isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); } // Insert temp marker and scroll to that marker = dom.create('span', {}, ' '); brElm.parentNode.insertBefore(marker, brElm); selection.scrollIntoView(marker); dom.remove(marker); if (!extraBr) { rng.setStartAfter(brElm); rng.setEndAfter(brElm); } else { rng.setStartBefore(brElm); rng.setEndBefore(brElm); } selection.setRng(rng); undoManager.add(); } // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element function trimLeadingLineBreaks(node) { do { if (node.nodeType === 3) { node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); } node = node.firstChild; } while (node); } function getEditableRoot(node) { var root = dom.getRoot(), parent, editableRoot; // Get all parents until we hit a non editable parent or the root parent = node; while (parent !== root && dom.getContentEditable(parent) !== "false") { if (dom.getContentEditable(parent) === "true") { editableRoot = parent; } parent = parent.parentNode; } return parent !== root ? editableRoot : root; } // Adds a BR at the end of blocks that only contains an IMG or INPUT since // these might be floated and then they won't expand the block function addBrToBlockIfNeeded(block) { var lastChild; // IE will render the blocks correctly other browsers needs a BR if (!isIE) { block.normalize(); // Remove empty text nodes that got left behind by the extract // Check if the block is empty or contains a floated last child lastChild = block.lastChild; if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { dom.add(block, 'br'); } } } // Delete any selected contents if (!rng.collapsed) { editor.execCommand('Delete'); return; } // Event is blocked by some other handler for example the lists plugin if (evt.isDefaultPrevented()) { return; } // Setup range items and newBlockName container = rng.startContainer; // life ace, 在ace editor下回车给ace来控制 var $container = $(container); var aceEditorAndPre = LeaAce.isInAce($container); if(aceEditorAndPre) { // alert(2); // 跳出aceEditor if(evt.shiftKey) { // alert(33); // var id = $container.attr('id'); var aceEditor = aceEditorAndPre[0]; aceEditor.blur(); var pre = aceEditorAndPre[1]; // 必须要延迟 setTimeout(function() { aceEditor.blur(); // log('blur...') var newBlock = $(""); pre.after(newBlock); // log(newBlock.get(0)); rng.setStart(newBlock.get(0), 0); rng.setEnd(newBlock.get(0), 0); // 没用 rng.selectNode(newBlock.get(0)); selection.setRng(rng); // 再延迟 setTimeout(function() { selection.setRng(rng); }, 10); // TODO ... 这里 // log("why"); // log(rng); // log(selection.getRng()); }, 10); return true; } return false; } offset = rng.startOffset; newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; documentMode = dom.doc.documentMode; shiftKey = evt.shiftKey; // Resolve node index if (container.nodeType == 1 && container.hasChildNodes()) { isAfterLastNodeInContainer = offset > container.childNodes.length - 1; container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; if (isAfterLastNodeInContainer && container.nodeType == 3) { offset = container.nodeValue.length; } else { offset = 0; } } // Get editable root node normaly the body element but sometimes a div or span editableRoot = getEditableRoot(container); // If there is no editable root then enter is done inside a contentEditable false element if (!editableRoot) { return; } undoManager.beforeChange(); // If editable root isn't block nor the root of the editor if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { if (!newBlockName || shiftKey) { insertBr(); } return; } // Wrap the current node and it's sibling in a default block if it's needed. // for example this
text|text2 will become this// This won't happen if root blocks are disabled or the shiftKey is pressed if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { container = wrapSelfAndSiblingsInDefaultBlock(container, offset); } // Find parent block and setup empty block paddings parentBlock = dom.getParent(container, dom.isBlock); containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; // Setup block names parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 // Enter inside block contained within a LI then split or insert before/after LI if (containerBlockName == 'LI' && !evt.ctrlKey) { parentBlock = containerBlock; parentBlockName = containerBlockName; } // Handle enter in LI if (parentBlockName == 'LI') { if (!newBlockName && shiftKey) { insertBr(); return; } // Handle enter inside an empty list item if (dom.isEmpty(parentBlock)) { handleEmptyListItem(); return; } } // Don't split PRE tags but insert a BR instead easier when writing code samples etc if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { if (!shiftKey) { insertBr(); return; } } else { // If no root block is configured then insert a BR by default or if the shiftKey is pressed if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { insertBr(); return; } } // If parent block is root then never insert new blocks if (newBlockName && parentBlock === editor.getBody()) { return; } // Default block name if it's not configured newBlockName = newBlockName || 'P'; // Insert new block before/after the parent block depending on caret location if (isCaretAtStartOrEndOfBlock()) { // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { newBlock = createNewBlock(newBlockName); } else { newBlock = createNewBlock(); } // Split the current container block element if enter is pressed inside an empty inner block element if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P newBlock = dom.split(containerBlock, parentBlock); } else { dom.insertAfter(newBlock, parentBlock); } moveToCaretPosition(newBlock); } else if (isCaretAtStartOrEndOfBlock(true)) { // Insert new block before newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); renderBlockOnIE(newBlock); moveToCaretPosition(parentBlock); } else { // Extract after fragment and insert it after the current block tmpRng = rng.cloneRange(); tmpRng.setEndAfter(parentBlock); fragment = tmpRng.extractContents(); trimLeadingLineBreaks(fragment); newBlock = fragment.firstChild; dom.insertAfter(fragment, parentBlock); trimInlineElementsOnLeftSideOfBlock(newBlock); addBrToBlockIfNeeded(parentBlock); moveToCaretPosition(newBlock); } dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique // Allow custom handling of new blocks editor.fire('NewBlock', { newBlock: newBlock }); undoManager.add(); } editor.on('keydown', function(evt) { if (evt.keyCode == 13) { if (handleEnterKey(evt) !== false) { evt.preventDefault(); } } }); }; }); // Included from: js/tinymce/classes/ForceBlocks.js /** * ForceBlocks.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define("tinymce/ForceBlocks", [], function() { return function(editor) { var settings = editor.settings, dom = editor.dom, selection = editor.selection; var schema = editor.schema, blockElements = schema.getBlockElements(); function addRootBlocks() { var node = selection.getStart(), rootNode = editor.getBody(), rng; var startContainer, startOffset, endContainer, endOffset, rootBlockNode; var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection; var tmpRng, rootNodeName, forcedRootBlock; forcedRootBlock = settings.forced_root_block; if (!node || node.nodeType !== 1 || !forcedRootBlock) { return; } // Check if node is wrapped in block while (node && node != rootNode) { if (blockElements[node.nodeName]) { return; } node = node.parentNode; } // Get current selection rng = selection.getRng(); if (rng.setStart) { startContainer = rng.startContainer; startOffset = rng.startOffset; endContainer = rng.endContainer; endOffset = rng.endOffset; try { restoreSelection = editor.getDoc().activeElement === rootNode; } catch (ex) { // IE throws unspecified error here sometimes } } else { // Force control range into text range if (rng.item) { node = rng.item(0); rng = editor.getDoc().body.createTextRange(); rng.moveToElementText(node); } restoreSelection = rng.parentElement().ownerDocument === editor.getDoc(); tmpRng = rng.duplicate(); tmpRng.collapse(true); startOffset = tmpRng.move('character', offset) * -1; if (!tmpRng.collapsed) { tmpRng = rng.duplicate(); tmpRng.collapse(false); endOffset = (tmpRng.move('character', offset) * -1) - startOffset; } } // Wrap non block elements and text nodes node = rootNode.firstChild; rootNodeName = rootNode.nodeName.toLowerCase(); while (node) { // TODO: Break this up, too complex if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) && schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) { // Remove empty text nodes if (node.nodeType === 3 && node.nodeValue.length === 0) { tempNode = node; node = node.nextSibling; dom.remove(tempNode); continue; } if (!rootBlockNode) { rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs); node.parentNode.insertBefore(rootBlockNode, node); wrapped = true; } tempNode = node; node = node.nextSibling; rootBlockNode.appendChild(tempNode); } else { rootBlockNode = null; node = node.nextSibling; } } if (wrapped && restoreSelection) { if (rng.setStart) { rng.setStart(startContainer, startOffset); rng.setEnd(endContainer, endOffset); selection.setRng(rng); } else { // Only select if the previous selection was inside the document to prevent auto focus in quirks mode try { rng = editor.getDoc().body.createTextRange(); rng.moveToElementText(rootNode); rng.collapse(true); rng.moveStart('character', startOffset); if (endOffset > 0) { rng.moveEnd('character', endOffset); } rng.select(); } catch (ex) { // Ignore } } editor.nodeChanged(); } } // Force root blocks if (settings.forced_root_block) { editor.on('NodeChange', addRootBlocks); } }; }); // Included from: js/tinymce/classes/EditorCommands.js /** * EditorCommands.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class enables you to add custom editor commands and it contains * overrides for native browser commands to address various bugs and issues. * * @class tinymce.EditorCommands */ define("tinymce/EditorCommands", [ "tinymce/html/Serializer", "tinymce/Env", "tinymce/util/Tools" ], function(Serializer, Env, Tools) { // Added for compression purposes var each = Tools.each, extend = Tools.extend; var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode; var isGecko = Env.gecko, isIE = Env.ie; var TRUE = true, FALSE = false; return function(editor) { var dom = editor.dom, selection = editor.selection, commands = {state: {}, exec: {}, value: {}}, settings = editor.settings, formatter = editor.formatter, bookmark; /** * Executes the specified command. * * @method execCommand * @param {String} command Command to execute. * @param {Boolean} ui Optional user interface state. * @param {Object} value Optional value for command. * @return {Boolean} true/false if the command was found or not. */ function execCommand(command, ui, value) { var func; command = command.toLowerCase(); if ((func = commands.exec[command])) { func(command, ui, value); return TRUE; } return FALSE; } /** * Queries the current state for a command for example if the current selection is "bold". * * @method queryCommandState * @param {String} command Command to check the state of. * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. */ function queryCommandState(command) { var func; command = command.toLowerCase(); if ((func = commands.state[command])) { return func(command); } return -1; } /** * Queries the command value for example the current fontsize. * * @method queryCommandValue * @param {String} command Command to check the value of. * @return {Object} Command value of false if it's not found. */ function queryCommandValue(command) { var func; command = command.toLowerCase(); if ((func = commands.value[command])) { return func(command); } return FALSE; } /** * Adds commands to the command collection. * * @method addCommands * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated. * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. */ function addCommands(command_list, type) { type = type || 'exec'; each(command_list, function(callback, command) { each(command.toLowerCase().split(','), function(command) { commands[type][command] = callback; }); }); } // Expose public methods extend(this, { execCommand: execCommand, queryCommandState: queryCommandState, queryCommandValue: queryCommandValue, addCommands: addCommands }); // Private methods function execNativeCommand(command, ui, value) { if (ui === undefined) { ui = FALSE; } if (value === undefined) { value = null; } return editor.getDoc().execCommand(command, ui, value); } function isFormatMatch(name) { return formatter.match(name); } function toggleFormat(name, value) { formatter.toggle(name, value ? {value: value} : undefined); editor.nodeChanged(); } function storeSelection(type) { bookmark = selection.getBookmark(type); } function restoreSelection() { selection.moveToBookmark(bookmark); } // Add execCommand overrides addCommands({ // Ignore these, added for compatibility 'mceResetDesignMode,mceBeginUndoLevel': function() {}, // Add undo manager logic 'mceEndUndoLevel,mceAddUndoLevel': function() { editor.undoManager.add(); }, 'Cut,Copy,Paste': function(command) { var doc = editor.getDoc(), failed; // Try executing the native command try { execNativeCommand(command); } catch (ex) { // Command failed failed = TRUE; } // Present alert message about clipboard access not being available if (failed || !doc.queryCommandSupported(command)) { editor.windowManager.alert( "Your browser doesn't support direct access to the clipboard. " + "Please use the Ctrl+X/C/V keyboard shortcuts instead." ); } }, // Override unlink command unlink: function(command) { if (selection.isCollapsed()) { selection.select(selection.getNode()); } execNativeCommand(command); selection.collapse(FALSE); }, // Override justify commands to use the text formatter engine 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { var align = command.substring(7); if (align == 'full') { align = 'justify'; } // Remove all other alignments first each('left,center,right,justify'.split(','), function(name) { if (align != name) { formatter.remove('align' + name); } }); toggleFormat('align' + align); execCommand('mceRepaint'); }, // Override list commands to fix WebKit bug 'InsertUnorderedList,InsertOrderedList': function(command) { var listElm, listParent; execNativeCommand(command); // WebKit produces lists within block elements so we need to split them // we will replace the native list creation logic to custom logic later on // TODO: Remove this when the list creation logic is removed listElm = dom.getParent(selection.getNode(), 'ol,ul'); if (listElm) { listParent = listElm.parentNode; // If list is within a text block then split that block if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { storeSelection(); dom.split(listParent, listElm); restoreSelection(); } } }, // Override commands to use the text formatter engine 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { toggleFormat(command); }, // Override commands to use the text formatter engine 'ForeColor,HiliteColor,FontName': function(command, ui, value) { toggleFormat(command, value); }, FontSize: function(command, ui, value) { var fontClasses, fontSizes; // Convert font size 1-7 to styles if (value >= 1 && value <= 7) { fontSizes = explode(settings.font_size_style_values); fontClasses = explode(settings.font_size_classes); if (fontClasses) { value = fontClasses[value - 1] || value; } else { value = fontSizes[value - 1] || value; } } toggleFormat(command, value); }, RemoveFormat: function(command) { formatter.remove(command); }, mceBlockQuote: function() { toggleFormat('blockquote'); }, FormatBlock: function(command, ui, value) { return toggleFormat(value || 'p'); }, mceCleanup: function() { var bookmark = selection.getBookmark(); editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE}); selection.moveToBookmark(bookmark); }, mceRemoveNode: function(command, ui, value) { var node = value || selection.getNode(); // Make sure that the body node isn't removed if (node != editor.getBody()) { storeSelection(); editor.dom.remove(node, TRUE); restoreSelection(); } }, mceSelectNodeDepth: function(command, ui, value) { var counter = 0; dom.getParent(selection.getNode(), function(node) { if (node.nodeType == 1 && counter++ == value) { selection.select(node); return FALSE; } }, editor.getBody()); }, mceSelectNode: function(command, ui, value) { selection.select(value); }, // 插入内容 mceInsertContent: function(command, ui, value) { var parser, serializer, parentNode, rootNode, fragment, args; var marker, rng, node, node2, bookmarkHtml; function trimOrPaddLeftRight(html) { var rng, container, offset; rng = selection.getRng(true); container = rng.startContainer; offset = rng.startOffset; function hasSiblingText(siblingName) { return container[siblingName] && container[siblingName].nodeType == 3; } if (container.nodeType == 3) { if (offset > 0) { html = html.replace(/^ /, ' '); } else if (!hasSiblingText('previousSibling')) { html = html.replace(/^ /, ' '); } if (offset < container.length) { html = html.replace(/ ( text|text2
|)$/, ' '); } else if (!hasSiblingText('nextSibling')) { html = html.replace(/( | )(
|)$/, ' '); } } return html; } // Check for whitespace before/after value if (/^ | $/.test(value)) { value = trimOrPaddLeftRight(value); } // Setup parser and serializer parser = editor.parser; serializer = new Serializer({}, editor.schema); bookmarkHtml = ''; // Run beforeSetContent handlers on the HTML to be inserted args = {content: value, format: 'html', selection: true}; editor.fire('BeforeSetContent', args); value = args.content; // Add caret at end of contents if it's missing if (value.indexOf('{$caret}') == -1) { value += '{$caret}'; } // Replace the caret marker with a span bookmark element value = value.replace(/\{\$caret\}/, bookmarkHtml); // If selection is at | then move it into|
var body = editor.getBody(); if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { body.firstChild.appendChild(dom.doc.createTextNode('\u00a0')); selection.select(body.firstChild, true); dom.remove(body.firstChild.lastChild); } // 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); }) ) ); // life ace // 插入的时候把内的标签全清掉 // life 把间的代码拿出, 去掉标签之类的 value = value.replace(/
]*?)>([\s\S]*?)<\/pre>/g, function(v, v1, v2) { // v == "a, b, c" v2 = v2.replace(/(<([^>]+)>)/gi, '').replace(/\s+$/, ''); // 把最后一个换行去掉 return "" + v2 + ""; }); // 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(); try { // If previous sibling is a text node set the selection to the end of that node // if(marker) { 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(); // } } catch(e) { } }, // life 修改 // 之前不是这个版本, 为什么要改, 因为考虑到在pre下粘贴后全部内容会修改(ace不友好) // 这里不是改全部的内容, 对ace好 mceInsertRawHTML: function(command, ui, value) { var parser, serializer, parentNode, rootNode, fragment, args; var marker, rng, node, node2, bookmarkHtml; // Setup parser and serializer parser = editor.parser; serializer = new Serializer({}, editor.schema); bookmarkHtml = ''; // Run beforeSetContent handlers on the HTML to be inserted args = {content: value, format: 'html', selection: true}; editor.fire('BeforeSetContent', args); value = args.content; // Add caret at end of contents if it's missing if (value.indexOf('{$caret}') == -1) { value += '{$caret}'; } // Replace the caret marker with a span bookmark element value = value.replace(/\{\$caret\}/, bookmarkHtml); // If selection is at | then move it into|
var body = editor.getBody(); if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { body.firstChild.appendChild(dom.doc.createTextNode('\u00a0')); selection.select(body.firstChild, true); dom.remove(body.firstChild.lastChild); } // 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) { // 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); // 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() }, 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) { var indentStyleName; if (element.nodeName != "LI") { indentStyleName = dom.getStyle(element, 'direction', true) == 'rtl' ? 'paddingRight' : 'paddingLeft'; 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, '
'); }, mceToggleVisualAid: function() { editor.hasVisual = !editor.hasVisual; editor.addVisual(); }, mceReplaceContent: function(command, ui, value) { editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'}))); }, mceInsertLink: function(command, ui, value) { var anchor; if (typeof(value) == 'string') { value = {href: value}; } anchor = dom.getParent(selection.getNode(), 'a'); // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. value.href = value.href.replace(' ', '%20'); // Remove existing links if there could be child links or that the href isn't specified if (!anchor || !value.href) { formatter.remove('link'); } // Apply new link to selection if (value.href) { formatter.apply('link', value, anchor); } }, selectAll: function() { var root = dom.getRoot(), rng = dom.createRng(); // Old IE does a better job with selectall than new versions if (selection.getRng().setStart) { rng.setStart(root, 0); rng.setEnd(root, root.childNodes.length); selection.setRng(rng); } else { execNativeCommand('SelectAll'); } }, 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; } // Absolute path with no host, fake host and protocol if (url.indexOf('/') === 0 && url.indexOf('//') !== 0) { 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; 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 */ 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 = ''; } //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) { 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 + '://'; } 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; // All construction is actually done in the init method if (!initializing) { self = this; // 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 */ /** * 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:* element:not( ) * element:first * element:last * element:odd * element:even * element element * element > element * * @class tinymce.ui.Selector */ define("tinymce/ui/Selector", [ "tinymce/util/Class" ], function(Class) { "use strict"; /** * Produces an array with a unique set of objects. It will not compare the values * but the references of the objects. * * @private * @method unqiue * @param {Array} array Array to make into an array with unique items. * @return {Array} Array with unique items. */ function unique(array) { var uniqueItems = [], i = array.length, item; while (i--) { item = array[i]; if (!item.__checked) { uniqueItems.push(item); item.__checked = 1; } } i = uniqueItems.length; while (i--) { delete uniqueItems[i].__checked; } return uniqueItems; } var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i; /*jshint maxlen:255 */ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, whiteSpace = /^\s*|\s*$/g, Collection; var Selector = Class.extend({ /** * Constructs a new Selector instance. * * @constructor * @method init * @param {String} selector CSS like selector expression. */ init: function(selector) { var match = this.match; function compileNameFilter(name) { if (name) { name = name.toLowerCase(); return function(item) { return name === '*' || item.type === name; }; } } function compileIdFilter(id) { if (id) { return function(item) { return item._name === id; }; } } function compileClassesFilter(classes) { if (classes) { classes = classes.split('.'); return function(item) { var i = classes.length; while (i--) { if (!item.hasClass(classes[i])) { return false; } } return true; }; } } function compileAttrFilter(name, cmp, check) { if (name) { return function(item) { var value = item[name] ? item[name]() : ''; return !cmp ? !!check : cmp === "=" ? value === check : cmp === "*=" ? value.indexOf(check) >= 0 : cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 : cmp === "!=" ? value != check : cmp === "^=" ? value.indexOf(check) === 0 : cmp === "$=" ? value.substr(value.length - check.length) === check : false; }; } } function compilePsuedoFilter(name) { var notSelectors; if (name) { name = /(?:not\((.+)\))|(.+)/i.exec(name); if (!name[1]) { name = name[2]; return function(item, index, length) { return name === 'first' ? index === 0 : name === 'last' ? index === length - 1 : name === 'even' ? index % 2 === 0 : name === 'odd' ? index % 2 === 1 : item[name] ? item[name]() : false; }; } else { // Compile not expression notSelectors = parseChunks(name[1], []); return function(item) { return !match(item, notSelectors); }; } } } function compile(selector, filters, direct) { var parts; function add(filter) { if (filter) { filters.push(filter); } } // Parse expression into parts parts = expression.exec(selector.replace(whiteSpace, '')); add(compileNameFilter(parts[1])); add(compileIdFilter(parts[2])); add(compileClassesFilter(parts[3])); add(compileAttrFilter(parts[4], parts[5], parts[6])); add(compilePsuedoFilter(parts[7])); // Mark the filter with psuedo for performance filters.psuedo = !!parts[7]; filters.direct = direct; return filters; } // Parser logic based on Sizzle by John Resig function parseChunks(selector, selectors) { var parts = [], extra, matches, i; do { chunker.exec(""); matches = chunker.exec(selector); if (matches) { selector = matches[3]; parts.push(matches[1]); if (matches[2]) { extra = matches[3]; break; } } } while (matches); if (extra) { parseChunks(extra, selectors); } selector = []; for (i = 0; i < parts.length; i++) { if (parts[i] != '>') { selector.push(compile(parts[i], [], parts[i - 1] === '>')); } } selectors.push(selector); return selectors; } this._selectors = parseChunks(selector, []); }, /** * Returns true/false if the selector matches the specified control. * * @method match * @param {tinymce.ui.Control} control Control to match agains the selector. * @param {Array} selectors Optional array of selectors, mostly used internally. * @return {Boolean} true/false state if the control matches or not. */ match: function(control, selectors) { var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item; selectors = selectors || this._selectors; for (i = 0, l = selectors.length; i < l; i++) { selector = selectors[i]; sl = selector.length; item = control; count = 0; for (si = sl - 1; si >= 0; si--) { filters = selector[si]; while (item) { // Find the index and length since a psuedo filter like :first needs it if (filters.psuedo) { siblings = item.parent().items(); index = length = siblings.length; while (index--) { if (siblings[index] === item) { break; } } } for (fi = 0, fl = filters.length; fi < fl; fi++) { if (!filters[fi](item, index, length)) { fi = fl + 1; break; } } if (fi === fl) { count++; break; } else { // If it didn't match the right most expression then // break since it's no point looking at the parents if (si === sl - 1) { break; } } item = item.parent(); } } // If we found all selectors then return true otherwise continue looking if (count === sl) { return true; } } return false; }, /** * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container. * * @method find * @param {tinymce.ui.Control} container Container to look for items in. * @return {tinymce.ui.Collection} Collection with matched elements. */ find: function(container) { var matches = [], i, l, selectors = this._selectors; function collect(items, selector, index) { var i, l, fi, fl, item, filters = selector[index]; for (i = 0, l = items.length; i < l; i++) { item = items[i]; // Run each filter agains the item for (fi = 0, fl = filters.length; fi < fl; fi++) { if (!filters[fi](item, i, l)) { fi = fl + 1; break; } } // All filters matched the item if (fi === fl) { // Matched item is on the last expression like: panel toolbar [button] if (index == selector.length - 1) { matches.push(item); } else { // Collect next expression type if (item.items) { collect(item.items(), selector, index + 1); } } } else if (filters.direct) { return; } // Collect child items if (item.items) { collect(item.items(), selector, index); } } } if (container.items) { for (i = 0, l = selectors.length; i < l; i++) { collect(container.items(), selectors[i], 0); } // Unique the matches if needed if (l > 1) { matches = unique(matches); } } // Fix for circular reference if (!Collection) { // TODO: Fix me! Collection = Selector.Collection; } return new Collection(matches); } }); return Selector; }); // Included from: js/tinymce/classes/ui/Collection.js /** * Collection.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Control collection, this class contains control instances and it enables you to * perform actions on all the contained items. This is very similar to how jQuery works. * * @example * someCollection.show().disabled(true); * * @class tinymce.ui.Collection */ define("tinymce/ui/Collection", [ "tinymce/util/Tools", "tinymce/ui/Selector", "tinymce/util/Class" ], function(Tools, Selector, Class) { "use strict"; var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice; proto = { /** * Current number of contained control instances. * * @field length * @type Number */ length: 0, /** * Constructor for the collection. * * @constructor * @method init * @param {Array} items Optional array with items to add. */ init: function(items) { if (items) { this.add(items); } }, /** * Adds new items to the control collection. * * @method add * @param {Array} items Array if items to add to collection. * @return {tinymce.ui.Collection} Current collection instance. */ add: function(items) { var self = this; // Force single item into array if (!Tools.isArray(items)) { if (items instanceof Collection) { self.add(items.toArray()); } else { push.call(self, items); } } else { push.apply(self, items); } return self; }, /** * Sets the contents of the collection. This will remove any existing items * and replace them with the ones specified in the input array. * * @method set * @param {Array} items Array with items to set into the Collection. * @return {tinymce.ui.Collection} Collection instance. */ set: function(items) { var self = this, len = self.length, i; self.length = 0; self.add(items); // Remove old entries for (i = self.length; i < len; i++) { delete self[i]; } return self; }, /** * Filters the collection item based on the specified selector expression or selector function. * * @method filter * @param {String} selector Selector expression to filter items by. * @return {tinymce.ui.Collection} Collection containing the filtered items. */ filter: function(selector) { var self = this, i, l, matches = [], item, match; // Compile string into selector expression if (typeof(selector) === "string") { selector = new Selector(selector); match = function(item) { return selector.match(item); }; } else { // Use selector as matching function match = selector; } for (i = 0, l = self.length; i < l; i++) { item = self[i]; if (match(item)) { matches.push(item); } } return new Collection(matches); }, /** * Slices the items within the collection. * * @method slice * @param {Number} index Index to slice at. * @param {Number} len Optional length to slice. * @return {tinymce.ui.Collection} Current collection. */ slice: function() { return new Collection(slice.apply(this, arguments)); }, /** * Makes the current collection equal to the specified index. * * @method eq * @param {Number} index Index of the item to set the collection to. * @return {tinymce.ui.Collection} Current collection. */ eq: function(index) { return index === -1 ? this.slice(index) : this.slice(index, +index + 1); }, /** * Executes the specified callback on each item in collection. * * @method each * @param {function} callback Callback to execute for each item in collection. * @return {tinymce.ui.Collection} Current collection instance. */ each: function(callback) { Tools.each(this, callback); return this; }, /** * Returns an JavaScript array object of the contents inside the collection. * * @method toArray * @return {Array} Array with all items from collection. */ toArray: function() { return Tools.toArray(this); }, /** * Finds the index of the specified control or return -1 if it isn't in the collection. * * @method indexOf * @param {Control} ctrl Control instance to look for. * @return {Number} Index of the specified control or -1. */ indexOf: function(ctrl) { var self = this, i = self.length; while (i--) { if (self[i] === ctrl) { break; } } return i; }, /** * Returns a new collection of the contents in reverse order. * * @method reverse * @return {tinymce.ui.Collection} Collection instance with reversed items. */ reverse: function() { return new Collection(Tools.toArray(this).reverse()); }, /** * Returns true/false if the class exists or not. * * @method hasClass * @param {String} cls Class to check for. * @return {Boolean} true/false state if the class exists or not. */ hasClass: function(cls) { return this[0] ? this[0].hasClass(cls) : false; }, /** * Sets/gets the specific property on the items in the collection. The same as executing control. ( ); * * @method prop * @param {String} name Property name to get/set. * @param {Object} value Optional object value to set. * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation. */ prop: function(name, value) { var self = this, undef, item; if (value !== undef) { self.each(function(item) { if (item[name]) { item[name](value); } }); return self; } item = self[0]; if (item && item[name]) { return item[name](); } }, /** * Executes the specific function name with optional arguments an all items in collection if it exists. * * @example collection.exec("myMethod", arg1, arg2, arg3); * @method exec * @param {String} name Name of the function to execute. * @param {Object} ... Multiple arguments to pass to each function. * @return {tinymce.ui.Collection} Current collection. */ exec: function(name) { var self = this, args = Tools.toArray(arguments).slice(1); self.each(function(item) { if (item[name]) { item[name].apply(item, args); } }); return self; }, /** * Remove all items from collection and DOM. * * @method remove * @return {tinymce.ui.Collection} Current collection. */ remove: function() { var i = this.length; while (i--) { this[i].remove(); } return this; } /** * 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 Optional arguments to pass to the event. * @return {tinymce.ui.Collection} Current collection instance. */ // fire: function(event, args) {}, -- Generated by code below /** * 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 have two parameters the first one being the control that received the event * the second one will be the event object either the browsers native event object or a custom JS object. * * @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.Collection} Current collection instance. */ // on: function(name, callback) {}, -- Generated by code below /** * 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 Optional name for the event to unbind. * @param {function} callback Optional callback function to unbind. * @return {tinymce.ui.Collection} Current collection instance. */ // off: function(name, callback) {}, -- Generated by code below /** * Shows the items in the current collection. * * @method show * @return {tinymce.ui.Collection} Current collection instance. */ // show: function() {}, -- Generated by code below /** * Hides the items in the current collection. * * @method hide * @return {tinymce.ui.Collection} Current collection instance. */ // hide: function() {}, -- Generated by code below /** * Sets/gets the text contents of the items in the current collection. * * @method text * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation. */ // text: function(value) {}, -- Generated by code below /** * Sets/gets the name contents of the items in the current collection. * * @method name * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation. */ // name: function(value) {}, -- Generated by code below /** * Sets/gets the disabled state on the items in the current collection. * * @method disabled * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation. */ // disabled: function(state) {}, -- Generated by code below /** * Sets/gets the active state on the items in the current collection. * * @method active * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation. */ // active: function(state) {}, -- Generated by code below /** * Sets/gets the selected state on the items in the current collection. * * @method selected * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation. */ // selected: function(state) {}, -- Generated by code below /** * Sets/gets the selected state on the items in the current collection. * * @method visible * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation. */ // visible: function(state) {}, -- Generated by code below /** * Adds a class to all items in the collection. * * @method addClass * @param {String} cls Class to add to each item. * @return {tinymce.ui.Collection} Current collection instance. */ // addClass: function(cls) {}, -- Generated by code below /** * Removes the specified class from all items in collection. * * @method removeClass * @param {String} cls Class to remove from each item. * @return {tinymce.ui.Collection} Current collection instance. */ // removeClass: function(cls) {}, -- Generated by code below }; // Extend tinymce.ui.Collection prototype with some generated control specific methods Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) { proto[name] = function() { var args = Tools.toArray(arguments); this.each(function(ctrl) { if (name in ctrl) { ctrl[name].apply(ctrl, args); } }); return this; }; }); // Extend tinymce.ui.Collection prototype with some property methods Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) { proto[name] = function(value) { return this.prop(name, value); }; }); // Create class based on the new prototype Collection = Class.extend(proto); // Stick Collection into Selector to prevent circual references Selector.Collection = Collection; return Collection; }); // Included from: js/tinymce/classes/ui/DomUtils.js /** * DOMUtils.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define("tinymce/ui/DomUtils", [ "tinymce/util/Tools", "tinymce/dom/DOMUtils" ], function(Tools, DOMUtils) { "use strict"; return { id: function() { return DOMUtils.DOM.uniqueId(); }, createFragment: function(html) { return DOMUtils.DOM.createFragment(html); }, getWindowSize: function() { return DOMUtils.DOM.getViewPort(); }, getSize: function(elm) { var width, height; if (elm.getBoundingClientRect) { var rect = elm.getBoundingClientRect(); width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth); height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight); } else { width = elm.offsetWidth; height = elm.offsetHeight; } return {width: width, height: height}; }, getPos: function(elm, root) { return DOMUtils.DOM.getPos(elm, root); }, getViewPort: function(win) { return DOMUtils.DOM.getViewPort(win); }, get: function(id) { return document.getElementById(id); }, addClass : function(elm, cls) { return DOMUtils.DOM.addClass(elm, cls); }, removeClass : function(elm, cls) { return DOMUtils.DOM.removeClass(elm, cls); }, hasClass : function(elm, cls) { return DOMUtils.DOM.hasClass(elm, cls); }, toggleClass: function(elm, cls, state) { return DOMUtils.DOM.toggleClass(elm, cls, state); }, css: function(elm, name, value) { return DOMUtils.DOM.setStyle(elm, name, value); }, on: function(target, name, callback, scope) { return DOMUtils.DOM.bind(target, name, callback, scope); }, off: function(target, name, callback) { return DOMUtils.DOM.unbind(target, name, callback); }, fire: function(target, name, args) { return DOMUtils.DOM.fire(target, name, args); }, innerHtml: function(elm, html) { // Workaround for inbug 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 */ /** * 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 Control = Class.extend({ Statics: { controlIdLookup: {}, elementIdCache: elementIdCache }, isRtl: function() { return Control.rtl; }, /** * Class/id prefix to use for all controls. * * @final * @field {String} classPrefix */ classPrefix: "mce-", /** * 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 = 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; while (elm) { ctrl = Control.controlIdLookup[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; 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 = rect.x + 'px'; lastRepaintRect.x = rect.x; } if (rect.y !== lastRepaintRect.y) { style.top = rect.y + 'px'; lastRepaintRect.y = rect.y; } if (rect.w !== lastRepaintRect.w) { style.width = (rect.w - borderW) + 'px'; lastRepaintRect.w = rect.w; } if (rect.h !== lastRepaintRect.h) { style.height = (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 = (rect.innerW) + 'px'; lastRepaintRect.innerW = rect.innerW; } if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) { bodyStyle = bodyStyle || self.getEl('body').style; bodyStyle.height = (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.parents().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 = {name: true}; } else { 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 ctrl = this, parents = new Collection(); // Add each parent to collection for (ctrl = ctrl.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 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(); if (typeof(value) === "undefined") { return self._aria[name]; } else { self._aria[name] = value; } if (self._rendered) { if (name == 'label') { elm.setAttribute('aria-labeledby', self._id); } 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 && Control.translate) { text = Control.translate(text); } return (text || '').replace(/[&<>"]/g, function(match) { return '' + match.charCodeAt(0) + ';'; }); }, /** * 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); } delete Control.controlIdLookup[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); } 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
* * This fixes the backspace to produce: *settings for (name in settings) { if (name.indexOf("on") === 0) { self.on(name.substr(2), settings[name]); } } if (self._eventsRoot) { for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) { parentEventsRoot = parent._eventsRoot; } if (parentEventsRoot) { for (name in parentEventsRoot._nativeEvents) { self._nativeEvents[name] = true; } } } self.bindPendingEvents(); if (settings.style) { elm = self.getEl(); if (elm) { elm.setAttribute('style', settings.style); elm.style.cssText = settings.style; } } if (!self._visible) { DomUtils.css(self.getEl(), 'display', 'none'); } if (self.settings.border) { box = self.borderBox(); DomUtils.css(self.getEl(), { 'border-top-width': box.top, 'border-right-width': box.right, 'border-bottom-width': box.bottom, 'border-left-width': box.left }); } // Add instance to lookup Control.controlIdLookup[self._id] = self; for (var key in self._aria) { self.aria(key, self._aria[key]); } self.fire('postrender', {}, false); }, /** * Scrolls the current control into view. * * @method scrollIntoView * @param {String} align Alignment in view top|center|bottom. * @return {tinymce.ui.Control} Current control instance. */ scrollIntoView: function(align) { function getOffset(elm, rootElm) { var x, y, parent = elm; x = y = 0; while (parent && parent != rootElm && parent.nodeType) { x += parent.offsetLeft || 0; y += parent.offsetTop || 0; parent = parent.offsetParent; } return {x: x, y: y}; } var elm = this.getEl(), parentElm = elm.parentNode; var x, y, width, height, parentWidth, parentHeight; var pos = getOffset(elm, parentElm); x = pos.x; y = pos.y; width = elm.offsetWidth; height = elm.offsetHeight; parentWidth = parentElm.clientWidth; parentHeight = parentElm.clientHeight; if (align == "end") { x -= parentWidth - width; y -= parentHeight - height; } else if (align == "center") { x -= (parentWidth / 2) - (width / 2); y -= (parentHeight / 2) - (height / 2); } parentElm.scrollLeft = x; parentElm.scrollTop = y; return this; }, /** * Binds pending DOM events. * * @private */ bindPendingEvents: function() { var self = this, i, l, parents, eventRootCtrl, nativeEvents, name; function delegate(e) { var control = self.getParentCtrl(e.target); if (control) { control.fire(e.type, e); } } function mouseLeaveHandler() { var ctrl = eventRootCtrl._lastHoverCtrl; if (ctrl) { ctrl.fire("mouseleave", {target: ctrl.getEl()}); ctrl.parents().each(function(ctrl) { ctrl.fire("mouseleave", {target: ctrl.getEl()}); }); eventRootCtrl._lastHoverCtrl = null; } } function mouseEnterHandler(e) { var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents; // Over on a new control if (ctrl !== lastCtrl) { eventRootCtrl._lastHoverCtrl = ctrl; parents = ctrl.parents().toArray().reverse(); parents.push(ctrl); if (lastCtrl) { lastParents = lastCtrl.parents().toArray().reverse(); lastParents.push(lastCtrl); for (idx = 0; idx < lastParents.length; idx++) { if (parents[idx] !== lastParents[idx]) { break; } } for (i = lastParents.length - 1; i >= idx; i--) { lastCtrl = lastParents[i]; lastCtrl.fire("mouseleave", { target : lastCtrl.getEl() }); } } for (i = idx; i < parents.length; i++) { ctrl = parents[i]; ctrl.fire("mouseenter", { target : ctrl.getEl() }); } } } function fixWheelEvent(e) { e.preventDefault(); if (e.type == "mousewheel") { e.deltaY = - 1/40 * e.wheelDelta; if (e.wheelDeltaX) { e.deltaX = -1/40 * e.wheelDeltaX; } } else { e.deltaX = 0; e.deltaY = e.detail; } e = self.fire("wheel", e); } self._rendered = true; nativeEvents = self._nativeEvents; if (nativeEvents) { // Find event root element if it exists parents = self.parents().toArray(); parents.unshift(self); for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) { eventRootCtrl = parents[i]._eventsRoot; } // Event root wasn't found the use the root control if (!eventRootCtrl) { eventRootCtrl = parents[parents.length - 1] || self; } // Set the eventsRoot property on children that didn't have it self._eventsRoot = eventRootCtrl; for (l = i, i = 0; i < l; i++) { parents[i]._eventsRoot = eventRootCtrl; } // Bind native event delegates for (name in nativeEvents) { if (!nativeEvents) { return false; } if (name === "wheel" && !hasWheelEventSupport) { if (hasMouseWheelEventSupport) { DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent); } else { DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent); } continue; } // Special treatment for mousenter/mouseleave since these doesn't bubble if (name === "mouseenter" || name === "mouseleave") { // Fake mousenter/mouseleave if (!eventRootCtrl._hasMouseEnter) { DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler); DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler); eventRootCtrl._hasMouseEnter = 1; } } else if (!eventRootCtrl[name]) { DomUtils.on(eventRootCtrl.getEl(), name, delegate); eventRootCtrl[name] = true; } // Remove the event once it's bound nativeEvents[name] = false; } } }, /** * Reflows the current control and it's parents. * This should be used after you for example append children to the current control so * that the layout managers know that they need to reposition everything. * * @example * container.append({type: 'button', text: 'My button'}).reflow(); * * @method reflow * @return {tinymce.ui.Control} Current control instance. */ reflow: function() { this.repaint(); return this; } /** * Sets/gets the parent container for the control. * * @method parent * @param {tinymce.ui.Container} parent Optional parent to set. * @return {tinymce.ui.Control} Parent control or the current control on a set action. */ // parent: function(parent) {} -- Generated /** * Sets/gets the text for the control. * * @method text * @param {String} value Value to set to control. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. */ // text: function(value) {} -- Generated /** * Sets/gets the width for the control. * * @method width * @param {Number} value Value to set to control. * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get. */ // width: function(value) {} -- Generated /** * Sets/gets the height for the control. * * @method height * @param {Number} value Value to set to control. * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get. */ // height: function(value) {} -- Generated /** * Sets/gets the disabled state on the control. * * @method disabled * @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. */ // disabled: function(state) {} -- Generated /** * Sets/gets the active for the control. * * @method active * @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. */ // active: function(state) {} -- Generated /** * Sets/gets the name for the control. * * @method name * @param {String} value Value to set to control. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. */ // name: function(value) {} -- Generated /** * Sets/gets the title for the control. * * @method title * @param {String} value Value to set to control. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. */ // title: function(value) {} -- Generated }); return Control; }); // Included from: js/tinymce/classes/ui/Factory.js /** * Factory.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /*global tinymce:true */ /** * This class is a factory for control instances. This enables you * to create instances of controls without having to require the UI controls directly. * * It also allow you to override or add new control types. * * @class tinymce.ui.Factory */ define("tinymce/ui/Factory", [], function() { "use strict"; var types = {}, namespaceInit; return { /** * Adds a new control instance type to the factory. * * @method add * @param {String} type Type name for example "button". * @param {function} typeClass Class type function. */ add: function(type, typeClass) { types[type.toLowerCase()] = typeClass; }, /** * Returns true/false if the specified type exists or not. * * @method has * @param {String} type Type to look for. * @return {Boolean} true/false if the control by name exists. */ has: function(type) { return !!types[type.toLowerCase()]; }, /** * Creates a new control instance based on the settings provided. The instance created will be * based on the specified type property it can also create whole structures of components out of * the specified JSON object. * * @example * tinymce.ui.Factory.create({ * type: 'button', * text: 'Hello world!' * }); * * @method create * @param {Object/String} settings Name/Value object with items used to create the type. * @return {tinymce.ui.Control} Control instance based on the specified type. */ create: function(type, settings) { var ControlType, name, namespace; // Build type lookup if (!namespaceInit) { namespace = tinymce.ui; for (name in namespace) { types[name.toLowerCase()] = namespace[name]; } namespaceInit = true; } // If string is specified then use it as the type if (typeof(type) == 'string') { settings = settings || {}; settings.type = type; } else { settings = type; type = settings.type; } // Find control type type = type.toLowerCase(); ControlType = types[type]; // #if debug if (!ControlType) { throw new Error("Could not find control by type: " + type); } // #endif ControlType = new ControlType(settings); ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine return ControlType; } }; }); // Included from: js/tinymce/classes/ui/Container.js /** * Container.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Container control. This is extended by all controls that can have * children such as panels etc. You can also use this class directly as an * generic container instance. The container doesn't have any specific role or style. * * @-x-less Container.less * @class tinymce.ui.Container * @extends tinymce.ui.Control */ define("tinymce/ui/Container", [ "tinymce/ui/Control", "tinymce/ui/Collection", "tinymce/ui/Selector", "tinymce/ui/Factory", "tinymce/util/Tools", "tinymce/ui/DomUtils" ], function(Control, Collection, Selector, Factory, Tools, DomUtils) { "use strict"; var selectorCache = {}; return Control.extend({ layout: '', innerClass: 'container-inner', /** * Constructs a new control instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {Array} items Items to add to container in JSON format or control instances. * @setting {String} layout Layout manager by name to use. * @setting {Object} defaults Default settings to apply to all items. */ init: function(settings) { var self = this; self._super(settings); settings = self.settings; self._fixed = settings.fixed; self._items = new Collection(); if (self.isRtl()) { self.addClass('rtl'); } self.addClass('container'); self.addClass('container-body', 'body'); if (settings.containerCls) { self.addClass(settings.containerCls); } self._layout = Factory.create((settings.layout || self.layout) + 'layout'); if (self.settings.items) { self.add(self.settings.items); } // TODO: Fix this! self._hasBody = true; }, /** * Returns a collection of child items that the container currently have. * * @method items * @return {tinymce.ui.Collection} Control collection direct child controls. */ items: function() { return this._items; }, /** * Find child controls by selector. * * @method find * @param {String} selector Selector CSS pattern to find children by. * @return {tinymce.ui.Collection} Control collection with child controls. */ find: function(selector) { selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector); return selector.find(this); }, /** * Adds one or many items to the current container. This will create instances of * the object representations if needed. * * @method add * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container. * @return {tinymce.ui.Collection} Current collection control. */ add: function(items) { var self = this; self.items().add(self.create(items)).parent(self); return self; }, /** * Focuses the current container instance. This will look * for the first control in the container and focus that. * * @method focus * @return {tinymce.ui.Collection} Current instance. */ focus: function() { var self = this; if (self.keyNav) { self.keyNav.focusFirst(); } else { self._super(); } return self; }, /** * Replaces the specified child control with a new control. * * @method replace * @param {tinymce.ui.Control} oldItem Old item to be replaced. * @param {tinymce.ui.Control} newItem New item to be inserted. */ replace: function(oldItem, newItem) { var ctrlElm, items = this.items(), i = items.length; // Replace the item in collection while (i--) { if (items[i] === oldItem) { items[i] = newItem; break; } } if (i >= 0) { // Remove new item from DOM ctrlElm = newItem.getEl(); if (ctrlElm) { ctrlElm.parentNode.removeChild(ctrlElm); } // Remove old item from DOM ctrlElm = oldItem.getEl(); if (ctrlElm) { ctrlElm.parentNode.removeChild(ctrlElm); } } // Adopt the item newItem.parent(this); }, /** * Creates the specified items. If any of the items is plain JSON style objects * it will convert these into real tinymce.ui.Control instances. * * @method create * @param {Array} items Array of items to convert into control instances. * @return {Array} Array with control instances. */ create: function(items) { var self = this, settings, ctrlItems = []; // Non array structure, then force it into an array if (!Tools.isArray(items)) { items = [items]; } // Add default type to each child control Tools.each(items, function(item) { if (item) { // Construct item if needed if (!(item instanceof Control)) { // Name only then convert it to an object if (typeof(item) == "string") { item = {type: item}; } // Create control instance based on input settings and default settings settings = Tools.extend({}, self.settings.defaults, item); item.type = settings.type = settings.type || item.type || self.settings.defaultType || (settings.defaults ? settings.defaults.type : null); item = Factory.create(settings); } ctrlItems.push(item); } }); return ctrlItems; }, /** * Renders new control instances. * * @private */ renderNew: function() { var self = this; // Render any new items self.items().each(function(ctrl, index) { var containerElm, fragment; ctrl.parent(self); if (!ctrl._rendered) { containerElm = self.getEl('body'); fragment = DomUtils.createFragment(ctrl.renderHtml()); // Insert or append the item if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) { containerElm.insertBefore(fragment, containerElm.childNodes[index]); } else { containerElm.appendChild(fragment); } ctrl.postRender(); } }); self._layout.applyClasses(self); self._lastRect = null; return self; }, /** * Appends new instances to the current container. * * @method append * @param {Array/tinymce.ui.Collection} items Array if controls to append. * @return {tinymce.ui.Container} Current container instance. */ append: function(items) { return this.add(items).renderNew(); }, /** * Prepends new instances to the current container. * * @method prepend * @param {Array/tinymce.ui.Collection} items Array if controls to prepend. * @return {tinymce.ui.Container} Current container instance. */ prepend: function(items) { var self = this; self.items().set(self.create(items).concat(self.items().toArray())); return self.renderNew(); }, /** * Inserts an control at a specific index. * * @method insert * @param {Array/tinymce.ui.Collection} items Array if controls to insert. * @param {Number} index Index to insert controls at. * @param {Boolean} [before=false] Inserts controls before the index. */ insert: function(items, index, before) { var self = this, curItems, beforeItems, afterItems; items = self.create(items); curItems = self.items(); if (!before && index < curItems.length - 1) { index += 1; } if (index >= 0 && index < curItems.length) { beforeItems = curItems.slice(0, index).toArray(); afterItems = curItems.slice(index).toArray(); curItems.set(beforeItems.concat(items, afterItems)); } return self.renderNew(); }, /** * Populates the form fields from the specified JSON data object. * * Control items in the form that matches the data will have it's value set. * * @method fromJSON * @param {Object} data JSON data object to set control values by. * @return {tinymce.ui.Container} Current form instance. */ fromJSON: function(data) { var self = this; for (var name in data) { self.find('#' + name).value(data[name]); } return self; }, /** * Serializes the form into a JSON object by getting all items * that has a name and a value. * * @method toJSON * @return {Object} JSON object with form data. */ toJSON: function() { var self = this, data = {}; self.find('*').each(function(ctrl) { var name = ctrl.name(), value = ctrl.value(); if (name && typeof(value) != "undefined") { data[name] = value; } }); return data; }, preRender: function() { }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, layout = self._layout; self.preRender(); layout.preRender(self); return ( ' ' + '' ); }, /** * Post render method. Called after the control has been rendered to the target. * * @method postRender * @return {tinymce.ui.Container} Current combobox instance. */ postRender: function() { var self = this, box; self.items().exec('postRender'); self._super(); self._layout.postRender(self); self._rendered = true; if (self.settings.style) { DomUtils.css(self.getEl(), self.settings.style); } if (self.settings.border) { box = self.borderBox(); DomUtils.css(self.getEl(), { 'border-top-width': box.top, 'border-right-width': box.right, 'border-bottom-width': box.bottom, 'border-left-width': box.left }); } return self; }, /** * 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, layoutRect = self._super(); // Recalc container size by asking layout manager self._layout.recalc(self); return layoutRect; }, /** * Recalculates the positions of the controls in the current container. * This is invoked by the reflow method and shouldn't be called directly. * * @method recalc */ recalc: function() { var self = this, rect = self._layoutRect, lastRect = self._lastRect; if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) { self._layout.recalc(self); rect = self.layoutRect(); self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h}; return true; } }, /** * Reflows the current container and it's children and possible parents. * This should be used after you for example append children to the current control so * that the layout managers know that they need to reposition everything. * * @example * container.append({type: 'button', text: 'My button'}).reflow(); * * @method reflow * @return {tinymce.ui.Container} Current container instance. */ reflow: function() { var i, items; if (this.visible()) { Control.repaintControls = []; Control.repaintControls.map = {}; items = this.recalc(); i = Control.repaintControls.length; while (i--) { Control.repaintControls[i].repaint(); } // TODO: Fix me! if (this.settings.layout !== "flow" && this.settings.layout !== "stack") { this.repaint(); } Control.repaintControls = []; } return this; } }); }); // Included from: js/tinymce/classes/ui/DragHelper.js /** * DragHelper.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Drag/drop helper class. * * @example * var dragHelper = new tinymce.ui.DragHelper('mydiv', { * start: function(evt) { * }, * * drag: function(evt) { * }, * * end: function(evt) { * } * }); * * @class tinymce.ui.DragHelper */ define("tinymce/ui/DragHelper", [ "tinymce/ui/DomUtils" ], function(DomUtils) { "use strict"; function getDocumentSize() { var doc = document, documentElement, body, scrollWidth, clientWidth; var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max; documentElement = doc.documentElement; body = doc.body; scrollWidth = max(documentElement.scrollWidth, body.scrollWidth); clientWidth = max(documentElement.clientWidth, body.clientWidth); offsetWidth = max(documentElement.offsetWidth, body.offsetWidth); scrollHeight = max(documentElement.scrollHeight, body.scrollHeight); clientHeight = max(documentElement.clientHeight, body.clientHeight); offsetHeight = max(documentElement.offsetHeight, body.offsetHeight); return { width: scrollWidth < offsetWidth ? clientWidth : scrollWidth, height: scrollHeight < offsetHeight ? clientHeight : scrollHeight }; } return function(id, settings) { var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY; settings = settings || {}; function getHandleElm() { return doc.getElementById(settings.handle || id); } start = function(e) { var docSize = getDocumentSize(), handleElm, cursor; e.preventDefault(); downButton = e.button; handleElm = getHandleElm(); startX = e.screenX; startY = e.screenY; // Grab cursor from handle if (window.getComputedStyle) { cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor"); } else { cursor = handleElm.runtimeStyle.cursor; } // Create event overlay and add it to document eventOverlayElm = doc.createElement('div'); DomUtils.css(eventOverlayElm, { position: "absolute", top: 0, left: 0, width: docSize.width, height: docSize.height, zIndex: 0x7FFFFFFF, opacity: 0.0001, background: 'red', cursor: cursor }); doc.body.appendChild(eventOverlayElm); // Bind mouse events DomUtils.on(doc, 'mousemove', drag); DomUtils.on(doc, 'mouseup', stop); // Begin drag settings.start(e); }; drag = function(e) { if (e.button !== downButton) { return stop(e); } e.deltaX = e.screenX - startX; e.deltaY = e.screenY - startY; e.preventDefault(); settings.drag(e); }; stop = function(e) { DomUtils.off(doc, 'mousemove', drag); DomUtils.off(doc, 'mouseup', stop); eventOverlayElm.parentNode.removeChild(eventOverlayElm); if (settings.stop) { settings.stop(e); } }; /** * Destroys the drag/drop helper instance. * * @method destroy */ this.destroy = function() { DomUtils.off(getHandleElm()); }; DomUtils.on(getHandleElm(), 'mousedown', start); }; }); // Included from: js/tinymce/classes/ui/Scrollable.js /** * Scrollable.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This mixin makes controls scrollable using custom scrollbars. * * @-x-less Scrollable.less * @mixin tinymce.ui.Scrollable */ define("tinymce/ui/Scrollable", [ "tinymce/ui/DomUtils", "tinymce/ui/DragHelper" ], function(DomUtils, DragHelper) { "use strict"; return { init: function() { var self = this; self.on('repaint', self.renderScroll); }, renderScroll: function() { var self = this, margin = 2; function repaintScroll() { var hasScrollH, hasScrollV, bodyElm; function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) { var containerElm, scrollBarElm, scrollThumbElm; var containerSize, scrollSize, ratio, rect; var posNameLower, sizeNameLower; scrollBarElm = self.getEl('scroll' + axisName); if (scrollBarElm) { posNameLower = posName.toLowerCase(); sizeNameLower = sizeName.toLowerCase(); if (self.getEl('absend')) { DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1); } if (!hasScroll) { DomUtils.css(scrollBarElm, 'display', 'none'); return; } DomUtils.css(scrollBarElm, 'display', 'block'); containerElm = self.getEl('body'); scrollThumbElm = self.getEl('scroll' + axisName + "t"); containerSize = containerElm["client" + sizeName] - (margin * 2); containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0; scrollSize = containerElm["scroll" + sizeName]; ratio = containerSize / scrollSize; rect = {}; rect[posNameLower] = containerElm["offset" + posName] + margin; rect[sizeNameLower] = containerSize; DomUtils.css(scrollBarElm, rect); rect = {}; rect[posNameLower] = containerElm["scroll" + posName] * ratio; rect[sizeNameLower] = containerSize * ratio; DomUtils.css(scrollThumbElm, rect); } } bodyElm = self.getEl('body'); hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth; hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight; repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height"); repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width"); } function addScroll() { function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) { var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix; self.getEl().appendChild(DomUtils.createFragment( ' ' )); self.draghelper = new DragHelper(axisId + 't', { start: function() { scrollStart = self.getEl('body')["scroll" + posName]; DomUtils.addClass(DomUtils.get(axisId), prefix + 'active'); }, drag: function(e) { var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect(); hasScrollH = layoutRect.contentW > layoutRect.innerW; hasScrollV = layoutRect.contentH > layoutRect.innerH; containerSize = self.getEl('body')["client" + sizeName] - (margin * 2); containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0; ratio = containerSize / self.getEl('body')["scroll" + sizeName]; self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio); }, stop: function() { DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active'); } }); /* self.on('click', function(e) { if (e.target.id == self._id + '-scrollv') { } });*/ } self.addClass('scroll'); addScrollAxis("v", "Top", "Height", "Y", "Width"); addScrollAxis("h", "Left", "Width", "X", "Height"); } if (self.settings.autoScroll) { if (!self._hasScroll) { self._hasScroll = true; addScroll(); self.on('wheel', function(e) { var bodyEl = self.getEl('body'); bodyEl.scrollLeft += (e.deltaX || 0) * 10; bodyEl.scrollTop += e.deltaY * 10; repaintScroll(); }); DomUtils.on(self.getEl('body'), "scroll", repaintScroll); } repaintScroll(); } } }; }); // Included from: js/tinymce/classes/ui/Panel.js /** * Panel.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new panel. * * @-x-less Panel.less * @class tinymce.ui.Panel * @extends tinymce.ui.Container * @mixes tinymce.ui.Scrollable */ define("tinymce/ui/Panel", [ "tinymce/ui/Container", "tinymce/ui/Scrollable" ], function(Container, Scrollable) { "use strict"; return Container.extend({ Defaults: { layout: 'fit', containerCls: 'panel' }, Mixins: [Scrollable], /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, layout = self._layout, innerHtml = self.settings.html; self.preRender(); layout.preRender(self); if (typeof(innerHtml) == "undefined") { innerHtml = ( ''+ (self.settings.html || '') + layout.renderHtml(self) + '' + '' + layout.renderHtml(self) + '' ); } else { if (typeof(innerHtml) == 'function') { innerHtml = innerHtml.call(self); } self._hasBody = false; } return ( '' + (self._preBodyHtml || '') + innerHtml + '' ); } }); }); // Included from: js/tinymce/classes/ui/Movable.js /** * Movable.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Movable mixin. Makes controls movable absolute and relative to other elements. * * @mixin tinymce.ui.Movable */ define("tinymce/ui/Movable", [ "tinymce/ui/DomUtils" ], function(DomUtils) { "use strict"; function calculateRelativePosition(ctrl, targetElm, rel) { var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size; viewport = DomUtils.getViewPort(); // Get pos of target pos = DomUtils.getPos(targetElm); x = pos.x; y = pos.y; if (ctrl._fixed) { x -= viewport.x; y -= viewport.y; } // Get size of self ctrlElm = ctrl.getEl(); size = DomUtils.getSize(ctrlElm); selfW = size.width; selfH = size.height; // Get size of target size = DomUtils.getSize(targetElm); targetW = size.width; targetH = size.height; // Parse align string rel = (rel || '').split(''); // Target corners if (rel[0] === 'b') { y += targetH; } if (rel[1] === 'r') { x += targetW; } if (rel[0] === 'c') { y += Math.round(targetH / 2); } if (rel[1] === 'c') { x += Math.round(targetW / 2); } // Self corners if (rel[3] === 'b') { y -= selfH; } if (rel[4] === 'r') { x -= selfW; } if (rel[3] === 'c') { y -= Math.round(selfH / 2); } if (rel[4] === 'c') { x -= Math.round(selfW / 2); } return { x: x, y: y, w: selfW, h: selfH }; } return { /** * Tests various positions to get the most suitable one. * * @method testMoveRel * @param {DOMElement} elm Element to position against. * @param {Array} rels Array with relative positions. * @return {String} Best suitable relative position. */ testMoveRel: function(elm, rels) { var viewPortRect = DomUtils.getViewPort(); for (var i = 0; i < rels.length; i++) { var pos = calculateRelativePosition(this, elm, rels[i]); if (this._fixed) { if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) { return rels[i]; } } else { if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x && pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) { return rels[i]; } } } return rels[0]; }, /** * Move relative to the specified element. * * @method moveRel * @param {Element} elm Element to move relative to. * @param {String} rel Relative mode. For example: br-tl. * @return {tinymce.ui.Control} Current control instance. */ moveRel: function(elm, rel) { if (typeof(rel) != 'string') { rel = this.testMoveRel(elm, rel); } var pos = calculateRelativePosition(this, elm, rel); return this.moveTo(pos.x, pos.y); }, /** * Move by a relative x, y values. * * @method moveBy * @param {Number} dx Relative x position. * @param {Number} dy Relative y position. * @return {tinymce.ui.Control} Current control instance. */ moveBy: function(dx, dy) { var self = this, rect = self.layoutRect(); self.moveTo(rect.x + dx, rect.y + dy); return self; }, /** * Move to absolute position. * * @method moveTo * @param {Number} x Absolute x position. * @param {Number} y Absolute y position. * @return {tinymce.ui.Control} Current control instance. */ moveTo: function(x, y) { var self = this; // TODO: Move this to some global class function contrain(value, max, size) { if (value < 0) { return 0; } if (value + size > max) { value = max - size; return value < 0 ? 0 : value; } return value; } if (self.settings.constrainToViewport) { var viewPortRect = DomUtils.getViewPort(window); var layoutRect = self.layoutRect(); x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w); y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h); } if (self._rendered) { self.layoutRect({x: x, y: y}).repaint(); } else { self.settings.x = x; self.settings.y = y; } self.fire('move', {x: x, y: y}); return self; } }; }); // Included from: js/tinymce/classes/ui/Resizable.js /** * Resizable.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Resizable mixin. Enables controls to be resized. * * @mixin tinymce.ui.Resizable */ define("tinymce/ui/Resizable", [ "tinymce/ui/DomUtils" ], function(DomUtils) { "use strict"; return { /** * Resizes the control to contents. * * @method resizeToContent */ resizeToContent: function() { this._layoutRect.autoResize = true; this._lastRect = null; this.reflow(); }, /** * Resizes the control to a specific width/height. * * @method resizeTo * @param {Number} w Control width. * @param {Number} h Control height. * @return {tinymce.ui.Control} Current control instance. */ resizeTo: function(w, h) { // TODO: Fix hack if (w <= 1 || h <= 1) { var rect = DomUtils.getWindowSize(); w = w <= 1 ? w * rect.w : w; h = h <= 1 ? h * rect.h : h; } this._layoutRect.autoResize = false; return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow(); }, /** * Resizes the control to a specific relative width/height. * * @method resizeBy * @param {Number} dw Relative control width. * @param {Number} dh Relative control height. * @return {tinymce.ui.Control} Current control instance. */ resizeBy: function(dw, dh) { var self = this, rect = self.layoutRect(); return self.resizeTo(rect.w + dw, rect.h + dh); } }; }); // Included from: js/tinymce/classes/ui/FloatPanel.js /** * FloatPanel.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class creates a floating panel. * * @-x-less FloatPanel.less * @class tinymce.ui.FloatPanel * @extends tinymce.ui.Panel * @mixes tinymce.ui.Movable * @mixes tinymce.ui.Resizable */ define("tinymce/ui/FloatPanel", [ "tinymce/ui/Panel", "tinymce/ui/Movable", "tinymce/ui/Resizable", "tinymce/ui/DomUtils" ], function(Panel, Movable, Resizable, DomUtils) { "use strict"; var documentClickHandler, documentScrollHandler, visiblePanels = []; var zOrder = [], hasModal; var FloatPanel = Panel.extend({ Mixins: [Movable, Resizable], /** * Constructs a new control instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {Boolean} autohide Automatically hide the panel. */ init: function(settings) { var self = this; function reorder() { var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal; if (zOrder.length) { for (i = 0; i < zOrder.length; i++) { if (zOrder[i].modal) { zIndex++; topModal = zOrder[i]; } zOrder[i].getEl().style.zIndex = zIndex; zOrder[i].zIndex = zIndex; zIndex++; } } var modalBlockEl = document.getElementById(self.classPrefix + 'modal-block'); if (topModal) { DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1); } else if (modalBlockEl) { modalBlockEl.parentNode.removeChild(modalBlockEl); hasModal = false; } FloatPanel.currentZIndex = zIndex; } function isChildOf(ctrl, parent) { while (ctrl) { if (ctrl == parent) { return true; } ctrl = ctrl.parent(); } } /** * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will * also reposition all child panels of the current panel. */ function repositionPanel(panel) { var scrollY = DomUtils.getViewPort().y; function toggleFixedChildPanels(fixed, deltaY) { var parent; for (var i = 0; i < visiblePanels.length; i++) { if (visiblePanels[i] != panel) { parent = visiblePanels[i].parent(); while (parent && (parent = parent.parent())) { if (parent == panel) { visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint(); } } } } } if (panel.settings.autofix) { if (!panel._fixed) { panel._autoFixY = panel.layoutRect().y; if (panel._autoFixY < scrollY) { panel.fixed(true).layoutRect({y: 0}).repaint(); toggleFixedChildPanels(true, scrollY - panel._autoFixY); } } else { if (panel._autoFixY > scrollY) { panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint(); toggleFixedChildPanels(false, panel._autoFixY - scrollY); } } } } self._super(settings); self._eventsRoot = self; self.addClass('floatpanel'); // Hide floatpanes on click out side the root button if (settings.autohide) { if (!documentClickHandler) { documentClickHandler = function(e) { var i, clickCtrl = self.getParentCtrl(e.target); // Hide any float panel when a click is out side that float panel and the // float panels direct parent for example a click on a menu button i = visiblePanels.length; while (i--) { var panel = visiblePanels[i]; if (panel.settings.autohide) { if (clickCtrl) { if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) { continue; } } e = panel.fire('autohide', {target: e.target}); if (!e.isDefaultPrevented()) { panel.hide(); } } } }; DomUtils.on(document, 'click', documentClickHandler); } visiblePanels.push(self); } if (settings.autofix) { if (!documentScrollHandler) { documentScrollHandler = function() { var i; i = visiblePanels.length; while (i--) { repositionPanel(visiblePanels[i]); } }; DomUtils.on(window, 'scroll', documentScrollHandler); } self.on('move', function() { repositionPanel(this); }); } self.on('postrender show', function(e) { if (e.control == self) { var modalBlockEl, prefix = self.classPrefix; if (self.modal && !hasModal) { modalBlockEl = DomUtils.createFragment(''); modalBlockEl = modalBlockEl.firstChild; self.getContainerElm().appendChild(modalBlockEl); setTimeout(function() { DomUtils.addClass(modalBlockEl, prefix + 'in'); DomUtils.addClass(self.getEl(), prefix + 'in'); }, 0); hasModal = true; } zOrder.push(self); reorder(); } }); self.on('close hide', function(e) { if (e.control == self) { var i = zOrder.length; while (i--) { if (zOrder[i] === self) { zOrder.splice(i, 1); } } reorder(); } }); self.on('show', function() { self.parents().each(function(ctrl) { if (ctrl._fixed) { self.fixed(true); return false; } }); }); if (settings.popover) { self._preBodyHtml = ''; self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start'); } }, fixed: function(state) { var self = this; if (self._fixed != state) { if (self._rendered) { var viewport = DomUtils.getViewPort(); if (state) { self.layoutRect().y -= viewport.y; } else { self.layoutRect().y += viewport.y; } } self.toggleClass('fixed', state); self._fixed = state; } return self; }, /** * Shows the current float panel. * * @method show * @return {tinymce.ui.FloatPanel} Current floatpanel instance. */ show: function() { var self = this, i, state = self._super(); i = visiblePanels.length; while (i--) { if (visiblePanels[i] === self) { break; } } if (i === -1) { visiblePanels.push(self); } return state; }, /** * Hides the current float panel. * * @method hide * @return {tinymce.ui.FloatPanel} Current floatpanel instance. */ hide: function() { removeVisiblePanel(this); return this._super(); }, /** * Hides all visible the float panels. * * @method hideAll */ hideAll: function() { FloatPanel.hideAll(); }, /** * Closes the float panel. This will remove the float panel from page and fire the close event. * * @method close */ close: function() { var self = this; self.fire('close'); return self.remove(); }, /** * Removes the float panel from page. * * @method remove */ remove: function() { removeVisiblePanel(this); this._super(); } }); /** * Hides all visible the float panels. * * @static * @method hideAll */ FloatPanel.hideAll = function() { var i = visiblePanels.length; while (i--) { var panel = visiblePanels[i]; if (panel.settings.autohide) { panel.fire('cancel', {}, false); panel.hide(); visiblePanels.splice(i, 1); } } }; function removeVisiblePanel(panel) { var i; i = visiblePanels.length; while (i--) { if (visiblePanels[i] === panel) { visiblePanels.splice(i, 1); } } i = zOrder.length; while (i--) { if (zOrder[i] === panel) { zOrder.splice(i, 1); } } } return FloatPanel; }); // Included from: js/tinymce/classes/ui/KeyboardNavigation.js /** * KeyboardNavigation.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class handles keyboard navigation of controls and elements. * * @class tinymce.ui.KeyboardNavigation */ define("tinymce/ui/KeyboardNavigation", [ "tinymce/ui/DomUtils" ], function(DomUtils) { "use strict"; /** * Create a new KeyboardNavigation instance to handle the focus for a specific element. * * @constructor * @param {Object} settings the settings object to define how keyboard navigation works. * * @setting {tinymce.ui.Control} root the root control navigation focus movement is scoped to this root. * @setting {Array} items an array containing the items to move focus between. Every object in this array must have an * id attribute which maps to the actual DOM element and it must be able to have focus i.e. tabIndex=-1. * @setting {Function} onCancel the callback for when the user presses escape or otherwise indicates canceling. * @setting {Function} onAction (optional) the action handler to call when the user activates an item. * @setting {Boolean} enableLeftRight (optional, default) when true, the up/down arrows move through items. * @setting {Boolean} enableUpDown (optional) when true, the up/down arrows move through items. * Note for both up/down and left/right explicitly set both enableLeftRight and enableUpDown to true. */ return function(settings) { var root = settings.root, enableUpDown = settings.enableUpDown !== false; var enableLeftRight = settings.enableLeftRight !== false; var items = settings.items, focussedId; /** * Initializes the items array if needed. This will collect items/elements * from the specified root control. */ function initItems() { if (!items) { items = []; if (root.find) { // Root is a container then get child elements using the UI API root.find('*').each(function(ctrl) { if (ctrl.canFocus) { items.push(ctrl.getEl()); } }); } else { // Root is a control/widget then get the child elements of that control var elements = root.getEl().getElementsByTagName('*'); for (var i = 0; i < elements.length; i++) { if (elements[i].id && elements[i]) { items.push(elements[i]); } } } } } /** * Returns the currently focused element. * * @private * @return {Element} Currently focused element. */ function getFocusElement() { return document.getElementById(focussedId); } /** * Returns the currently focused elements wai aria role. * * @private * @param {Element} elm Optional element to get role from. * @return {String} Role of specified element. */ function getRole(elm) { elm = elm || getFocusElement(); return elm && elm.getAttribute('role'); } /** * Returns the role of the parent element. * * @private * @param {Element} elm Optional element to get parent role from. * @return {String} Role of the first parent that has a role. */ function getParentRole(elm) { var role, parent = elm || getFocusElement(); while ((parent = parent.parentNode)) { if ((role = getRole(parent))) { return role; } } } /** * Returns an wai aria property by name. * * @private * @param {String} name Name of the aria property to get for example "disabled". * @return {String} Aria property value. */ function getAriaProp(name) { var elm = document.getElementById(focussedId); if (elm) { return elm.getAttribute('aria-' + name); } } /** * Executes the onAction event callback. This is when the user presses enter/space. * * @private */ function action() { var focusElm = getFocusElement(); if (focusElm && (focusElm.nodeName == "TEXTAREA" || focusElm.type == "text")) { return; } if (settings.onAction) { settings.onAction(focussedId); } else { DomUtils.fire(getFocusElement(), 'click', {keyboard: true}); } return true; } /** * Cancels the current navigation. The same as pressing the Esc key. * * @method cancel */ function cancel() { var focusElm; if (settings.onCancel) { if ((focusElm = getFocusElement())) { focusElm.blur(); } settings.onCancel(); } else { settings.root.fire('cancel'); } } /** * Moves the focus to the next or previous item. It will wrap to start/end if it can't move. * * @method moveFocus * @param {Number} dir Direction for move -1 or 1. */ function moveFocus(dir) { var idx = -1, focusElm, i; var visibleItems = []; function isVisible(elm) { var rootElm = root ? root.getEl() : document.body; while (elm && elm != rootElm) { if (elm.style.display == 'none') { return false; } elm = elm.parentNode; } return true; } initItems(); // TODO: Optimize this, will be slow on lots of items i = visibleItems.length; for (i = 0; i < items.length; i++) { if (isVisible(items[i])) { visibleItems.push(items[i]); } } i = visibleItems.length; while (i--) { if (visibleItems[i].id === focussedId) { idx = i; break; } } idx += dir; if (idx < 0) { idx = visibleItems.length - 1; } else if (idx >= visibleItems.length) { idx = 0; } focusElm = visibleItems[idx]; focusElm.focus(); focussedId = focusElm.id; if (settings.actOnFocus) { action(); } } /** * Moves focus to the first item or the last focused item if root is a toolbar. * * @method focusFirst * @return {[type]} [description] */ function focusFirst() { var i, rootRole; rootRole = getRole(settings.root.getEl()); initItems(); i = items.length; while (i--) { if (rootRole == 'toolbar' && items[i].id === focussedId) { items[i].focus(); return; } } items[0].focus(); } // Handle accessible keys root.on('keydown', function(e) { var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40; var DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32, DOM_VK_TAB = 9; var preventDefault; switch (e.keyCode) { case DOM_VK_LEFT: if (enableLeftRight) { if (settings.leftAction) { settings.leftAction(); } else { moveFocus(-1); } preventDefault = true; } break; case DOM_VK_RIGHT: if (enableLeftRight) { if (getRole() == 'menuitem' && getParentRole() == 'menu') { if (getAriaProp('haspopup')) { action(); } } else { moveFocus(1); } preventDefault = true; } break; case DOM_VK_UP: if (enableUpDown) { moveFocus(-1); preventDefault = true; } break; case DOM_VK_DOWN: if (enableUpDown) { if (getRole() == 'menuitem' && getParentRole() == 'menubar') { action(); } else if (getRole() == 'button' && getAriaProp('haspopup')) { action(); } else { moveFocus(1); } preventDefault = true; } break; case DOM_VK_TAB: preventDefault = true; if (e.shiftKey) { moveFocus(-1); } else { moveFocus(1); } break; case DOM_VK_ESCAPE: preventDefault = true; cancel(); break; case DOM_VK_ENTER: case DOM_VK_RETURN: case DOM_VK_SPACE: preventDefault = action(); break; } if (preventDefault) { e.stopPropagation(); e.preventDefault(); } }); // Init on focus in root.on('focusin', function(e) { initItems(); focussedId = e.target.id; }); return { moveFocus: moveFocus, focusFirst: focusFirst, cancel: cancel }; }; }); // Included from: js/tinymce/classes/ui/Window.js /** * Window.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new window. * * @-x-less Window.less * @class tinymce.ui.Window * @extends tinymce.ui.FloatPanel */ define("tinymce/ui/Window", [ "tinymce/ui/FloatPanel", "tinymce/ui/Panel", "tinymce/ui/DomUtils", "tinymce/ui/KeyboardNavigation", "tinymce/ui/DragHelper" ], function(FloatPanel, Panel, DomUtils, KeyboardNavigation, DragHelper) { "use strict"; var Window = FloatPanel.extend({ modal: true, Defaults: { border: 1, layout: 'flex', containerCls: 'panel', role: 'dialog', callbacks: { submit: function() { this.fire('submit', {data: this.toJSON()}); }, close: function() { this.close(); } } }, /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { var self = this; self._super(settings); if (self.isRtl()) { self.addClass('rtl'); } self.addClass('window'); self._fixed = true; // Create statusbar if (settings.buttons) { self.statusbar = new Panel({ layout: 'flex', border: '1 0 0 0', spacing: 3, padding: 10, align: 'center', pack: self.isRtl() ? 'start' : 'end', defaults: { type: 'button' }, items: settings.buttons }); self.statusbar.addClass('foot'); self.statusbar.parent(self); } self.on('click', function(e) { if (e.target.className.indexOf(self.classPrefix + 'close') != -1) { self.close(); } }); self.aria('label', settings.title); self._fullscreen = false; }, /** * Recalculates the positions of the controls in the current container. * This is invoked by the reflow method and shouldn't be called directly. * * @method recalc */ recalc: function() { var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc; if (self._fullscreen) { self.layoutRect(DomUtils.getWindowSize()); self.layoutRect().contentH = self.layoutRect().innerH; } self._super(); layoutRect = self.layoutRect(); // Resize window based on title width if (self.settings.title && !self._fullscreen) { width = layoutRect.headerW; if (width > layoutRect.w) { x = layoutRect.x - Math.max(0, width / 2); self.layoutRect({w: width, x: x}); needsRecalc = true; } } // Resize window based on statusbar width if (statusbar) { statusbar.layoutRect({w: self.layoutRect().innerW}).recalc(); width = statusbar.layoutRect().minW + layoutRect.deltaW; if (width > layoutRect.w) { x = layoutRect.x - Math.max(0, width - layoutRect.w); self.layoutRect({w: width, x: x}); needsRecalc = true; } } // Recalc body and disable auto resize if (needsRecalc) { self.recalc(); } }, /** * 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, layoutRect = self._super(), deltaH = 0, headEl; // Reserve vertical space for title if (self.settings.title && !self._fullscreen) { headEl = self.getEl('head'); var size = DomUtils.getSize(headEl); layoutRect.headerW = size.width; layoutRect.headerH = size.height; deltaH += layoutRect.headerH; } // Reserve vertical space for statusbar if (self.statusbar) { deltaH += self.statusbar.layoutRect().h; } layoutRect.deltaH += deltaH; layoutRect.minH += deltaH; //layoutRect.innerH -= deltaH; layoutRect.h += deltaH; var rect = DomUtils.getWindowSize(); layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2); layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2); return layoutRect; }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix; var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html; self.preRender(); layout.preRender(self); if (settings.title) { headerHtml = ( '' + '' ); } if (settings.url) { html = ''; } if (typeof(html) == "undefined") { html = layout.renderHtml(self); } if (self.statusbar) { footerHtml = self.statusbar.renderHtml(); } return ( '' + self.encode(settings.title) + '' + '' + '' + '' + headerHtml + '' ); }, /** * Switches the window fullscreen mode. * * @method fullscreen * @param {Boolean} state True/false state. * @return {tinymce.ui.Window} Current window instance. */ fullscreen: function(state) { var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect; if (state != self._fullscreen) { DomUtils.on(window, 'resize', function() { var time; if (self._fullscreen) { // Time the layout time if it's to slow use a timeout to not hog the CPU if (!slowRendering) { time = new Date().getTime(); var rect = DomUtils.getWindowSize(); self.moveTo(0, 0).resizeTo(rect.w, rect.h); if ((new Date().getTime()) - time > 50) { slowRendering = true; } } else { if (!self._timer) { self._timer = setTimeout(function() { var rect = DomUtils.getWindowSize(); self.moveTo(0, 0).resizeTo(rect.w, rect.h); self._timer = 0; }, 50); } } } }); layoutRect = self.layoutRect(); self._fullscreen = state; if (!state) { self._borderBox = self.parseBox(self.settings.border); self.getEl('head').style.display = ''; layoutRect.deltaH += layoutRect.headerH; DomUtils.removeClass(documentElement, prefix + 'fullscreen'); DomUtils.removeClass(document.body, prefix + 'fullscreen'); self.removeClass('fullscreen'); self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h); } else { self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h}; self._borderBox = self.parseBox('0'); self.getEl('head').style.display = 'none'; layoutRect.deltaH -= layoutRect.headerH + 2; DomUtils.addClass(documentElement, prefix + 'fullscreen'); DomUtils.addClass(document.body, prefix + 'fullscreen'); self.addClass('fullscreen'); var rect = DomUtils.getWindowSize(); self.moveTo(0, 0).resizeTo(rect.w, rect.h); } } return self.reflow(); }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this, items = [], focusCtrl, autoFocusFound, startPos; setTimeout(function() { self.addClass('in'); }, 0); self.keyboardNavigation = new KeyboardNavigation({ root: self, enableLeftRight: false, enableUpDown: false, items: items, onCancel: function() { self.close(); } }); self.find('*').each(function(ctrl) { if (ctrl.canFocus) { autoFocusFound = autoFocusFound || ctrl.settings.autofocus; focusCtrl = focusCtrl || ctrl; // TODO: Figure out a better way if (ctrl.type == 'filepicker') { items.push(ctrl.getEl('inp')); if (ctrl.getEl('open')) { items.push(ctrl.getEl('open')); } } else { items.push(ctrl.getEl()); } } }); if (self.statusbar) { self.statusbar.find('*').each(function(ctrl) { if (ctrl.canFocus) { autoFocusFound = autoFocusFound || ctrl.settings.autofocus; focusCtrl = focusCtrl || ctrl; items.push(ctrl.getEl()); } }); } self._super(); if (self.statusbar) { self.statusbar.postRender(); } if (!autoFocusFound && focusCtrl) { focusCtrl.focus(); } this.dragHelper = new DragHelper(self._id + '-dragh', { start: function() { startPos = { x: self.layoutRect().x, y: self.layoutRect().y }; }, drag: function(e) { self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY); } }); self.on('submit', function(e) { if (!e.isDefaultPrevented()) { self.close(); } }); }, /** * Fires a submit event with the serialized form. * * @method submit * @return {Object} Event arguments object. */ submit: function() { return this.fire('submit', {data: this.toJSON()}); }, /** * Removes the current control from DOM and from UI collections. * * @method remove * @return {tinymce.ui.Control} Current control instance. */ remove: function() { var self = this; self.dragHelper.destroy(); self._super(); if (self.statusbar) { this.statusbar.remove(); } } }); return Window; }); // Included from: js/tinymce/classes/ui/MessageBox.js /** * MessageBox.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 create MessageBoxes like alerts/confirms etc. * * @class tinymce.ui.Window * @extends tinymce.ui.FloatPanel */ define("tinymce/ui/MessageBox", [ "tinymce/ui/Window" ], function(Window) { "use strict"; var MessageBox = Window.extend({ /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { settings = { border: 1, padding: 20, layout: 'flex', pack: "center", align: "center", containerCls: 'panel', autoScroll: true, buttons: {type: "button", text: "Ok", action: "ok"}, items: { type: "label", multiline: true, maxWidth: 500, maxHeight: 200 } }; this._super(settings); }, Statics: { /** * Ok buttons constant. * * @static * @final * @field {Number} OK */ OK: 1, /** * Ok/cancel buttons constant. * * @static * @final * @field {Number} OK_CANCEL */ OK_CANCEL: 2, /** * yes/no buttons constant. * * @static * @final * @field {Number} YES_NO */ YES_NO: 3, /** * yes/no/cancel buttons constant. * * @static * @final * @field {Number} YES_NO_CANCEL */ YES_NO_CANCEL: 4, /** * Constructs a new message box and renders it to the body element. * * @static * @method msgBox * @param {Object} settings Name/value object with settings. */ msgBox: function(settings) { var buttons, callback = settings.callback || function() {}; switch (settings.buttons) { case MessageBox.OK_CANCEL: buttons = [ {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { e.control.parents()[1].close(); callback(true); }}, {type: "button", text: "Cancel", onClick: function(e) { e.control.parents()[1].close(); callback(false); }} ]; break; case MessageBox.YES_NO: buttons = [ {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { e.control.parents()[1].close(); callback(true); }} ]; break; case MessageBox.YES_NO_CANCEL: buttons = [ {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { e.control.parents()[1].close(); }} ]; break; default: buttons = [ {type: "button", text: "Ok", subtype: "primary", onClick: function(e) { e.control.parents()[1].close(); callback(true); }} ]; break; } return new Window({ padding: 20, x: settings.x, y: settings.y, minWidth: 300, minHeight: 100, layout: "flex", pack: "center", align: "center", buttons: buttons, title: settings.title, items: { type: "label", multiline: true, maxWidth: 500, maxHeight: 200, text: settings.text }, onClose: settings.onClose }).renderTo(document.body).reflow(); }, /** * Creates a new alert dialog. * * @method alert * @param {Object} settings Settings for the alert dialog. * @param {function} [callback] Callback to execute when the user makes a choice. */ alert: function(settings, callback) { if (typeof(settings) == "string") { settings = {text: settings}; } settings.callback = callback; return MessageBox.msgBox(settings); }, /** * Creates a new confirm dialog. * * @method confirm * @param {Object} settings Settings for the confirm dialog. * @param {function} [callback] Callback to execute when the user makes a choice. */ confirm: function(settings, callback) { if (typeof(settings) == "string") { settings = {text: settings}; } settings.callback = callback; settings.buttons = MessageBox.OK_CANCEL; return MessageBox.msgBox(settings); } } }); return MessageBox; }); // Included from: js/tinymce/classes/WindowManager.js /** * WindowManager.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 creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. * * @class tinymce.WindowManager * @example * // Opens a new dialog with the file.htm file and the size 320x240 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. * tinymce.activeEditor.windowManager.open({ * url: 'file.htm', * width: 320, * height: 240 * }, { * custom_param: 1 * }); * * // Displays an alert box using the active editors window manager instance * tinymce.activeEditor.windowManager.alert('Hello world!'); * * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { * if (s) * tinymce.activeEditor.windowManager.alert("Ok"); * else * tinymce.activeEditor.windowManager.alert("Cancel"); * }); */ define("tinymce/WindowManager", [ "tinymce/ui/Window", "tinymce/ui/MessageBox" ], function(Window, MessageBox) { return function(editor) { var self = this, windows = []; function getTopMostWindow() { if (windows.length) { return windows[windows.length - 1]; } } self.windows = windows; /** * Opens a new window. * * @method open * @param {Object} args Optional name/value settings collection contains things like width/height/url etc. * @option {String} title Window title. * @option {String} file URL of the file to open in the window. * @option {Number} width Width in pixels. * @option {Number} height Height in pixels. * @option {Boolean} resizable Specifies whether the popup window is resizable or not. * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not. * @option {String/bool} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content * larger than the popup size specified). */ self.open = function(args, params) { var win; editor.editorManager.activeEditor = editor; args.title = args.title || ' '; // Handle URL args.url = args.url || args.file; // Legacy if (args.url) { args.width = parseInt(args.width || 320, 10); args.height = parseInt(args.height || 240, 10); } // Handle body if (args.body) { args.items = { defaults: args.defaults, type: args.bodyType || 'form', items: args.body }; } if (!args.url && !args.buttons) { args.buttons = [ {text: 'Ok', subtype: 'primary', onclick: function() { win.find('form')[0].submit(); win.close(); }}, {text: 'Cancel', onclick: function() { win.close(); }} ]; } win = new Window(args); windows.push(win); win.on('close', function() { var i = windows.length; while (i--) { if (windows[i] === win) { windows.splice(i, 1); } } editor.focus(); }); // Handle data if (args.data) { win.on('postRender', function() { this.find('*').each(function(ctrl) { var name = ctrl.name(); if (name in args.data) { ctrl.value(args.data[name]); } }); }); } // store args and parameters win.features = args || {}; win.params = params || {}; // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog editor.nodeChanged(); return win.renderTo(document.body).reflow(); }; /** * Creates a alert dialog. Please don't use the blocking behavior of this * native version use the callback method instead then it can be extended. * * @method alert * @param {String} message Text to display in the new alert dialog. * @param {function} callback Callback function to be executed after the user has selected ok. * @param {Object} scope Optional scope to execute the callback in. * @example * // Displays an alert box using the active editors window manager instance * tinymce.activeEditor.windowManager.alert('Hello world!'); */ self.alert = function(message, callback, scope) { MessageBox.alert(message, function() { if (callback) { callback.call(scope || this); } }); }; /** * Creates a confirm dialog. Please don't use the blocking behavior of this * native version use the callback method instead then it can be extended. * * @method confirm * @param {String} messageText to display in the new confirm dialog. * @param {function} callback Callback function to be executed after the user has selected ok or cancel. * @param {Object} scope Optional scope to execute the callback in. * @example * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { * if (s) * tinymce.activeEditor.windowManager.alert("Ok"); * else * tinymce.activeEditor.windowManager.alert("Cancel"); * }); */ self.confirm = function(message, callback, scope) { MessageBox.confirm(message, function(state) { callback.call(scope || this, state); }); }; /** * Closes the top most window. * * @method close */ self.close = function() { if (getTopMostWindow()) { getTopMostWindow().close(); } }; /** * Returns the params of the last window open call. This can be used in iframe based * dialog to get params passed from the tinymce plugin. * * @example * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams(); * * @method getParams * @return {Object} Name/value object with parameters passed from windowManager.open call. */ self.getParams = function() { return getTopMostWindow() ? getTopMostWindow().params : null; }; /** * Sets the params of the last opened window. * * @method setParams * @param {Object} params Params object to set for the last opened window. */ self.setParams = function(params) { if (getTopMostWindow()) { getTopMostWindow().params = params; } }; }; }); // Included from: js/tinymce/classes/util/Quirks.js /** * Quirks.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing * * @ignore-file */ /** * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes. * * @class tinymce.util.Quirks */ define("tinymce/util/Quirks", [ "tinymce/util/VK", "tinymce/dom/RangeUtils", "tinymce/html/Node", "tinymce/html/Entities", "tinymce/Env", "tinymce/util/Tools" ], function(VK, RangeUtils, Node, Entities, Env, Tools) { return function(editor) { var each = Tools.each; var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings, parser = editor.parser, serializer = editor.serializer; var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit; /** * Executes a command with a specific state this can be to enable/disable browser editing features. */ function setEditorCommandState(cmd, state) { try { editor.getDoc().execCommand(cmd, false, state); } catch (ex) { // Ignore } } /** * Returns current IE document mode. */ function getDocumentMode() { var documentMode = editor.getDoc().documentMode; return documentMode ? documentMode : 6; } /** * Returns true/false if the event is prevented or not. * * @private * @param {Event} e Event object. * @return {Boolean} true/false if the event is prevented or not. */ function isDefaultPrevented(e) { return e.isDefaultPrevented(); } /** * Fixes a WebKit bug when deleting contents using backspace or delete key. * WebKit will produce a span element if you delete across two block elements. * * Example: *' + html + '' + footerHtml + 'a
|b
* * Will produce this on backspace: *ab
a|b * * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784 * * This code is a bit of a hack and hopefully it will be fixed soon in WebKit. */ function cleanupStylesWhenDeleting() { function removeMergedFormatSpans(isDelete) { var rng, blockElm, wrapperElm, bookmark, container, offset, elm; function isAtStartOrEndOfElm() { if (container.nodeType == 3) { if (isDelete && offset == container.length) { return true; } if (!isDelete && offset === 0) { return true; } } } rng = selection.getRng(); var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset]; if (!rng.collapsed) { isDelete = true; } container = rng[(isDelete ? 'start' : 'end') + 'Container']; offset = rng[(isDelete ? 'start' : 'end') + 'Offset']; if (container.nodeType == 3) { blockElm = dom.getParent(rng.startContainer, dom.isBlock); // On delete clone the root span of the next block element if (isDelete) { blockElm = dom.getNext(blockElm, dom.isBlock); } if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) { // Wrap children of block in a EM and let WebKit stick is // runtime styles junk into that EM wrapperElm = dom.create('em', {'id': '__mceDel'}); each(Tools.grep(blockElm.childNodes), function(node) { wrapperElm.appendChild(node); }); blockElm.appendChild(wrapperElm); } } // Do the backspace/delete action rng = dom.createRng(); rng.setStart(tmpRng[0], tmpRng[1]); rng.setEnd(tmpRng[2], tmpRng[3]); selection.setRng(rng); editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); // Remove temp wrapper element if (wrapperElm) { bookmark = selection.getBookmark(); while ((elm = dom.get('__mceDel'))) { dom.remove(elm, true); } selection.moveToBookmark(bookmark); } } editor.on('keydown', function(e) { var isDelete; isDelete = e.keyCode == DELETE; if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { e.preventDefault(); removeMergedFormatSpans(isDelete); } }); editor.addCommand('Delete', function() {removeMergedFormatSpans();}); } /** * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors. * * For example: *
|
* * Or: *|
* * Or: * [] */ function emptyEditorWhenDeleting() { function serializeRng(rng) { var body = dom.create("body"); var contents = rng.cloneContents(); body.appendChild(contents); return selection.serializer.serialize(body, {format: 'html'}); } function allContentsSelected(rng) { var selection = serializeRng(rng); var allRng = dom.createRng(); allRng.selectNode(editor.getBody()); var allSelection = serializeRng(allRng); return selection === allSelection; } editor.on('keydown', function(e) { var keyCode = e.keyCode, isCollapsed; // Empty the editor if it's needed for example backspace at|
if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { isCollapsed = editor.selection.isCollapsed(); // Selection is collapsed but the editor isn't empty if (isCollapsed && !dom.isEmpty(editor.getBody())) { return; } // IE deletes all contents correctly when everything is selected if (isIE && !isCollapsed) { 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(''); editor.selection.setCursorLocation(editor.getBody(), 0); editor.nodeChanged(); } }); } /** * WebKit doesn't select all the nodes in the body when you press Ctrl+A. * 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.getWin().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() { 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', ''); } } }); } /** * Backspace or delete on WebKit will combine all visual styles in a span if the last character is deleted. * * For example backspace on: *x|
* * Will produce: *|
* * When it should produce: *|
* * See: https://bugs.webkit.org/show_bug.cgi?id=81656 */ function keepInlineElementOnDeleteBackspace() { editor.on('keydown', function(e) { var isDelete, rng, container, offset, brElm, sibling, collapsed, nonEmptyElements; isDelete = e.keyCode == DELETE; if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { rng = selection.getRng(); container = rng.startContainer; offset = rng.startOffset; collapsed = rng.collapsed; // Override delete if the start container is a text node and is at the beginning of text or // just before/after the last character to be deleted in collapsed mode if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { // Edge case when deletingsibling = container.previousSibling; if (sibling && sibling.nodeName == "IMG") { return; } nonEmptyElements = editor.schema.getNonEmptyElements(); // Prevent default logic since it's broken e.preventDefault(); // Insert a BR before the text node this will prevent the containing element from being deleted/converted brElm = dom.create('br', {id: '__tmp'}); container.parentNode.insertBefore(brElm, container); // Do the browser delete editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); // Check if the previous sibling is empty after deleting for example:
|x
|
container = selection.getRng().startContainer; sibling = container.previousSibling; if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { dom.remove(sibling); } // Remove the temp element we inserted dom.remove('__tmp'); } } }); } /** * 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.win.focus(); startRng.select(); } } }); } /** * Fixes selection issues on Gecko 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', function(e) { if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { selection.normalize(); } }); } /** * 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.execCommand('SelectAll'); editor.selection.collapse(true); editor.nodeChanged(); } }); } } // All browsers disableBackspaceIntoATable(); removeBlockQuoteOnBackSpace(); emptyEditorWhenDeleting(); normalizeSelection(); // WebKit if (isWebKit) { keepInlineElementOnDeleteBackspace(); cleanupStylesWhenDeleting(); inputMethodFocus(); selectControlElements(); setDefaultBlockType(); // iOS if (Env.iOS) { selectionChangeNodeChanged(); restoreFocusOnKeyDown(); } else { selectAll(); } } // IE if (isIE && Env.ie < 11) { removeHrOnBackspace(); ensureBodyHasRoleApplication(); addNewLinesBeforeBrInPre(); removePreSerializedStylesWhenSelectingControls(); deleteControlItemOnBackSpace(); renderEmptyBlocksFix(); keepNoScriptContents(); fixCaretSelectionOfDocumentElementOnIe(); } if (Env.ie >= 11) { bodyHeight(); } // Gecko if (isGecko) { removeHrOnBackspace(); focusBody(); removeStylesWhenDeletingAcrossBlockElements(); setGeckoEditingOptions(); addBrAfterLastLinks(); removeGhostSelection(); showBrokenImageIcon(); } }; }); // Included from: js/tinymce/classes/util/Observable.js /** * Observable.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This mixin will add event binding logic to classes. * * @mixin tinymce.util.Observable */ define("tinymce/util/Observable", [ "tinymce/util/Tools" ], function(Tools) { var bindingsName = "__bindings"; var nativeEvents = Tools.makeMap( "focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange" + " mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragend dragover draggesture dragdrop drop drag", ' ' ); function returnFalse() { return false; } function returnTrue() { return true; } return { /** * Fires the specified event by name. * * @method fire * @param {String} name Name of the event to fire. * @param {tinymce.Event/Object?} args Event arguments. * @param {Boolean?} bubble True/false if the event is to be bubbled. * @return {tinymce.Event} Event instance passed in converted into tinymce.Event instance. * @example * instance.fire('event', {...}); */ fire: function(name, args, bubble) { var self = this, handlers, i, l, callback, parent; name = name.toLowerCase(); args = args || {}; args.type = name; // Setup target is there isn't one if (!args.target) { args.target = 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; } //console.log(name, args); if (self[bindingsName]) { handlers = self[bindingsName][name]; if (handlers) { for (i = 0, l = handlers.length; i < l; i++) { handlers[i] = callback = handlers[i]; // Stop immediate propagation if needed if (args.isImmediatePropagationStopped()) { break; } // If callback returns false then prevent default and stop all propagation if (callback.call(self, args) === false) { args.preventDefault(); return args; } } } } // Bubble event up to parents if (bubble !== false && self.parent) { parent = self.parent(); while (parent && !args.isPropagationStopped()) { parent.fire(name, args, false); parent = parent.parent(); } } return args; }, /** * Binds an event listener to a specific event by name. * * @method on * @param {String} name Event name or space separated list of events to bind. * @param {callback} callback Callback to be executed when the event occurs. * @return {Object} Current class instance. * @example * instance.on('event', function(e) { * // Callback logic * }); */ on: function(name, callback) { var self = this, bindings, handlers, names, i; if (callback === false) { callback = function() { return false; }; } if (callback) { names = name.toLowerCase().split(' '); i = names.length; while (i--) { name = names[i]; bindings = self[bindingsName]; if (!bindings) { bindings = self[bindingsName] = {}; } handlers = bindings[name]; if (!handlers) { handlers = bindings[name] = []; if (self.bindNative && nativeEvents[name]) { self.bindNative(name); } } handlers.push(callback); } } return self; }, /** * Unbinds an event listener to a specific event by name. * * @method off * @param {String?} name Name of the event to unbind. * @param {callback?} callback Callback to unbind. * @return {Object} Current class instance. * @example * // Unbind specific callback * instance.off('event', handler); * * // Unbind all listeners by name * instance.off('event'); * * // Unbind all events * instance.off(); */ off: function(name, callback) { var self = this, i, bindings = self[bindingsName], 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[name].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); } } } if (!handlers.length && self.unbindNative && nativeEvents[name]) { self.unbindNative(name); delete bindings[name]; } } } } else { if (self.unbindNative) { for (name in bindings) { self.unbindNative(name); } } self[bindingsName] = []; } } return self; }, hasEventListeners: function(name) { var bindings = this[bindingsName]; name = name.toLowerCase(); return !(!bindings || !bindings[name] || bindings[name].length === 0); } }; }); // Included from: js/tinymce/classes/Shortcuts.js /** * Shortcuts.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Contains all logic for handling of keyboard shortcuts. */ define("tinymce/Shortcuts", [ "tinymce/util/Tools", "tinymce/Env" ], function(Tools, Env) { var each = Tools.each, explode = Tools.explode; var keyCodeLookup = { "f9": 120, "f10": 121, "f11": 122 }; return function(editor) { var self = this, shortcuts = {}; editor.on('keyup keypress keydown', function(e) { if (e.altKey || e.ctrlKey || e.metaKey) { each(shortcuts, function(shortcut) { var ctrlKey = Env.mac ? (e.ctrlKey || e.metaKey) : e.ctrlKey; if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) { return; } if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { e.preventDefault(); if (e.type == "keydown") { shortcut.func.call(shortcut.scope); } return true; } }); } }); /** * 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. */ self.add = function(pattern, desc, cmdFunc, scope) { var cmd; cmd = cmdFunc; if (typeof(cmdFunc) === 'string') { cmdFunc = function() { editor.execCommand(cmd, false, null); }; } else if (Tools.isArray(cmd)) { cmdFunc = function() { editor.execCommand(cmd[0], cmd[1], cmd[2]); }; } each(explode(pattern.toLowerCase()), function(pattern) { var shortcut = { func: cmdFunc, scope: scope || editor, desc: editor.translate(desc), alt: false, ctrl: false, shift: false }; each(explode(pattern, '+'), function(value) { switch (value) { case 'alt': case 'ctrl': case 'shift': shortcut[value] = true; break; default: shortcut.charCode = value.charCodeAt(0); shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0); } }); shortcuts[ (shortcut.ctrl ? 'ctrl' : '') + ',' + (shortcut.alt ? 'alt' : '') + ',' + (shortcut.shift ? 'shift' : '') + ',' + shortcut.keyCode ] = shortcut; }); return true; }; }; }); // Included from: js/tinymce/classes/Editor.js /** * Editor.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /*jshint scripturl:true */ /** * Include the base event class documentation. * * @include ../../../tools/docs/tinymce.Event.js */ /** * This class contains the core logic for a TinyMCE editor. * * @class tinymce.Editor * @mixes tinymce.util.Observable * @example * // Add a class to all paragraphs in the editor. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); * * // Gets the current editors selection as text * tinymce.activeEditor.selection.getContent({format: 'text'}); * * // Creates a new editor instance * var ed = new tinymce.Editor('textareaid', { * some_setting: 1 * }, tinymce.EditorManager); * * // Select each item the user clicks on * ed.on('click', function(e) { * ed.selection.select(e.target); * }); * * ed.render(); */ define("tinymce/Editor", [ "tinymce/dom/DOMUtils", "tinymce/AddOnManager", "tinymce/html/Node", "tinymce/dom/Serializer", "tinymce/html/Serializer", "tinymce/dom/Selection", "tinymce/Formatter", "tinymce/UndoManager", "tinymce/EnterKey", "tinymce/ForceBlocks", "tinymce/EditorCommands", "tinymce/util/URI", "tinymce/dom/ScriptLoader", "tinymce/dom/EventUtils", "tinymce/WindowManager", "tinymce/html/Schema", "tinymce/html/DomParser", "tinymce/util/Quirks", "tinymce/Env", "tinymce/util/Tools", "tinymce/util/Observable", "tinymce/Shortcuts" ], function( DOMUtils, AddOnManager, Node, DomSerializer, Serializer, Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands, URI, ScriptLoader, EventUtils, WindowManager, Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts ) { // Shorten these names var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager; var extend = Tools.extend, each = Tools.each, explode = Tools.explode; var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve; var Event = EventUtils.Event; var isGecko = Env.gecko, ie = Env.ie; function getEventTarget(editor, eventName) { if (eventName == 'selectionchange' || eventName == 'drop') { return editor.getDoc(); } // Need to bind mousedown/mouseup etc to document not body in iframe mode // Since the user might click on the HTML element not the BODY if (!editor.inline && /^mouse|click|contextmenu/.test(eventName)) { return editor.getDoc(); } return editor.getBody(); } /** * Include documentation for all the events. * * @include ../../../tools/docs/tinymce.Editor.js */ /** * Constructs a editor instance by id. * * @constructor * @method Editor * @param {String} id Unique id for the editor. * @param {Object} settings Settings for the editor. * @param {tinymce.EditorManager} editorManager EditorManager instance. * @author Moxiecode */ function Editor(id, settings, editorManager) { var self = this, documentBaseUrl, baseUri; documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL; baseUri = editorManager.baseURI; /** * Name/value collection with editor settings. * * @property settings * @type Object * @example * // Get the value of the theme setting * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme"); */ self.settings = settings = extend({ id: id, theme: 'modern', delta_width: 0, delta_height: 0, popup_css: '', plugins: '', document_base_url: documentBaseUrl, add_form_submit_trigger: true, submit_patch: true, add_unload_trigger: true, convert_urls: true, relative_urls: true, remove_script_host: true, object_resizing: true, doctype: '', visual: true, font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%', forced_root_block: 'p', hidden_input: true, padd_empty_editor: true, render_ui: true, indentation: '30px', inline_styles: true, convert_fonts_to_spans: true, indent: 'simple', indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' + 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' + 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', validate: true, entity_encoding: 'named', url_converter: self.convertURL, url_converter_scope: self, ie7_compat: true }, settings); AddOnManager.language = settings.language || 'en'; AddOnManager.languageLoad = settings.language_load; AddOnManager.baseURL = editorManager.baseURL; /** * Editor instance id, normally the same as the div/textarea that was replaced. * * @property id * @type String */ self.id = settings.id = id; /** * State to force the editor to return false on a isDirty call. * * @property isNotDirty * @type Boolean * @example * function ajaxSave() { * var ed = tinymce.get('elm1'); * * // Save contents using some XHR call * alert(ed.getContent()); * * ed.isNotDirty = true; // Force not dirty state * } */ self.isNotDirty = true; /** * Name/Value object containting plugin instances. * * @property plugins * @type Object * @example * // Execute a method inside a plugin directly * tinymce.activeEditor.plugins.someplugin.someMethod(); */ self.plugins = {}; /** * URI object to document configured for the TinyMCE instance. * * @property documentBaseURI * @type tinymce.util.URI * @example * // Get relative URL from the location of document_base_url * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm'); * * // Get absolute URL from the location of document_base_url * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm'); */ self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, { base_uri: baseUri }); /** * URI object to current document that holds the TinyMCE editor instance. * * @property baseURI * @type tinymce.util.URI * @example * // Get relative URL from the location of the API * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm'); * * // Get absolute URL from the location of the API * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm'); */ self.baseURI = baseUri; /** * Array with CSS files to load into the iframe. * * @property contentCSS * @type Array */ self.contentCSS = []; /** * Array of CSS styles to add to head of document when the editor loads. * * @property contentStyles * @type Array */ self.contentStyles = []; // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic self.shortcuts = new Shortcuts(self); // Internal command handler objects self.execCommands = {}; self.queryStateCommands = {}; self.queryValueCommands = {}; self.loadedCSS = {}; self.suffix = editorManager.suffix; self.editorManager = editorManager; self.inline = settings.inline; // Call setup self.execCallback('setup', self); editorManager.fire('SetupEditor', self); } Editor.prototype = { /** * Renderes the editor/adds it to the page. * * @method render */ render: function() { var self = this, settings = self.settings, id = self.id, suffix = self.suffix; function readyHandler() { DOM.unbind(window, 'ready', readyHandler); self.render(); } // Page is not loaded yet, wait for it if (!Event.domLoaded) { DOM.bind(window, 'ready', readyHandler); return; } // Element not found, then skip initialization if (!self.getElement()) { return; } // No editable support old iOS versions etc if (!Env.contentEditable) { return; } // Hide target element early to prevent content flashing if (!settings.inline) { self.orgVisibility = self.getElement().style.visibility; self.getElement().style.visibility = 'hidden'; } else { self.inline = true; } var form = self.getElement().form || DOM.getParent(id, 'form'); if (form) { self.formElement = form; // Add hidden input for non input elements inside form elements if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) { DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id); self.hasHiddenInput = true; } // Pass submit/reset from form to editor instance self.formEventDelegate = function(e) { self.fire(e.type, e); }; DOM.bind(form, 'submit reset', self.formEventDelegate); // Reset contents in editor when the form is reset self.on('reset', function() { self.setContent(self.startContent, {format: 'raw'}); }); // Check page uses id="submit" or name="submit" for it's submit button if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) { form._mceOldSubmit = form.submit; form.submit = function() { self.editorManager.triggerSave(); self.isNotDirty = true; return form._mceOldSubmit(form); }; } } /** * Window manager reference, use this to open new windows and dialogs. * * @property windowManager * @type tinymce.WindowManager * @example * // Shows an alert message * tinymce.activeEditor.windowManager.alert('Hello world!'); * * // Opens a new dialog with the file.htm file and the size 320x240 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. * tinymce.activeEditor.windowManager.open({ * url: 'file.htm', * width: 320, * height: 240 * }, { * custom_param: 1 * }); */ self.windowManager = new WindowManager(self); if (settings.encoding == 'xml') { self.on('GetContent', function(e) { if (e.save) { e.content = DOM.encode(e.content); } }); } if (settings.add_form_submit_trigger) { self.on('submit', function() { if (self.initialized) { self.save(); } }); } if (settings.add_unload_trigger) { self._beforeUnload = function() { if (self.initialized && !self.destroyed && !self.isHidden()) { self.save({format: 'raw', no_events: true, set_dirty: false}); } }; self.editorManager.on('BeforeUnload', self._beforeUnload); } // Load scripts function loadScripts() { var scriptLoader = ScriptLoader.ScriptLoader; if (settings.language && settings.language != 'en') { settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js'; } if (settings.language_url) { scriptLoader.add(settings.language_url); } if (settings.theme && typeof settings.theme != "function" && settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) { var themeUrl = settings.theme_url; if (themeUrl) { themeUrl = self.documentBaseURI.toAbsolute(themeUrl); } else { themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js'; } ThemeManager.load(settings.theme, themeUrl); } if (Tools.isArray(settings.plugins)) { settings.plugins = settings.plugins.join(' '); } each(settings.external_plugins, function(url, name) { PluginManager.load(name, url); settings.plugins += ' ' + name; }); each(settings.plugins.split(/[ ,]/), function(plugin) { plugin = trim(plugin); if (plugin && !PluginManager.urls[plugin]) { if (plugin.charAt(0) == '-') { plugin = plugin.substr(1, plugin.length); var dependencies = PluginManager.dependencies(plugin); each(dependencies, function(dep) { var defaultSettings = { prefix:'plugins/', resource: dep, suffix:'/plugin' + suffix + '.js' }; dep = PluginManager.createUrl(defaultSettings, dep); PluginManager.load(dep.resource, dep); }); } else { PluginManager.load(plugin, { prefix: 'plugins/', resource: plugin, suffix: '/plugin' + suffix + '.js' }); } } }); scriptLoader.loadQueue(function() { if (!self.removed) { self.init(); } }); } loadScripts(); }, /** * Initializes the editor this will be called automatically when * all plugins/themes and language packs are loaded by the rendered method. * This method will setup the iframe and create the theme and plugin instances. * * @method init */ init: function() { var self = this, settings = self.settings, elm = self.getElement(); var w, h, minHeight, n, o, url, bodyId, bodyClass, re, i, initializedPlugins = []; self.rtl = this.editorManager.i18n.rtl; self.editorManager.add(self); settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area')); /** * Reference to the theme instance that was used to generate the UI. * * @property theme * @type tinymce.Theme * @example * // Executes a method on the theme directly * tinymce.activeEditor.theme.someMethod(); */ if (settings.theme) { if (typeof settings.theme != "function") { settings.theme = settings.theme.replace(/-/, ''); o = ThemeManager.get(settings.theme); self.theme = new o(self, ThemeManager.urls[settings.theme]); if (self.theme.init) { self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, '')); } } else { self.theme = settings.theme; } } function initPlugin(plugin) { var constr = PluginManager.get(plugin), url, pluginInstance; url = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, ''); plugin = trim(plugin); if (constr && inArray(initializedPlugins, plugin) === -1) { each(PluginManager.dependencies(plugin), function(dep){ initPlugin(dep); }); pluginInstance = new constr(self, url); self.plugins[plugin] = pluginInstance; if (pluginInstance.init) { pluginInstance.init(self, url); initializedPlugins.push(plugin); } } } // Create all plugins each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin); // Enables users to override the control factory self.fire('BeforeRenderUI'); // Measure box if (settings.render_ui && self.theme) { self.orgDisplay = elm.style.display; if (typeof settings.theme != "function") { w = settings.width || elm.style.width || elm.offsetWidth; h = settings.height || elm.style.height || elm.offsetHeight; minHeight = settings.min_height || 100; re = /^[0-9\.]+(|px)$/i; if (re.test('' + w)) { w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); } if (re.test('' + h)) { h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), minHeight); } // Render UI o = self.theme.renderUI({ targetNode: elm, width: w, height: h, deltaWidth: settings.delta_width, deltaHeight: settings.delta_height }); // Resize editor if (!settings.content_editable) { DOM.setStyles(o.sizeContainer || o.editorContainer, { wi2dth: w, // TODO: Fix this h2eight: h }); h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); if (h < minHeight) { h = minHeight; } } } else { o = settings.theme(self, elm); // Convert element type to id:s if (o.editorContainer.nodeType) { o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent"; } // Convert element type to id:s if (o.iframeContainer.nodeType) { o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer"; } // Use specified iframe height or the targets offsetHeight h = o.iframeHeight || elm.offsetHeight; } self.editorContainer = o.editorContainer; } // Load specified content CSS last if (settings.content_css) { each(explode(settings.content_css), function(u) { self.contentCSS.push(self.documentBaseURI.toAbsolute(u)); }); } // Load specified content CSS last if (settings.content_style) { self.contentStyles.push(settings.content_style); } // Content editable mode ends here if (settings.content_editable) { elm = n = o = null; // Fix IE leak return self.initContentBody(); } self.iframeHTML = settings.doctype + ''; // We only need to override paths if we have to // IE has a bug where it remove site absolute urls to relative ones if this is specified if (settings.document_base_url != self.documentBaseUrl) { self.iframeHTML += ''; } // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. if (!Env.caretAfter && settings.ie7_compat) { self.iframeHTML += ''; } self.iframeHTML += ''; // Load the CSS by injecting them into the HTML this will reduce "flicker" for (i = 0; i < self.contentCSS.length; i++) { var cssUrl = self.contentCSS[i]; self.iframeHTML += ''; self.loadedCSS[cssUrl] = true; } bodyId = settings.body_id || 'tinymce'; if (bodyId.indexOf('=') != -1) { bodyId = self.getParam('body_id', '', 'hash'); bodyId = bodyId[self.id] || bodyId; } bodyClass = settings.body_class || ''; if (bodyClass.indexOf('=') != -1) { bodyClass = self.getParam('body_class', '', 'hash'); bodyClass = bodyClass[self.id] || ''; } self.iframeHTML += '
'; var domainRelaxUrl = 'javascript:(function(){'+ 'document.open();document.domain="' + document.domain + '";' + 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' + 'document.close();ed.initContentBody(true);})()'; // Domain relaxing is required since the user has messed around with document.domain if (document.domain != location.hostname) { url = domainRelaxUrl; } // Create iframe // TODO: ACC add the appropriate description on this. n = DOM.add(o.iframeContainer, 'iframe', { id: self.id + "_ifr", src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7 frameBorder: '0', allowTransparency: "true", title: self.editorManager.translate( "leanote编辑器" ), style: { width: '100%', height: h, display: 'block' // Important for Gecko to render the iframe correctly } }); // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!! if (ie) { try { self.getDoc(); } catch (e) { n.src = url = domainRelaxUrl; } } self.contentAreaContainer = o.iframeContainer; if (o.editorContainer) { DOM.get(o.editorContainer).style.display = self.orgDisplay; } DOM.get(self.id).style.display = 'none'; DOM.setAttrib(self.id, 'aria-hidden', true); if (!url) { self.initContentBody(); } elm = n = o = null; // Cleanup }, /** * This method get called by the init method ones the iframe is loaded. * It will fill the iframe with contents, setups DOM and selection objects for the iframe. * * @method initContentBody * @private */ initContentBody: function(skipWrite) { var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText; // Restore visibility on target element if (!settings.inline) { self.getElement().style.visibility = self.orgVisibility; } // Setup iframe body if (!skipWrite && !settings.content_editable) { doc.open(); doc.write(self.iframeHTML); doc.close(); } if (settings.content_editable) { self.on('remove', function() { var body = this.getBody(); DOM.removeClass(body, 'mce-content-body'); DOM.removeClass(body, 'mce-edit-focus'); DOM.setAttrib(body, 'tabIndex', null); DOM.setAttrib(body, 'contentEditable', null); }); DOM.addClass(targetElm, 'mce-content-body'); targetElm.tabIndex = -1; self.contentDocument = doc = settings.content_document || document; self.contentWindow = settings.content_window || window; self.bodyElement = targetElm; // Prevent leak in IE settings.content_document = settings.content_window = null; // TODO: Fix this settings.root_name = targetElm.nodeName.toLowerCase(); } // It will not steal focus while setting contentEditable body = self.getBody(); body.disabled = true; if (!settings.readonly) { if (self.inline && DOM.getStyle(body, 'position', true) == 'static') { body.style.position = 'relative'; } body.contentEditable = self.getParam('content_editable_state', true); } body.disabled = false; /** * Schema instance, enables you to validate elements and it's children. * * @property schema * @type tinymce.html.Schema */ self.schema = new Schema(settings); /** * DOM instance for the editor. * * @property dom * @type tinymce.dom.DOMUtils * @example * // Adds a class to all paragraphs within the editor * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); */ self.dom = new DOMUtils(doc, { keep_values: true, url_converter: self.convertURL, url_converter_scope: self, hex_colors: settings.force_hex_style_colors, class_filter: settings.class_filter, update_styles: true, root_element: settings.content_editable ? self.id : null, collect: settings.content_editable, schema: self.schema, onSetAttrib: function(e) { self.fire('SetAttrib', e); } }); /** * HTML parser will be used when contents is inserted into the editor. * * @property parser * @type tinymce.html.DomParser */ self.parser = new DomParser(settings, self.schema); // Convert src and href into data-mce-src, data-mce-href and data-mce-style self.parser.addAttributeFilter('src,href,style', function(nodes, name) { var i = nodes.length, node, dom = self.dom, value, internalName; while (i--) { node = nodes[i]; value = node.attr(name); internalName = 'data-mce-' + name; // Add internal attribute if we need to we don't on a refresh of the document if (!node.attributes.map[internalName]) { if (name === "style") { node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); } else { node.attr(internalName, self.convertURL(value, name, node.name)); } } } }); // Keep scripts from executing self.parser.addNodeFilter('script', function(nodes) { var i = nodes.length, node; while (i--) { node = nodes[i]; node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); } }); self.parser.addNodeFilter('#cdata', function(nodes) { var i = nodes.length, node; while (i--) { node = nodes[i]; node.type = 8; node.name = '#comment'; node.value = '[CDATA[' + node.value + ']]'; } }); self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) { var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); while (i--) { node = nodes[i]; if (node.isEmpty(nonEmptyElements)) { node.empty().append(new Node('br', 1)).shortEnded = true; } } }); /** * DOM serializer for the editor. Will be used when contents is extracted from the editor. * * @property serializer * @type tinymce.dom.Serializer * @example * // Serializes the first paragraph in the editor into a string * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]); */ self.serializer = new DomSerializer(settings, self); /** * Selection instance for the editor. * * @property selection * @type tinymce.dom.Selection * @example * // Sets some contents to the current selection in the editor * tinymce.activeEditor.selection.setContent('Some contents'); * * // Gets the current selection * alert(tinymce.activeEditor.selection.getContent()); * * // Selects the first paragraph found * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); */ self.selection = new Selection(self.dom, self.getWin(), self.serializer, self); /** * Formatter instance. * * @property formatter * @type tinymce.Formatter */ self.formatter = new Formatter(self); /** * Undo manager instance, responsible for handling undo levels. * * @property undoManager * @type tinymce.UndoManager * @example * // Undoes the last modification to the editor * tinymce.activeEditor.undoManager.undo(); */ self.undoManager = new UndoManager(self); self.forceBlocks = new ForceBlocks(self); self.enterKey = new EnterKey(self); self.editorCommands = new EditorCommands(self); self.fire('PreInit'); if (!settings.browser_spellcheck && !settings.gecko_spellcheck) { doc.body.spellcheck = false; // Gecko DOM.setAttrib(body, "spellcheck", "false"); } self.fire('PostRender'); self.quirks = Quirks(self); if (settings.directionality) { body.dir = settings.directionality; } if (settings.nowrap) { body.style.whiteSpace = "nowrap"; } if (settings.protect) { self.on('BeforeSetContent', function(e) { each(settings.protect, function(pattern) { e.content = e.content.replace(pattern, function(str) { return ''; }); }); }); } self.on('SetContent', function() { self.addVisual(self.getBody()); }); // Remove empty contents if (settings.padd_empty_editor) { self.on('PostProcess', function(e) { e.content = e.content.replace(/^(]*>( | |\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 && Env.ie < 11) { body.setActive(); } 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) { // 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 ControlManager. This is a shorter and easier method * of adding buttons without the need to deal with the ControlManager directly. But it's also less * powerfull if you need more control use the ControlManagers factory methods instead. * * @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 modern theme. * * @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', { * title: '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)) { // life ace if(cmd != "toggleCode") 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) { 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)) { 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; /** * life ace */ // 先destroy之前的ace if(LeaAce && LeaAce.canAce) { // 有些地方不用, 比如单页面 var everContent = $(self.getBody()); if(everContent) { LeaAce.destroyAceFromContent(everContent); } } // end // 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; // 这里修改高度 // life-height // iframe父的高度是屏幕的高度, 定死. 而其内容包含title+iframe的高度, 很高 // 所以滑动会是title+iframe一起滑. // iframe的高度肯定是内容的高度度 resizeEditor(); // $("#editorContent_ifr").attr("src", "#1"); // 不做这样, scroll不启作用 // 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())) { content = ie && ie < 11 ? '' : '
'; content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content); } else if (!ie || ie < 11) { // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret content = '
'; } body.innerHTML = content; self.fire('SetContent', args); } else { // Parse and serialize the html var a = (self.parser.parse(content, {isRootContent: true})); if (args.format !== 'raw') { content = new Serializer({}, self.schema).serialize( self.parser.parse(content, {isRootContent: true}) ); } // Set the new cleaned contents to the editor args.content = trim(content); self.dom.setHTML(body, args.content); // Do post processing if (!args.no_events) { self.fire('SetContent', args); } // Don't normalize selection if the focused element isn't the body in // content editable mode since it will steal focus otherwise /*if (!self.settings.content_editable || document.activeElement === self.getBody()) { self.selection.normalize(); }*/ } /** * life ace */ if(LeaAce && LeaAce.canAce) { if(LeaAce.canAce() && LeaAce.isAce) { try { LeaAce.initAceFromContent(self); } catch(e) { log(e); } } else { // 为了在firefox下有正常的显示 $("#editorContent pre").removeClass("ace-tomorrow ace_editor"); } } // end return args.content; }, /** * Gets the content from the editor instance, this will cleanup the content before it gets returned using * the different cleanup rules options. * * @method getContent * @param {Object} args Optional content object, this gets passed around through the whole get process. * @return {String} Cleaned content string, normally HTML contents. * @example * // Get the HTML contents of the currently active editor * console.debug(tinymce.activeEditor.getContent()); * * // Get the raw contents of the currently active editor * tinymce.activeEditor.getContent({format: 'raw'}); * * // Get content of a specific editor: * tinymce.get('content id').getContent() */ getContent: function(args) { var self = this, content, body = self.getBody(); // Setup args object args = args || {}; args.format = args.format || 'html'; args.get = true; args.getInner = true; // Do preprocessing if (!args.no_events) { self.fire('BeforeGetContent', args); } // Get raw contents or by default the cleaned contents if (args.format == 'raw') { content = body.innerHTML; } else if (args.format == 'text') { content = body.innerText || body.textContent; } else { content = self.serializer.serialize(body, args); } // Trim whitespace in beginning/end of HTML if (args.format != 'text') { args.content = trim(content); } else { args.content = content; } // Do post processing if (!args.no_events) { self.fire('GetContent', args); } return args.content; }, /** * Inserts content at caret position. * * @method insertContent * @param {String} content Content to insert. */ insertContent: function(content) { this.execCommand('mceInsertContent', false, content); }, // life insertRawContent: function(content) { this.execCommand('mceInsertRawHTML', false, content); }, /** * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. * * @method isDirty * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. * @example * if (tinymce.activeEditor.isDirty()) * alert("You must save your contents."); */ isDirty: function() { return !this.isNotDirty; }, /** * Returns the editors container element. The container element wrappes in * all the elements added to the page for the editor. Such as UI, iframe etc. * * @method getContainer * @return {Element} HTML DOM element for the editor container. */ getContainer: function() { var self = this; if (!self.container) { self.container = DOM.get(self.editorContainer || self.id + '_parent'); } return self.container; }, /** * Returns the editors content area container element. The this element is the one who * holds the iframe or the editable element. * * @method getContentAreaContainer * @return {Element} HTML DOM element for the editor area container. */ getContentAreaContainer: function() { return this.contentAreaContainer; }, /** * Returns the target element/textarea that got replaced with a TinyMCE editor instance. * * @method getElement * @return {Element} HTML DOM element for the replaced element. */ getElement: function() { return DOM.get(this.settings.content_element || this.id); }, /** * Returns the iframes window object. * * @method getWin * @return {Window} Iframe DOM window object. */ getWin: function() { var self = this, elm; if (!self.contentWindow) { elm = DOM.get(self.id + "_ifr"); if (elm) { self.contentWindow = elm.contentWindow; } } return self.contentWindow; }, /** * Returns the iframes document object. * * @method getDoc * @return {Document} Iframe DOM document object. */ getDoc: function() { var self = this, win; if (!self.contentDocument) { win = self.getWin(); if (win) { self.contentDocument = win.document; } } return self.contentDocument; }, /** * Returns the iframes body element. * * @method getBody * @return {Element} Iframe body element. */ getBody: function() { return this.bodyElement || this.getDoc().body; }, /** * URL converter function this gets executed each time a user adds an img, a or * any other element that has a URL in it. This will be called both by the DOM and HTML * manipulation functions. * * @method convertURL * @param {string} url URL to convert. * @param {string} name Attribute name src, href etc. * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert. * @return {string} Converted URL string. */ convertURL: function(url, name, elm) { var self = this, settings = self.settings; // Use callback instead if (settings.urlconverter_callback) { return self.execCallback('urlconverter_callback', url, elm, true, name); } // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) { return url; } // Convert to relative if (settings.relative_urls) { return self.documentBaseURI.toRelative(url); } // Convert to absolute url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); return url; }, /** * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor. * * @method addVisual * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid. */ addVisual: function(elm) { var self = this, settings = self.settings, dom = self.dom, cls; elm = elm || self.getBody(); if (self.hasVisual === undefined) { self.hasVisual = settings.visual; } each(dom.select('table,a', elm), function(elm) { var value; switch (elm.nodeName) { case 'TABLE': cls = settings.visual_table_class || 'mce-item-table'; value = dom.getAttrib(elm, 'border'); if (!value || value == '0') { if (self.hasVisual) { dom.addClass(elm, cls); } else { dom.removeClass(elm, cls); } } return; case 'A': if (!dom.getAttrib(elm, 'href', false)) { value = dom.getAttrib(elm, 'name') || elm.id; cls = 'mce-item-anchor'; if (value) { if (self.hasVisual) { dom.addClass(elm, cls); } else { dom.removeClass(elm, cls); } } } return; } }); self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual}); }, /** * Removes the editor from the dom and tinymce collection. * * @method remove */ remove: function() { var self = this; if (!self.removed) { self.removed = 1; // Cancels post remove event execution // Remove any hidden input if (self.hasHiddenInput) { DOM.remove(self.getElement().nextSibling); } // Fixed bug where IE has a blinking cursor left from the editor var doc = self.getDoc(); if (ie && doc) { doc.execCommand('SelectAll'); } // We must save before we hide so Safari doesn't crash self.save(); DOM.setStyle(self.id, 'display', self.orgDisplay); // Don't clear the window or document if content editable // is enabled since other instances might still be present if (!self.settings.content_editable) { Event.unbind(self.getWin()); Event.unbind(self.getDoc()); } var elm = self.getContainer(); Event.unbind(self.getBody()); Event.unbind(elm); self.fire('remove'); self.editorManager.remove(self); DOM.remove(elm); self.destroy(); } }, bindNative: function(name) { var self = this; if (self.settings.readonly) { return; } if (self.initialized) { self.dom.bind(getEventTarget(self, name), name, function(e) { self.fire(name, e); }); } else { if (!self._pendingNativeEvents) { self._pendingNativeEvents = [name]; } else { self._pendingNativeEvents.push(name); } } }, unbindNative: function(name) { var self = this; if (self.initialized) { self.dom.unbind(name); } }, /** * Destroys the editor instance by removing all events, element references or other resources * that could leak memory. This method will be called automatically when the page is unloaded * but you can also call it directly if you know what you are doing. * * @method destroy * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one. */ destroy: function(automatic) { var self = this, form; // One time is enough if (self.destroyed) { return; } // If user manually calls destroy and not remove // Users seems to have logic that calls destroy instead of remove if (!automatic && !self.removed) { self.remove(); return; } // We must unbind on Gecko since it would otherwise produce the pesky "attempt // to run compile-and-go script on a cleared scope" message if (automatic && isGecko) { Event.unbind(self.getDoc()); Event.unbind(self.getWin()); Event.unbind(self.getBody()); } if (!automatic) { self.editorManager.off('beforeunload', self._beforeUnload); // Manual destroy if (self.theme && self.theme.destroy) { self.theme.destroy(); } // Destroy controls, selection and dom self.selection.destroy(); self.dom.destroy(); } form = self.formElement; if (form) { if (form._mceOldSubmit) { form.submit = form._mceOldSubmit; form._mceOldSubmit = null; } DOM.unbind(form, 'submit reset', self.formEventDelegate); } self.contentAreaContainer = self.formElement = self.container = null; self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null; if (self.selection) { self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null; } self.destroyed = 1; }, // Internal functions _refreshContentEditable: function() { var self = this, body, parent; // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again if (self._isHidden()) { body = self.getBody(); parent = body.parentNode; parent.removeChild(body); parent.appendChild(body); body.focus(); } }, _isHidden: function() { var sel; if (!isGecko) { return 0; } // Weird, wheres that cursor selection? sel = this.selection.getSel(); return (!sel || !sel.rangeCount || sel.rangeCount === 0); } }; extend(Editor.prototype, Observable); return Editor; }); // Included from: js/tinymce/classes/util/I18n.js /** * I18n.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * I18n class that handles translation of TinyMCE UI. * Uses po style with csharp style parameters. * * @class tinymce.util.I18n */ define("tinymce/util/I18n", [], function() { "use strict"; var data = {}; return { /** * Property gets set to true if a RTL language pack was loaded. * * @property rtl * @type {Boolean} */ rtl: false, /** * Adds translations for a specific language code. * * @method add * @param {String} code Language code like sv_SE. * @param {Array} items Name/value array with English en_US to sv_SE. */ add: function(code, items) { for (var name in items) { data[name] = items[name]; } this.rtl = this.rtl || data._dir === 'rtl'; }, /** * Translates the specified text. * * It has a few formats: * I18n.translate("Text"); * I18n.translate(["Text {0}/{1}", 0, 1]); * I18n.translate({raw: "Raw string"}); * * @method translate * @param {String/Object/Array} text Text to translate. * @return {String} String that got translated. */ translate: function(text) { if (typeof(text) == "undefined") { return text; } if (typeof(text) != "string" && text.raw) { return text.raw; } if (text.push) { var values = text.slice(1); text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) { return values[match2]; }); } return data[text] || text; }, data: data }; }); // Included from: js/tinymce/classes/FocusManager.js /** * FocusManager.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class manages the focus/blur state of the editor. This class is needed since some * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar. * * This class will fire two events focus and blur on the editor instances that got affected. * It will also handle the restore of selection when the focus is lost and returned. * * @class tinymce.FocusManager */ define("tinymce/FocusManager", [ "tinymce/dom/DOMUtils", "tinymce/Env" ], function(DOMUtils, Env) { /** * Constructs a new focus manager instance. * * @constructor FocusManager * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for. */ function FocusManager(editorManager) { function getActiveElement() { try { return document.activeElement; } catch (ex) { // IE sometimes fails to get the activeElement when resizing table // TODO: Investigate this return document.body; } } // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well. function createBookmark(rng) { if (rng && rng.startContainer) { return { startContainer: rng.startContainer, startOffset: rng.startOffset, endContainer: rng.endContainer, endOffset: rng.endOffset }; } return rng; } function bookmarkToRng(editor, bookmark) { var rng; if (bookmark.startContainer) { rng = editor.getDoc().createRange(); rng.setStart(bookmark.startContainer, bookmark.startOffset); rng.setEnd(bookmark.endContainer, bookmark.endOffset); } else { rng = bookmark; } return rng; } function registerEvents(e) { var editor = e.editor, lastRng, selectionChangeHandler; function isUIElement(elm) { return !!DOMUtils.DOM.getParent(elm, FocusManager.isEditorUIElement); } editor.on('init', function() { // On IE take selection snapshot onbeforedeactivate if ("onbeforedeactivate" in document && Env.ie < 11) { editor.dom.bind(editor.getBody(), 'beforedeactivate', function() { var ieSelection = editor.getDoc().selection; try { lastRng = ieSelection && ieSelection.createRange ? ieSelection.createRange() : editor.selection.getRng(); } catch (ex) { // IE throws "Unexcpected call to method or property access" some times so lets ignore it } }); } else if (editor.inline || Env.ie > 10) { // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes editor.on('nodechange keyup', function() { var isInBody, node = document.activeElement; // IE 11 reports active element as iframe not body of iframe if (node && node.id == editor.id + '_ifr') { node = editor.getBody(); } // Check if selection is within editor body while (node) { if (node == editor.getBody()) { isInBody = true; break; } node = node.parentNode; } if (isInBody) { lastRng = editor.selection.getRng(); } }); // Handles the issue with WebKit not retaining selection within inline document // If the user releases the mouse out side the body while selecting a nodeChange won't // fire and there for the selection snapshot won't be stored // TODO: Optimize this since we only need to bind these on the active editor if (Env.webkit) { selectionChangeHandler = function() { var rng = editor.selection.getRng(); // Store when it's non collapsed if (!rng.collapsed) { lastRng = rng; } }; // Bind selection handler DOMUtils.DOM.bind(document, 'selectionchange', selectionChangeHandler); editor.on('remove', function() { DOMUtils.DOM.unbind(document, 'selectionchange', selectionChangeHandler); }); } } }); editor.on('setcontent', function() { lastRng = null; }); // Remove last selection bookmark on mousedown see #6305 editor.on('mousedown', function() { editor.selection.lastFocusBookmark = null; }); editor.on('focusin', function() { var focusedEditor = editorManager.focusedEditor; if (editor.selection.lastFocusBookmark) { editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark)); editor.selection.lastFocusBookmark = null; } if (focusedEditor != editor) { if (focusedEditor) { focusedEditor.fire('blur', {focusedEditor: editor}); } editorManager.activeEditor = editor; editor.fire('focus', {blurredEditor: focusedEditor}); editor.focus(false); editorManager.focusedEditor = editor; } lastRng = null; }); editor.on('focusout', function() { editor.selection.lastFocusBookmark = createBookmark(lastRng); window.setTimeout(function() { var focusedEditor = editorManager.focusedEditor; // Focus from editorA into editorB then don't restore selection if (focusedEditor != editor) { editor.selection.lastFocusBookmark = null; } // Still the same editor the the blur was outside any editor UI if (!isUIElement(getActiveElement()) && focusedEditor == editor) { editor.fire('blur', {focusedEditor: null}); editorManager.focusedEditor = null; editor.selection.lastFocusBookmark = null; } }, 0); }); } editorManager.on('AddEditor', registerEvents); } /** * Returns true if the specified element is part of the UI for example an button or text input. * * @method isEditorUIElement * @param {Element} elm Element to check if it's part of the UI or not. * @return {Boolean} True/false state if the element is part of the UI or not. */ FocusManager.isEditorUIElement = function(elm) { return elm.className.indexOf('mce-') !== -1; }; return FocusManager; }); // Included from: js/tinymce/classes/EditorManager.js /** * EditorManager.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class used as a factory for manager for tinymce.Editor instances. * * @example * tinymce.EditorManager.init({}); * * @class tinymce.EditorManager * @mixes tinymce.util.Observable * @static */ define("tinymce/EditorManager", [ "tinymce/Editor", "tinymce/dom/DOMUtils", "tinymce/util/URI", "tinymce/Env", "tinymce/util/Tools", "tinymce/util/Observable", "tinymce/util/I18n", "tinymce/FocusManager" ], function(Editor, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) { var DOM = DOMUtils.DOM; var explode = Tools.explode, each = Tools.each, extend = Tools.extend; var instanceCounter = 0, beforeUnloadDelegate; var EditorManager = { /** * Major version of TinyMCE build. * * @property majorVersion * @type String */ majorVersion : '4', /** * Minor version of TinyMCE build. * * @property minorVersion * @type String */ minorVersion : '0.17', /** * Release date of TinyMCE build. * * @property releaseDate * @type String */ releaseDate: '2014-02-xx', /** * Collection of editor instances. * * @property editors * @type Object * @example * for (edId in tinymce.editors) * tinymce.editors[edId].save(); */ editors: [], /** * Collection of language pack data. * * @property i18n * @type Object */ i18n: I18n, /** * Currently active editor instance. * * @property activeEditor * @type tinymce.Editor * @example * tinyMCE.activeEditor.selection.getContent(); * tinymce.EditorManager.activeEditor.selection.getContent(); */ activeEditor: null, setup: function() { var self = this, baseURL, documentBaseURL, suffix = "", preInit; // Get base URL for the current document documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); if (!/[\/\\]$/.test(documentBaseURL)) { documentBaseURL += '/'; } // If tinymce is defined and has a base use that or use the old tinyMCEPreInit preInit = window.tinymce || window.tinyMCEPreInit; if (preInit) { baseURL = preInit.base || preInit.baseURL; suffix = preInit.suffix; } else { // Get base where the tinymce script is located var scripts = document.getElementsByTagName('script'); for (var i = 0; i < scripts.length; i++) { var src = scripts[i].src; if (/tinymce(\.jquery|)(\.min|\.dev|\.full\.min|)\.js/.test(src)) { // life fix fug full.min if (src.indexOf('.min') != -1) { suffix = '.min'; } baseURL = src.substring(0, src.lastIndexOf('/')); break; } } } /** * Base URL where the root directory if TinyMCE is located. * * @property baseURL * @type String */ self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL); /** * Document base URL where the current document is located. * * @property documentBaseURL * @type String */ self.documentBaseURL = documentBaseURL; /** * Absolute baseURI for the installation path of TinyMCE. * * @property baseURI * @type tinymce.util.URI */ self.baseURI = new URI(self.baseURL); /** * Current suffix to add to each plugin/theme that gets loaded for example ".min". * * @property suffix * @type String */ self.suffix = suffix; self.focusManager = new FocusManager(self); }, /** * Initializes a set of editors. This method will create editors based on various settings. * * @method init * @param {Object} settings Settings object to be passed to each editor instance. * @example * // Initializes a editor using the longer method * tinymce.EditorManager.init({ * some_settings : 'some value' * }); * * // Initializes a editor instance using the shorter version * tinyMCE.init({ * some_settings : 'some value' * }); */ init: function(settings) { var self = this, editors = [], editor; function createId(elm) { var id = elm.id; // Use element id, or unique name or generate a unique id if (!id) { id = elm.name; if (id && !DOM.get(id)) { id = elm.name; } else { // Generate unique name id = DOM.uniqueId(); } elm.setAttribute('id', id); } return id; } function execCallback(se, n, s) { var f = se[n]; if (!f) { return; } return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); } function hasClass(n, c) { return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); } function readyHandler() { var l, co; DOM.unbind(window, 'ready', readyHandler); execCallback(settings, 'onpageload'); if (settings.types) { // Process type specific selector each(settings.types, function(type) { each(DOM.select(type.selector), function(elm) { var editor = new Editor(createId(elm), extend({}, settings, type), self); editors.push(editor); editor.render(1); }); }); return; } else if (settings.selector) { // Process global selector each(DOM.select(settings.selector), function(elm) { var editor = new Editor(createId(elm), settings, self); editors.push(editor); editor.render(1); }); return; } // Fallback to old setting switch (settings.mode) { case "exact": l = settings.elements || ''; if(l.length > 0) { each(explode(l), function(v) { if (DOM.get(v)) { editor = new Editor(v, settings, self); editors.push(editor); editor.render(true); } else { each(document.forms, function(f) { each(f.elements, function(e) { if (e.name === v) { v = 'mce_editor_' + instanceCounter++; DOM.setAttrib(e, 'id', v); editor = new Editor(v, settings, self); editors.push(editor); editor.render(1); } }); }); } }); } break; case "textareas": case "specific_textareas": each(DOM.select('textarea'), function(elm) { if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) { return; } if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) { editor = new Editor(createId(elm), settings, self); editors.push(editor); editor.render(true); } }); break; } // Call onInit when all editors are initialized if (settings.oninit) { l = co = 0; each(editors, function(ed) { co++; if (!ed.initialized) { // Wait for it ed.on('init', function() { l++; // All done if (l == co) { execCallback(settings, 'oninit'); } }); } else { l++; } // All done if (l == co) { execCallback(settings, 'oninit'); } }); } } self.settings = settings; DOM.bind(window, 'ready', readyHandler); }, /** * Returns a editor instance by id. * * @method get * @param {String/Number} id Editor instance id or index to return. * @return {tinymce.Editor} Editor instance to return. * @example * // Adds an onclick event to an editor by id (shorter version) * tinymce.get('mytextbox').on('click', function(e) { * ed.windowManager.alert('Hello world!'); * }); * * // Adds an onclick event to an editor by id (longer version) * tinymce.EditorManager.get('mytextbox').on('click', function(e) { * ed.windowManager.alert('Hello world!'); * }); */ get: function(id) { if (id === undefined) { return this.editors; } return this.editors[id]; }, /** * Adds an editor instance to the editor collection. This will also set it as the active editor. * * @method add * @param {tinymce.Editor} editor Editor instance to add to the collection. * @return {tinymce.Editor} The same instance that got passed in. */ add: function(editor) { var self = this, editors = self.editors; // Add named and index editor instance editors[editor.id] = editor; editors.push(editor); self.activeEditor = editor; /** * Fires when an editor is added to the EditorManager collection. * * @event AddEditor * @param {Object} e Event arguments. */ self.fire('AddEditor', {editor: editor}); if (!beforeUnloadDelegate) { beforeUnloadDelegate = function() { self.fire('BeforeUnload'); }; DOM.bind(window, 'beforeunload', beforeUnloadDelegate); } return editor; }, /** * Creates an editor instance and adds it to the EditorManager collection. * * @method createEditor * @param {String} id Instance id to use for editor. * @param {Object} settings Editor instance settings. * @return {tinymce.Editor} Editor instance that got created. */ createEditor: function(id, settings) { return this.add(new Editor(id, settings, this)); }, /** * Removes a editor or editors form page. * * @example * // Remove all editors bound to divs * tinymce.remove('div'); * * // Remove all editors bound to textareas * tinymce.remove('textarea'); * * // Remove all editors * tinymce.remove(); * * // Remove specific instance by id * tinymce.remove('#id'); * * @method remove * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove. * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. */ remove: function(selector) { var self = this, i, editors = self.editors, editor, removedFromList; // Remove all editors if (!selector) { for (i = editors.length - 1; i >= 0; i--) { self.remove(editors[i]); } return; } // Remove editors by selector if (typeof(selector) == "string") { selector = selector.selector || selector; each(DOM.select(selector), function(elm) { self.remove(editors[elm.id]); }); return; } // Remove specific editor editor = selector; // Not in the collection if (!editors[editor.id]) { return null; } delete editors[editor.id]; for (i = 0; i < editors.length; i++) { if (editors[i] == editor) { editors.splice(i, 1); removedFromList = true; break; } } // Select another editor since the active one was removed if (self.activeEditor == editor) { self.activeEditor = editors[0]; } /** * Fires when an editor is removed from EditorManager collection. * * @event RemoveEditor * @param {Object} e Event arguments. */ if (removedFromList) { self.fire('RemoveEditor', {editor: editor}); } if (!editors.length) { DOM.unbind(window, 'beforeunload', beforeUnloadDelegate); } editor.remove(); return editor; }, /** * Executes a specific command on the currently active editor. * * @method execCommand * @param {String} c Command to perform for example Bold. * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. * @param {String} v Optional value parameter like for example an URL to a link. * @return {Boolean} true/false if the command was executed or not. */ execCommand: function(cmd, ui, value) { var self = this, editor = self.get(value); // Manager commands switch (cmd) { case "mceAddEditor": if (!self.get(value)) { new Editor(value, self.settings, self).render(); } return true; case "mceRemoveEditor": if (editor) { editor.remove(); } return true; case 'mceToggleEditor': if (!editor) { self.execCommand('mceAddEditor', 0, value); return true; } if (editor.isHidden()) { editor.show(); } else { editor.hide(); } return true; } // Run command on active editor if (self.activeEditor) { return self.activeEditor.execCommand(cmd, ui, value); } return false; }, /** * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. * * @method triggerSave * @example * // Saves all contents * tinyMCE.triggerSave(); */ triggerSave: function() { each(this.editors, function(editor) { editor.save(); }); }, /** * Adds a language pack, this gets called by the loaded language files like en.js. * * @method addI18n * @param {String} code Optional language code. * @param {Object} items Name/value object with translations. */ addI18n: function(code, items) { I18n.add(code, items); }, /** * Translates the specified string using the language pack items. * * @method translate * @param {String/Array/Object} text String to translate * @return {String} Translated string. */ translate: function(text) { return I18n.translate(text); } }; extend(EditorManager, Observable); EditorManager.setup(); // Export EditorManager as tinymce/tinymce in global namespace window.tinymce = window.tinyMCE = EditorManager; return EditorManager; }); // Included from: js/tinymce/classes/LegacyInput.js /** * LegacyInput.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define("tinymce/LegacyInput", [ "tinymce/EditorManager", "tinymce/util/Tools" ], function(EditorManager, Tools) { var each = Tools.each, explode = Tools.explode; EditorManager.on('AddEditor', function(e) { var editor = e.editor; editor.on('preInit', function() { var filters, fontSizes, dom, settings = editor.settings; function replaceWithSpan(node, styles) { each(styles, function(value, name) { if (value) { dom.setStyle(node, name, value); } }); dom.rename(node, 'span'); } function convert(e) { dom = editor.dom; if (settings.convert_fonts_to_spans) { each(dom.select('font,u,strike', e.node), function(node) { filters[node.nodeName.toLowerCase()](dom, node); }); } } if (settings.inline_styles) { fontSizes = explode(settings.font_size_legacy_values); filters = { font: function(dom, node) { replaceWithSpan(node, { backgroundColor: node.style.backgroundColor, color: node.color, fontFamily: node.face, fontSize: fontSizes[parseInt(node.size, 10) - 1] }); }, u: function(dom, node) { replaceWithSpan(node, { textDecoration: 'underline' }); }, strike: function(dom, node) { replaceWithSpan(node, { textDecoration: 'line-through' }); } }; editor.on('PreProcess SetContent', convert); } }); }); }); // Included from: js/tinymce/classes/util/XHR.js /** * XHR.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class enables you to send XMLHTTPRequests cross browser. * @class tinymce.util.XHR * @static * @example * // Sends a low level Ajax request * tinymce.util.XHR.send({ * url: 'someurl', * success: function(text) { * console.debug(text); * } * }); */ define("tinymce/util/XHR", [], function() { return { /** * Sends a XMLHTTPRequest. * Consult the Wiki for details on what settings this method takes. * * @method send * @param {Object} settings Object will target URL, callbacks and other info needed to make the request. */ send: function(settings) { var xhr, count = 0; function ready() { if (!settings.async || xhr.readyState == 4 || count++ > 10000) { if (settings.success && count < 10000 && xhr.status == 200) { settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings); } else if (settings.error) { settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings); } xhr = null; } else { setTimeout(ready, 10); } } // Default settings settings.scope = settings.scope || this; settings.success_scope = settings.success_scope || settings.scope; settings.error_scope = settings.error_scope || settings.scope; settings.async = settings.async === false ? false : true; settings.data = settings.data || ''; xhr = new XMLHttpRequest(); if (xhr) { if (xhr.overrideMimeType) { xhr.overrideMimeType(settings.content_type); } xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async); if (settings.content_type) { xhr.setRequestHeader('Content-Type', settings.content_type); } xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.send(settings.data); // Syncronous request if (!settings.async) { return ready(); } // Wait for response, onReadyStateChange can not be used since it leaks memory in IE setTimeout(ready, 10); } } }; }); // Included from: js/tinymce/classes/util/JSON.js /** * JSON.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * JSON parser and serializer class. * * @class tinymce.util.JSON * @static * @example * // JSON parse a string into an object * var obj = tinymce.util.JSON.parse(somestring); * * // JSON serialize a object into an string * var str = tinymce.util.JSON.serialize(obj); */ define("tinymce/util/JSON", [], function() { function serialize(o, quote) { var i, v, t, name; quote = quote || '"'; if (o === null) { return 'null'; } t = typeof o; if (t == 'string') { v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { // Make sure single quotes never get encoded inside double quotes for JSON compatibility if (quote === '"' && a === "'") { return a; } i = v.indexOf(b); if (i + 1) { return '\\' + v.charAt(i + 1); } a = b.charCodeAt().toString(16); return '\\u' + '0000'.substring(a.length) + a; }) + quote; } if (t == 'object') { if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { for (i=0, v = '['; i0 ? ',' : '') + serialize(o[i], quote); } return v + ']'; } v = '{'; for (name in o) { if (o.hasOwnProperty(name)) { v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; } } return v + '}'; } return '' + o; } return { /** * Serializes the specified object as a JSON string. * * @method serialize * @param {Object} obj Object to serialize as a JSON string. * @param {String} quote Optional quote string defaults to ". * @return {string} JSON string serialized from input. */ serialize: serialize, /** * Unserializes/parses the specified JSON string into a object. * * @method parse * @param {string} s JSON String to parse into a JavaScript object. * @return {Object} Object from input JSON string or undefined if it failed. */ parse: function(text) { try { // Trick uglify JS return window[String.fromCharCode(101) + 'val']('(' + text + ')'); } catch (ex) { // Ignore } } /**#@-*/ }; }); // Included from: js/tinymce/classes/util/JSONRequest.js /** * JSONRequest.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class enables you to use JSON-RPC to call backend methods. * * @class tinymce.util.JSONRequest * @example * var json = new tinymce.util.JSONRequest({ * url: 'somebackend.php' * }); * * // Send RPC call 1 * json.send({ * method: 'someMethod1', * params: ['a', 'b'], * success: function(result) { * console.dir(result); * } * }); * * // Send RPC call 2 * json.send({ * method: 'someMethod2', * params: ['a', 'b'], * success: function(result) { * console.dir(result); * } * }); */ define("tinymce/util/JSONRequest", [ "tinymce/util/JSON", "tinymce/util/XHR", "tinymce/util/Tools" ], function(JSON, XHR, Tools) { var extend = Tools.extend; function JSONRequest(settings) { this.settings = extend({}, settings); this.count = 0; } /** * Simple helper function to send a JSON-RPC request without the need to initialize an object. * Consult the Wiki API documentation for more details on what you can pass to this function. * * @method sendRPC * @static * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. */ JSONRequest.sendRPC = function(o) { return new JSONRequest().send(o); }; JSONRequest.prototype = { /** * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function. * * @method send * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc. */ send: function(args) { var ecb = args.error, scb = args.success; args = extend(this.settings, args); args.success = function(c, x) { c = JSON.parse(c); if (typeof(c) == 'undefined') { c = { error : 'JSON Parse error.' }; } if (c.error) { ecb.call(args.error_scope || args.scope, c.error, x); } else { scb.call(args.success_scope || args.scope, c.result); } }; args.error = function(ty, x) { if (ecb) { ecb.call(args.error_scope || args.scope, ty, x); } }; args.data = JSON.serialize({ id: args.id || 'c' + (this.count++), method: args.method, params: args.params }); // JSON content type for Ruby on rails. Bug: #1883287 args.content_type = 'application/json'; XHR.send(args); } }; return JSONRequest; }); // Included from: js/tinymce/classes/util/JSONP.js /** * JSONP.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ define("tinymce/util/JSONP", [ "tinymce/dom/DOMUtils" ], function(DOMUtils) { return { callbacks: {}, count: 0, send: function(settings) { var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count; var id = 'tinymce_jsonp_' + count; self.callbacks[count] = function(json) { dom.remove(id); delete self.callbacks[count]; settings.callback(json); }; dom.add(dom.doc.body, 'script', { id: id, src: settings.url, type: 'text/javascript' }); self.count++; } }; }); // Included from: js/tinymce/classes/util/LocalStorage.js /** * LocalStorage.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers. * Storage is done using userData on IE 7 and a special serialization format. The format is designed * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This * makes it possible to store for example HTML data. * * Storage format for userData: * , , , ,... * * For example this data key1=value1,key2=value2 would be: * 4,key1,6,value1,4,key2,6,value2 * * @class tinymce.util.LocalStorage * @static * @version 4.0 * @example * tinymce.util.LocalStorage.setItem('key', 'value'); * var value = tinymce.util.LocalStorage.getItem('key'); */ define("tinymce/util/LocalStorage", [], function() { var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport; // Check for native support try { if (window.localStorage) { return localStorage; } } catch (ex) { // Ignore } userDataKey = "tinymce"; storageElm = document.documentElement; hasOldIEDataSupport = !!storageElm.addBehavior; if (hasOldIEDataSupport) { storageElm.addBehavior('#default#userData'); } /** * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters. */ function updateKeys() { keys = []; for (var key in items) { keys.push(key); } LocalStorage.length = keys.length; } /** * Loads the userData string and parses it into the items structure. */ function load() { var key, data, value, pos = 0; items = {}; // localStorage can be disabled on WebKit/Gecko so make a dummy storage if (!hasOldIEDataSupport) { return; } function next(end) { var value, nextPos; nextPos = end !== undefined ? pos + end : data.indexOf(',', pos); if (nextPos === -1 || nextPos > data.length) { return null; } value = data.substring(pos, nextPos); pos = nextPos + 1; return value; } storageElm.load(userDataKey); data = storageElm.getAttribute(userDataKey) || ''; do { var offset = next(); if (offset === null) { break; } key = next(parseInt(offset, 32) || 0); if (key !== null) { offset = next(); if (offset === null) { break; } value = next(parseInt(offset, 32) || 0); if (key) { items[key] = value; } } } while (key !== null); updateKeys(); } /** * Saves the items structure into a the userData format. */ function save() { var value, data = ''; // localStorage can be disabled on WebKit/Gecko so make a dummy storage if (!hasOldIEDataSupport) { return; } for (var key in items) { value = items[key]; data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value; } storageElm.setAttribute(userDataKey, data); try { storageElm.save(userDataKey); } catch (ex) { // Ignore disk full } updateKeys(); } LocalStorage = { /** * Length of the number of items in storage. * * @property length * @type Number * @return {Number} Number of items in storage. */ //length:0, /** * Returns the key name by index. * * @method key * @param {Number} index Index of key to return. * @return {String} Key value or null if it wasn't found. */ key: function(index) { return keys[index]; }, /** * Returns the value if the specified key or null if it wasn't found. * * @method getItem * @param {String} key Key of item to retrive. * @return {String} Value of the specified item or null if it wasn't found. */ getItem: function(key) { return key in items ? items[key] : null; }, /** * Sets the value of the specified item by it's key. * * @method setItem * @param {String} key Key of the item to set. * @param {String} value Value of the item to set. */ setItem: function(key, value) { items[key] = "" + value; save(); }, /** * Removes the specified item by key. * * @method removeItem * @param {String} key Key of item to remove. */ removeItem: function(key) { delete items[key]; save(); }, /** * Removes all items. * * @method clear */ clear: function() { items = {}; save(); } }; load(); return LocalStorage; }); // Included from: js/tinymce/classes/Compat.js /** * Compat.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * TinyMCE core class. * * @static * @class tinymce * @borrow-members tinymce.EditorManager * @borrow-members tinymce.util.Tools */ define("tinymce/Compat", [ "tinymce/dom/DOMUtils", "tinymce/dom/EventUtils", "tinymce/dom/ScriptLoader", "tinymce/AddOnManager", "tinymce/util/Tools", "tinymce/Env" ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) { var tinymce = window.tinymce; /** * @property {tinymce.dom.DOMUtils} DOM Global DOM instance. * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance. * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance. * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance. */ tinymce.DOM = DOMUtils.DOM; tinymce.ScriptLoader = ScriptLoader.ScriptLoader; tinymce.PluginManager = AddOnManager.PluginManager; tinymce.ThemeManager = AddOnManager.ThemeManager; tinymce.dom = tinymce.dom || {}; tinymce.dom.Event = EventUtils.Event; Tools.each(Tools, function(func, key) { tinymce[key] = func; }); Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) { tinymce[name] = Env[name.substr(2).toLowerCase()]; }); return {}; }); // Describe the different namespaces /** * Root level namespace this contains classes directly releated to the TinyMCE editor. * * @namespace tinymce */ /** * Contains classes for handling the browsers DOM. * * @namespace tinymce.dom */ /** * Contains html parser and serializer logic. * * @namespace tinymce.html */ /** * Contains the different UI types such as buttons, listboxes etc. * * @namespace tinymce.ui */ /** * Contains various utility classes such as json parser, cookies etc. * * @namespace tinymce.util */ // Included from: js/tinymce/classes/ui/Layout.js /** * Layout.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Base layout manager class. * * @class tinymce.ui.Layout */ define("tinymce/ui/Layout", [ "tinymce/util/Class", "tinymce/util/Tools" ], function(Class, Tools) { "use strict"; return Class.extend({ Defaults: { firstControlClass: 'first', lastControlClass: 'last' }, /** * Constructs a layout instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { this.settings = Tools.extend({}, this.Defaults, settings); }, /** * This method gets invoked before the layout renders the controls. * * @method preRender * @param {tinymce.ui.Container} container Container instance to preRender. */ preRender: function(container) { container.addClass(this.settings.containerClass, 'body'); }, /** * Applies layout classes to the container. * * @private */ applyClasses: function(container) { var self = this, settings = self.settings, items, firstClass, lastClass; items = container.items().filter(':visible'); firstClass = settings.firstControlClass; lastClass = settings.lastControlClass; items.each(function(item) { item.removeClass(firstClass).removeClass(lastClass); if (settings.controlClass) { item.addClass(settings.controlClass); } }); items.eq(0).addClass(firstClass); items.eq(-1).addClass(lastClass); }, /** * Renders the specified container and any layout specific HTML. * * @method renderHtml * @param {tinymce.ui.Container} container Container to render HTML for. */ renderHtml: function(container) { var self = this, settings = self.settings, items, html = ''; items = container.items(); items.eq(0).addClass(settings.firstControlClass); items.eq(-1).addClass(settings.lastControlClass); items.each(function(item) { if (settings.controlClass) { item.addClass(settings.controlClass); } html += item.renderHtml(); }); return html; }, /** * Recalculates the positions of the controls in the specified container. * * @method recalc * @param {tinymce.ui.Container} container Container instance to recalc. */ recalc: function() { }, /** * This method gets invoked after the layout renders the controls. * * @method postRender * @param {tinymce.ui.Container} container Container instance to postRender. */ postRender: function() { } }); }); // Included from: js/tinymce/classes/ui/AbsoluteLayout.js /** * AbsoluteLayout.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * LayoutManager for absolute positioning. This layout manager is more of * a base class for other layouts but can be created and used directly. * * @-x-less AbsoluteLayout.less * @class tinymce.ui.AbsoluteLayout * @extends tinymce.ui.Layout */ define("tinymce/ui/AbsoluteLayout", [ "tinymce/ui/Layout" ], function(Layout) { "use strict"; return Layout.extend({ Defaults: { containerClass: 'abs-layout', controlClass: 'abs-layout-item' }, /** * Recalculates the positions of the controls in the specified container. * * @method recalc * @param {tinymce.ui.Container} container Container instance to recalc. */ recalc: function(container) { container.items().filter(':visible').each(function(ctrl) { var settings = ctrl.settings; ctrl.layoutRect({ x: settings.x, y: settings.y, w: settings.w, h: settings.h }); if (ctrl.recalc) { ctrl.recalc(); } }); }, /** * Renders the specified container and any layout specific HTML. * * @method renderHtml * @param {tinymce.ui.Container} container Container to render HTML for. */ renderHtml: function(container) { return '' + this._super(container); } }); }); // Included from: js/tinymce/classes/ui/Tooltip.js /** * Tooltip.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a tooltip instance. * * @-x-less ToolTip.less * @class tinymce.ui.ToolTip * @extends tinymce.ui.Control * @mixes tinymce.ui.Movable */ define("tinymce/ui/Tooltip", [ "tinymce/ui/Control", "tinymce/ui/Movable" ], function(Control, Movable) { return Control.extend({ Mixins: [Movable], Defaults: { classes: 'widget tooltip tooltip-n' }, /** * Sets/gets the current label text. * * @method text * @param {String} [text] New label text. * @return {String|tinymce.ui.Tooltip} Current text or current label instance. */ text: function(value) { var self = this; if (typeof(value) != "undefined") { self._value = value; if (self._rendered) { self.getEl().lastChild.innerHTML = self.encode(value); } return self; } return self._value; }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, prefix = self.classPrefix; return ( ' ' + '' + '' ); }, /** * Repaints the control after a layout operation. * * @method repaint */ repaint: function() { var self = this, style, rect; style = self.getEl().style; rect = self._layoutRect; style.left = rect.x + 'px'; style.top = rect.y + 'px'; style.zIndex = 0xFFFF + 0xFFFF; } }); }); // Included from: js/tinymce/classes/ui/Widget.js /** * Widget.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Widget base class a widget is a control that has a tooltip and some basic states. * * @class tinymce.ui.Widget * @extends tinymce.ui.Control */ define("tinymce/ui/Widget", [ "tinymce/ui/Control", "tinymce/ui/Tooltip" ], function(Control, Tooltip) { "use strict"; var tooltip; var Widget = Control.extend({ /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {String} tooltip Tooltip text to display when hovering. * @setting {Boolean} autofocus True if the control should be focused when rendered. * @setting {String} text Text to display inside widget. */ init: function(settings) { var self = this; self._super(settings); self.canFocus = true; if (settings.tooltip && Widget.tooltips !== false) { self.on('mouseenter mouseleave', function(e) { var tooltip = self.tooltip().moveTo(-0xFFFF); if (e.control == self && e.type == 'mouseenter') { var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']); tooltip.toggleClass('tooltip-n', rel == 'bc-tc'); tooltip.toggleClass('tooltip-nw', rel == 'bc-tl'); tooltip.toggleClass('tooltip-ne', rel == 'bc-tr'); tooltip.moveRel(self.getEl(), rel); } else { tooltip.hide(); } }); } self.aria('label', settings.tooltip); }, /** * Returns the current tooltip instance. * * @method tooltip * @return {tinymce.ui.Tooltip} Tooltip instance. */ tooltip: function() { var self = this; if (!tooltip) { tooltip = new Tooltip({type: 'tooltip'}); tooltip.renderTo(self.getContainerElm()); } return tooltip; }, /** * Sets/gets the active state of the widget. * * @method active * @param {Boolean} [state] State if the control is active. * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance. */ active: function(state) { var self = this, undef; if (state !== undef) { self.aria('pressed', state); self.toggleClass('active', state); } return self._super(state); }, /** * Sets/gets the disabled state of the widget. * * @method disabled * @param {Boolean} [state] State if the control is disabled. * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance. */ disabled: function(state) { var self = this, undef; if (state !== undef) { self.aria('disabled', state); self.toggleClass('disabled', state); } return self._super(state); }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this, settings = self.settings; self._rendered = true; self._super(); if (!self.parent() && (settings.width || settings.height)) { self.initLayoutRect(); self.repaint(); } if (settings.autofocus) { setTimeout(function() { self.focus(); }, 0); } }, /** * Removes the current control from DOM and from UI collections. * * @method remove * @return {tinymce.ui.Control} Current control instance. */ remove: function() { this._super(); if (tooltip) { tooltip.remove(); tooltip = null; } } }); return Widget; }); // Included from: js/tinymce/classes/ui/Button.js /** * Button.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 create buttons. You can create them directly or through the Factory. * * @example * // Create and render a button to the body element * tinymce.ui.Factory.create({ * type: 'button', * text: 'My button' * }).renderTo(document.body); * * @-x-less Button.less * @class tinymce.ui.Button * @extends tinymce.ui.Widget */ define("tinymce/ui/Button", [ "tinymce/ui/Widget" ], function(Widget) { "use strict"; return Widget.extend({ Defaults: { classes: "widget btn", role: "button" }, /** * Constructs a new button instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {String} size Size of the button small|medium|large. * @setting {String} image Image to use for icon. * @setting {String} icon Icon to use for button. */ init: function(settings) { var self = this, size; self.on('click mousedown', function(e) { e.preventDefault(); }); self._super(settings); size = settings.size; if (settings.subtype) { self.addClass(settings.subtype); } if (size) { self.addClass('btn-' + size); } }, /** * Repaints the button for example after it's been resizes by a layout engine. * * @method repaint */ repaint: function() { var btnStyle = this.getEl().firstChild.style; btnStyle.width = btnStyle.height = "100%"; this._super(); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, prefix = self.classPrefix; var icon = self.settings.icon, image = ''; if (self.settings.image) { icon = 'none'; image = ' style="background-image: url(\'' + self.settings.image + '\')"'; } icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; return ( '' + self.encode(self._text) + '' + '' + '' + '' ); } }); }); // Included from: js/tinymce/classes/ui/ButtonGroup.js /** * ButtonGroup.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This control enables you to put multiple buttons into a group. This is * useful when you want to combine similar toolbar buttons into a group. * * @example * // Create and render a buttongroup with two buttons to the body element * tinymce.ui.Factory.create({ * type: 'buttongroup', * items: [ * {text: 'Button A'}, * {text: 'Button B'} * ] * }).renderTo(document.body); * * @-x-less ButtonGroup.less * @class tinymce.ui.ButtonGroup * @extends tinymce.ui.Container */ define("tinymce/ui/ButtonGroup", [ "tinymce/ui/Container" ], function(Container) { "use strict"; return Container.extend({ Defaults: { defaultType: 'button', role: 'toolbar' }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, layout = self._layout; self.addClass('btn-group'); self.preRender(); layout.preRender(self); return ( ''+ '' ); } }); }); // Included from: js/tinymce/classes/ui/Checkbox.js /** * Checkbox.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This control creates a custom checkbox. * * @example * // Create and render a checkbox to the body element * tinymce.ui.Factory.create({ * type: 'checkbox', * checked: true, * text: 'My checkbox' * }).renderTo(document.body); * * @-x-less Checkbox.less * @class tinymce.ui.Checkbox * @extends tinymce.ui.Widget */ define("tinymce/ui/Checkbox", [ "tinymce/ui/Widget" ], function(Widget) { "use strict"; return Widget.extend({ Defaults: { classes: "checkbox", role: "checkbox", checked: false }, /** * Constructs a new Checkbox instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {Boolean} checked True if the checkbox should be checked by default. */ init: function(settings) { var self = this; self._super(settings); self.on('click mousedown', function(e) { e.preventDefault(); }); self.on('click', function(e) { e.preventDefault(); if (!self.disabled()) { self.checked(!self.checked()); } }); self.checked(self.settings.checked); }, /** * Getter/setter function for the checked state. * * @method checked * @param {Boolean} [state] State to be set. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. */ checked: function(state) { var self = this; if (typeof state != "undefined") { if (state) { self.addClass('checked'); } else { self.removeClass('checked'); } self._checked = state; self.aria('checked', state); return self; } return self._checked; }, /** * Getter/setter function for the value state. * * @method value * @param {Boolean} [state] State to be set. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. */ value: function(state) { return this.checked(state); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, prefix = self.classPrefix; return ( ''+ (self.settings.html || '') + layout.renderHtml(self) + '' + '' + '' + '' + self.encode(self._text) + '' + '' ); } }); }); // Included from: js/tinymce/classes/ui/PanelButton.js /** * PanelButton.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new panel button. * * @class tinymce.ui.PanelButton * @extends tinymce.ui.Button */ define("tinymce/ui/PanelButton", [ "tinymce/ui/Button", "tinymce/ui/FloatPanel" ], function(Button, FloatPanel) { "use strict"; return Button.extend({ /** * Shows the panel for the button. * * @method showPanel */ showPanel: function() { var self = this, settings = self.settings; self.active(true); if (!self.panel) { var panelSettings = settings.panel; // Wrap panel in grid layout if type if specified // This makes it possible to add forms or other containers directly in the panel option if (panelSettings.type) { panelSettings = { layout: 'grid', items: panelSettings }; } panelSettings.popover = true; panelSettings.autohide = true; self.panel = new FloatPanel(panelSettings).on('hide', function() { self.active(false); }).parent(self).renderTo(self.getContainerElm()); self.panel.fire('show'); self.panel.reflow(); } else { self.panel.show(); } self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc'])); }, /** * Hides the panel for the button. * * @method hidePanel */ hidePanel: function() { var self = this; if (self.panel) { self.panel.hide(); } }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this; self.on('click', function(e) { if (e.control === self) { if (self.panel && self.panel.visible()) { self.hidePanel(); } else { self.showPanel(); } } }); return self._super(); } }); }); // Included from: js/tinymce/classes/ui/ColorButton.js /** * ColorButton.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class creates a color button control. This is a split button in which the main * button has a visual representation of the currently selected color. When clicked * the caret button displays a color picker, allowing the user to select a new color. * * @-x-less ColorButton.less * @class tinymce.ui.ColorButton * @extends tinymce.ui.PanelButton */ define("tinymce/ui/ColorButton", [ "tinymce/ui/PanelButton", "tinymce/dom/DOMUtils" ], function(PanelButton, DomUtils) { "use strict"; var DOM = DomUtils.DOM; return PanelButton.extend({ /** * Constructs a new ColorButton instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { this._super(settings); this.addClass('colorbutton'); }, /** * Getter/setter for the current color. * * @method color * @param {String} [color] Color to set. * @return {String|tinymce.ui.ColorButton} Current color or current instance. */ color: function(color) { if (color) { this._color = color; this.getEl('preview').style.backgroundColor = color; return this; } return this._color; }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, prefix = self.classPrefix; var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : ''; return ( '' + '' + '' + '' ); }, /** * Called after the control has been rendered. * 这里绑定事件 * * @method postRender */ postRender: function() { var self = this, onClickHandler = self.settings.onclick; self.on('click', function(e) { if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) { e.stopImmediatePropagation(); onClickHandler.call(self, e); } }); delete self.settings.onclick; return self._super(); } }); }); // Included from: js/tinymce/classes/ui/ComboBox.js /** * ComboBox.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class creates a combobox control. Select box that you select a value from or * type a value into. * * @-x-less ComboBox.less * @class tinymce.ui.ComboBox * @extends tinymce.ui.Widget */ define("tinymce/ui/ComboBox", [ "tinymce/ui/Widget", "tinymce/ui/DomUtils" ], function(Widget, DomUtils) { "use strict"; return Widget.extend({ /** * Constructs a new control instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {String} placeholder Placeholder text to display. */ init: function(settings) { var self = this; self._super(settings); self.addClass('combobox'); self.on('click', function(e) { var elm = e.target; while (elm) { if (elm.id && elm.id.indexOf('-open') != -1) { self.fire('action'); } elm = elm.parentNode; } }); // TODO: Rework this self.on('keydown', function(e) { if (e.target.nodeName == "INPUT" && e.keyCode == 13) { self.parents().reverse().each(function(ctrl) { e.preventDefault(); self.fire('change'); if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { ctrl.fire('submit', {data: ctrl.toJSON()}); return false; } }); } }); if (settings.placeholder) { self.addClass('placeholder'); self.on('focusin', function() { if (!self._hasOnChange) { DomUtils.on(self.getEl('inp'), 'change', function() { self.fire('change'); }); self._hasOnChange = true; } if (self.hasClass('placeholder')) { self.getEl('inp').value = ''; self.removeClass('placeholder'); } }); self.on('focusout', function() { if (self.value().length === 0) { self.getEl('inp').value = settings.placeholder; self.addClass('placeholder'); } }); } }, /** * Getter/setter function for the control value. * * @method value * @param {String} [value] Value to be set. * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation. */ value: function(value) { var self = this; if (typeof(value) != "undefined") { self._value = value; self.removeClass('placeholder'); if (self._rendered) { self.getEl('inp').value = value; } return self; } if (self._rendered) { value = self.getEl('inp').value; if (value != self.settings.placeholder) { return value; } return ''; } return self._value; }, /** * Getter/setter function for the disabled state. * * @method value * @param {Boolean} [state] State to be set. * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation. */ disabled: function(state) { var self = this; if (self._rendered && typeof(state) != 'undefined') { self.getEl('inp').disabled = state; } return self._super(state); }, /** * Focuses the input area of the control. * * @method focus */ focus: function() { this.getEl('inp').focus(); }, /** * Repaints the control after a layout operation. * * @method repaint */ repaint: function() { var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect(); var width, lineHeight; if (openElm) { width = rect.w - DomUtils.getSize(openElm).width - 10; } else { width = rect.w - 10; } // Detect old IE 7+8 add lineHeight to align caret vertically in the middle var doc = document; if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) { lineHeight = (self.layoutRect().h - 2) + 'px'; } DomUtils.css(elm.firstChild, { width: width, lineHeight: lineHeight }); self._super(); return self; }, /** * Post render method. Called after the control has been rendered to the target. * * @method postRender * @return {tinymce.ui.ComboBox} Current combobox instance. */ postRender: function() { var self = this; DomUtils.on(this.getEl('inp'), 'change', function() { self.fire('change'); }); return self._super(); }, remove: function() { DomUtils.off(this.getEl('inp')); this._super(); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix; var value = settings.value || settings.placeholder || ''; var icon, text, openBtnHtml = ''; icon = settings.icon ? prefix + 'ico ' + prefix + 'i-' + settings.icon : ''; text = self._text; if (icon || text) { openBtnHtml = ( '' + '' + '' ); self.addClass('has-open'); } return ( '' + '' + openBtnHtml + '' ); } }); }); // Included from: js/tinymce/classes/ui/Path.js /** * Path.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new path control. * * @-x-less Path.less * @class tinymce.ui.Path * @extends tinymce.ui.Widget */ define("tinymce/ui/Path", [ "tinymce/ui/Widget", "tinymce/ui/KeyboardNavigation" ], function(Widget, KeyboardNavigation) { "use strict"; return Widget.extend({ /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {String} delimiter Delimiter to display between items in path. */ init: function(settings) { var self = this; if (!settings.delimiter) { settings.delimiter = '\u00BB'; } self._super(settings); self.addClass('path'); self.canFocus = true; self.on('click', function(e) { var index, target = e.target; if ((index = target.getAttribute('data-index'))) { self.fire('select', {value: self.data()[index], index: index}); } }); }, /** * Focuses the current control. * * @method focus * @return {tinymce.ui.Control} Current control instance. */ focus: function() { var self = this; self.keyNav = new KeyboardNavigation({ root: self, enableLeftRight: true }); self.keyNav.focusFirst(); return self; }, /** * Sets/gets the data to be used for the path. * * @method data * @param {Array} data Array with items name is rendered to path. */ data: function(data) { var self = this; if (typeof(data) !== "undefined") { self._data = data; self.update(); return self; } return self._data; }, /** * Updated the path. * * @private */ update: function() { this.innerHtml(this._getPathHtml()); }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this; self._super(); self.data(self.settings.data); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this; return ( '' + self._getPathHtml() + '' ); }, _getPathHtml: function() { var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix; for (i = 0, l = parts.length; i < l; i++) { html += ( (i > 0 ? ' ' : '') + '' + parts[i].name + '' ); } if (!html) { html = ''; } return html; } }); }); // Included from: js/tinymce/classes/ui/ElementPath.js /** * ElementPath.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This control creates an path for the current selections parent elements in TinyMCE. * * @class tinymce.ui.ElementPath * @extends tinymce.ui.Path */ define("tinymce/ui/ElementPath", [ "tinymce/ui/Path", "tinymce/EditorManager" ], function(Path, EditorManager) { return Path.extend({ /** * Post render method. Called after the control has been rendered to the target. * * @method postRender * @return {tinymce.ui.ElementPath} Current combobox instance. */ postRender: function() { var self = this, editor = EditorManager.activeEditor; function isHidden(elm) { if (elm.nodeType === 1) { if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) { return true; } if (elm.getAttribute('data-mce-type') === 'bookmark') { return true; } } return false; } self.on('select', function(e) { var parents = [], node, body = editor.getBody(); editor.focus(); node = editor.selection.getStart(); while (node && node != body) { if (!isHidden(node)) { parents.push(node); } node = node.parentNode; } editor.selection.select(parents[parents.length - 1 - e.index]); editor.nodeChanged(); }); editor.on('nodeChange', function(e) { var parents = [], selectionParents = e.parents, i = selectionParents.length; while (i--) { if (selectionParents[i].nodeType == 1 && !isHidden(selectionParents[i])) { var args = editor.fire('ResolveName', { name: selectionParents[i].nodeName.toLowerCase(), target: selectionParents[i] }); parents.push({name: args.name}); } } self.data(parents); }); return self._super(); } }); }); // Included from: js/tinymce/classes/ui/FormItem.js /** * FormItem.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 container created by the form element with * a label and control item. * * @class tinymce.ui.FormItem * @extends tinymce.ui.Container * @setting {String} label Label to display for the form item. */ define("tinymce/ui/FormItem", [ "tinymce/ui/Container" ], function(Container) { "use strict"; return Container.extend({ Defaults: { layout: 'flex', align: 'center', defaults: { flex: 1 } }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, layout = self._layout, prefix = self.classPrefix; self.addClass('formitem'); layout.preRender(self); return ( '' + (self.settings.title ? ('' ); } }); }); // Included from: js/tinymce/classes/ui/Form.js /** * Form.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class creates a form container. A form container has the ability * to automatically wrap items in tinymce.ui.FormItem instances. * * Each FormItem instance is a container for the label and the item. * * @example * tinymce.ui.Factory.create({ * type: 'form', * items: [ * {type: 'textbox', label: 'My text box'} * ] * }).renderTo(document.body); * * @class tinymce.ui.Form * @extends tinymce.ui.Container */ define("tinymce/ui/Form", [ "tinymce/ui/Container", "tinymce/ui/FormItem" ], function(Container, FormItem) { "use strict"; return Container.extend({ Defaults: { containerCls: 'form', layout: 'flex', direction: 'column', align: 'stretch', flex: 1, padding: 20, labelGap: 30, spacing: 10, callbacks: { submit: function() { this.submit(); } } }, /** * This method gets invoked before the control is rendered. * * @method preRender */ preRender: function() { var self = this, items = self.items(); // Wrap any labeled items in FormItems items.each(function(ctrl) { var formItem, label = ctrl.settings.label; if (label) { formItem = new FormItem({ layout: 'flex', autoResize: "overflow", defaults: {flex: 1}, items: [ {type: 'label', text: label, flex: 0, forId: ctrl._id} ] }); formItem.type = 'formitem'; if (typeof(ctrl.settings.flex) == "undefined") { ctrl.settings.flex = 1; } self.replace(ctrl, formItem); formItem.add(ctrl); } }); }, /** * Recalcs label widths. * * @private */ recalcLabels: function() { var self = this, maxLabelWidth = 0, labels = [], i, labelGap; if (self.settings.labelGapCalc === false) { return; } self.items().filter('formitem').each(function(item) { var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth; maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth; labels.push(labelCtrl); }); labelGap = self.settings.labelGap || 0; i = labels.length; while (i--) { labels[i].settings.minWidth = maxLabelWidth + labelGap; } }, /** * Getter/setter for the visibility state. * * @method visible * @param {Boolean} [state] True/false state to show/hide. * @return {tinymce.ui.Form|Boolean} True/false state or current control. */ visible: function(state) { var val = this._super(state); if (state === true && this._rendered) { this.recalcLabels(); } return val; }, /** * Fires a submit event with the serialized form. * * @method submit * @return {Object} Event arguments object. */ submit: function() { return this.fire('submit', {data: this.toJSON()}); }, /** * Post render method. Called after the control has been rendered to the target. * * @method postRender * @return {tinymce.ui.ComboBox} Current combobox instance. */ postRender: function() { var self = this; self._super(); self.recalcLabels(); self.fromJSON(self.settings.data); } }); }); // Included from: js/tinymce/classes/ui/FieldSet.js /** * FieldSet.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class creates fieldset containers. * * @-x-less FieldSet.less * @class tinymce.ui.FieldSet * @extends tinymce.ui.Form */ define("tinymce/ui/FieldSet", [ "tinymce/ui/Form" ], function(Form) { "use strict"; return Form.extend({ Defaults: { containerCls: 'fieldset', layout: 'flex', direction: 'column', align: 'stretch', flex: 1, padding: "25 15 5 15", labelGap: 30, spacing: 10, border: 1 }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, layout = self._layout, prefix = self.classPrefix; self.preRender(); layout.preRender(self); return ( '' ); } }); }); // Included from: js/tinymce/classes/ui/FilePicker.js /** * FilePicker.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /*global tinymce:true */ /** * This class creates a file picker control. * * @class tinymce.ui.FilePicker * @extends tinymce.ui.ComboBox */ define("tinymce/ui/FilePicker", [ "tinymce/ui/ComboBox" ], function(ComboBox) { "use strict"; return ComboBox.extend({ /** * Constructs a new control instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { var self = this, editor = tinymce.activeEditor, fileBrowserCallback; settings.spellcheck = false; fileBrowserCallback = editor.settings.file_browser_callback; if (fileBrowserCallback) { settings.icon = 'browse'; settings.onaction = function() { fileBrowserCallback( self.getEl('inp').id, self.getEl('inp').value, settings.filetype, window ); }; } self._super(settings); } }); }); // Included from: js/tinymce/classes/ui/FitLayout.js /** * FitLayout.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This layout manager will resize the control to be the size of it's parent container. * In other words width: 100% and height: 100%. * * @-x-less FitLayout.less * @class tinymce.ui.FitLayout * @extends tinymce.ui.AbsoluteLayout */ define("tinymce/ui/FitLayout", [ "tinymce/ui/AbsoluteLayout" ], function(AbsoluteLayout) { "use strict"; return AbsoluteLayout.extend({ /** * Recalculates the positions of the controls in the specified container. * * @method recalc * @param {tinymce.ui.Container} container Container instance to recalc. */ recalc: function(container) { var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox(); container.items().filter(':visible').each(function(ctrl) { ctrl.layoutRect({ x: paddingBox.left, y: paddingBox.top, w: contLayoutRect.innerW - paddingBox.right - paddingBox.left, h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom }); if (ctrl.recalc) { ctrl.recalc(); } }); } }); }); // Included from: js/tinymce/classes/ui/FlexLayout.js /** * FlexLayout.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This layout manager works similar to the CSS flex box. * * @setting {String} direction row|row-reverse|column|column-reverse * @setting {Number} flex A positive-number to flex by. * @setting {String} align start|end|center|stretch * @setting {String} pack start|end|justify * * @class tinymce.ui.FlexLayout * @extends tinymce.ui.AbsoluteLayout */ define("tinymce/ui/FlexLayout", [ "tinymce/ui/AbsoluteLayout" ], function(AbsoluteLayout) { "use strict"; return AbsoluteLayout.extend({ /** * Recalculates the positions of the controls in the specified container. * * @method recalc * @param {tinymce.ui.Container} container Container instance to recalc. */ recalc: function(container) { // A ton of variables, needs to be in the same scope for performance var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction; var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos; var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, afterName, deltaSizeName, contentSizeName; var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignMaxSizeName, alignBeforeName, alignAfterName; var alignDeltaSizeName, alignContentSizeName; var max = Math.max, min = Math.min; // Get container items, properties and settings items = container.items().filter(':visible'); contLayoutRect = container.layoutRect(); contPaddingBox = container._paddingBox; contSettings = container.settings; direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction; align = contSettings.align; pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack; spacing = contSettings.spacing || 0; if (direction == "row-reversed" || direction == "column-reverse") { items = items.set(items.toArray().reverse()); direction = direction.split('-')[0]; } // Setup axis variable name for row/column direction since the calculations is the same if (direction == "column") { posName = "y"; sizeName = "h"; minSizeName = "minH"; maxSizeName = "maxH"; innerSizeName = "innerH"; beforeName = 'top'; afterName = 'bottom'; deltaSizeName = "deltaH"; contentSizeName = "contentH"; alignBeforeName = "left"; alignSizeName = "w"; alignAxisName = "x"; alignInnerSizeName = "innerW"; alignMinSizeName = "minW"; alignMaxSizeName = "maxW"; alignAfterName = "right"; alignDeltaSizeName = "deltaW"; alignContentSizeName = "contentW"; } else { posName = "x"; sizeName = "w"; minSizeName = "minW"; maxSizeName = "maxW"; innerSizeName = "innerW"; beforeName = 'left'; afterName = 'right'; deltaSizeName = "deltaW"; contentSizeName = "contentW"; alignBeforeName = "top"; alignSizeName = "h"; alignAxisName = "y"; alignInnerSizeName = "innerH"; alignMinSizeName = "minH"; alignMaxSizeName = "maxH"; alignAfterName = "bottom"; alignDeltaSizeName = "deltaH"; alignContentSizeName = "contentH"; } // Figure out total flex, availableSpace and collect any max size elements availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName]; maxAlignEndPos = totalFlex = 0; for (i = 0, l = items.length; i < l; i++) { ctrl = items[i]; ctrlLayoutRect = ctrl.layoutRect(); ctrlSettings = ctrl.settings; flex = ctrlSettings.flex; availableSpace -= (i < l - 1 ? spacing : 0); if (flex > 0) { totalFlex += flex; // Flexed item has a max size then we need to check if we will hit that size if (ctrlLayoutRect[maxSizeName]) { maxSizeItems.push(ctrl); } ctrlLayoutRect.flex = flex; } availableSpace -= ctrlLayoutRect[minSizeName]; // Calculate the align end position to be used to check for overflow/underflow size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName]; if (size > maxAlignEndPos) { maxAlignEndPos = size; } } // Calculate minW/minH rect = {}; if (availableSpace < 0) { rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName]; } else { rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName]; } rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName]; rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace; rect[alignContentSizeName] = maxAlignEndPos; rect.minW = min(rect.minW, contLayoutRect.maxW); rect.minH = min(rect.minH, contLayoutRect.maxH); rect.minW = max(rect.minW, contLayoutRect.startMinWidth); rect.minH = max(rect.minH, contLayoutRect.startMinHeight); // Resize container container if minSize was changed if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { rect.w = rect.minW; rect.h = rect.minH; container.layoutRect(rect); this.recalc(container); // Forced recalc for example if items are hidden/shown if (container._lastRect === null) { var parentCtrl = container.parent(); if (parentCtrl) { parentCtrl._lastRect = null; parentCtrl.recalc(); } } return; } // Handle max size elements, check if they will become to wide with current options ratio = availableSpace / totalFlex; for (i = 0, l = maxSizeItems.length; i < l; i++) { ctrl = maxSizeItems[i]; ctrlLayoutRect = ctrl.layoutRect(); maxSize = ctrlLayoutRect[maxSizeName]; size = ctrlLayoutRect[minSizeName] + Math.ceil(ctrlLayoutRect.flex * ratio); if (size > maxSize) { availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]); totalFlex -= ctrlLayoutRect.flex; ctrlLayoutRect.flex = 0; ctrlLayoutRect.maxFlexSize = maxSize; } else { ctrlLayoutRect.maxFlexSize = 0; } } // Setup new ratio, target layout rect, start position ratio = availableSpace / totalFlex; pos = contPaddingBox[beforeName]; rect = {}; // Handle pack setting moves the start position to end, center if (totalFlex === 0) { if (pack == "end") { pos = availableSpace + contPaddingBox[beforeName]; } else if (pack == "center") { pos = Math.round( (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2) ) + contPaddingBox[beforeName]; if (pos < 0) { pos = contPaddingBox[beforeName]; } } else if (pack == "justify") { pos = contPaddingBox[beforeName]; spacing = Math.floor(availableSpace / (items.length - 1)); } } // Default aligning (start) the other ones needs to be calculated while doing the layout rect[alignAxisName] = contPaddingBox[alignBeforeName]; // Start laying out controls for (i = 0, l = items.length; i < l; i++) { ctrl = items[i]; ctrlLayoutRect = ctrl.layoutRect(); size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName]; // Align the control on the other axis if (align === "center") { rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2)); } else if (align === "stretch") { rect[alignSizeName] = max( ctrlLayoutRect[alignMinSizeName] || 0, contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName] ); rect[alignAxisName] = contPaddingBox[alignBeforeName]; } else if (align === "end") { rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top; } // Calculate new size based on flex if (ctrlLayoutRect.flex > 0) { size += Math.ceil(ctrlLayoutRect.flex * ratio); } rect[sizeName] = size; rect[posName] = pos; ctrl.layoutRect(rect); // Recalculate containers if (ctrl.recalc) { ctrl.recalc(); } // Move x/y position pos += size + spacing; } } }); }); // Included from: js/tinymce/classes/ui/FlowLayout.js /** * FlowLayout.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This layout manager will place the controls by using the browsers native layout. * * @-x-less FlowLayout.less * @class tinymce.ui.FlowLayout * @extends tinymce.ui.Layout */ define("tinymce/ui/FlowLayout", [ "tinymce/ui/Layout" ], function(Layout) { return Layout.extend({ Defaults: { containerClass: 'flow-layout', controlClass: 'flow-layout-item', endClass : 'break' }, /** * Recalculates the positions of the controls in the specified container. * * @method recalc * @param {tinymce.ui.Container} container Container instance to recalc. */ recalc: function(container) { container.items().filter(':visible').each(function(ctrl) { if (ctrl.recalc) { ctrl.recalc(); } }); } }); }); // Included from: js/tinymce/classes/ui/FormatControls.js /** * FormatControls.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Internal class containing all TinyMCE specific control types such as * format listboxes, fontlist boxes, toolbar buttons etc. * * @class tinymce.ui.FormatControls */ define("tinymce/ui/FormatControls", [ "tinymce/ui/Control", "tinymce/ui/Widget", "tinymce/ui/FloatPanel", "tinymce/util/Tools", "tinymce/EditorManager", "tinymce/Env" ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) { var each = Tools.each; EditorManager.on('AddEditor', function(e) { if (e.editor.rtl) { Control.rtl = true; } registerControls(e.editor); }); Control.translate = function(text) { return EditorManager.translate(text); }; Widget.tooltips = !Env.iOS; function registerControls(editor) { var formatMenu; // Generates a preview for a format function getPreviewCss(format) { var name, previewElm, dom = editor.dom; var previewCss = '', parentFontSize, previewStyles; previewStyles = editor.settings.preview_styles; // No preview forced if (previewStyles === false) { return ''; } // Default preview if (!previewStyles) { previewStyles = 'font-family font-size font-weight text-decoration ' + 'text-transform color background-color border border-radius'; } // Removes any variables since these can't be previewed function removeVars(val) { return val.replace(/%(\w+)/g, ''); } // Create block/inline element to use for preview format = editor.formatter.get(format); if (!format) { return; } format = format[0]; name = format.block || format.inline || 'span'; previewElm = dom.create(name); // Add format styles to preview element each(format.styles, function(value, name) { value = removeVars(value); if (value) { dom.setStyle(previewElm, name, value); } }); // Add attributes to preview element each(format.attributes, function(value, name) { value = removeVars(value); if (value) { dom.setAttrib(previewElm, name, value); } }); // Add classes to preview element each(format.classes, function(value) { value = removeVars(value); if (!dom.hasClass(previewElm, value)) { dom.addClass(previewElm, value); } }); editor.fire('PreviewFormats'); // Add the previewElm outside the visual area dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF}); editor.getBody().appendChild(previewElm); // Get parent container font size so we can compute px values out of em/% for older IE:s parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true); parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0; each(previewStyles.split(' '), function(name) { var value = dom.getStyle(previewElm, name, true); // If background is transparent then check if the body has a background color we can use if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) { value = dom.getStyle(editor.getBody(), name, true); // Ignore white since it's the default color, not the nicest fix // TODO: Fix this by detecting runtime style if (dom.toHex(value).toLowerCase() == '#ffffff') { return; } } if (name == 'color') { // Ignore black since it's the default color, not the nicest fix // TODO: Fix this by detecting runtime style if (dom.toHex(value).toLowerCase() == '#000000') { return; } } // Old IE won't calculate the font size so we need to do that manually if (name == 'font-size') { if (/em|%$/.test(value)) { if (parentFontSize === 0) { return; } // Convert font size from em/% to px value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1); value = (value * parentFontSize) + 'px'; } } if (name == "border" && value) { previewCss += 'padding:0 2px;'; } previewCss += name + ':' + value + ';'; }); editor.fire('AfterPreviewFormats'); //previewCss += 'line-height:normal'; dom.remove(previewElm); return previewCss; } function createListBoxChangeHandler(items, formatName) { return function() { var self = this; editor.on('nodeChange', function(e) { var formatter = editor.formatter; var value = null; each(e.parents, function(node) { each(items, function(item) { if (formatName) { if (formatter.matchNode(node, formatName, {value: item.value})) { value = item.value; } } else { if (formatter.matchNode(node, item.value)) { value = item.value; } } if (value) { return false; } }); if (value) { return false; } }); self.value(value); }); }; } function createFormats(formats) { formats = formats.split(';'); var i = formats.length; while (i--) { formats[i] = formats[i].split('='); } return formats; } function createFormatMenu() { var count = 0, newFormats = []; var defaultStyleFormats = [ {title: 'Headers', items: [ {title: 'Header 1', format: 'h1'}, {title: 'Header 2', format: 'h2'}, {title: 'Header 3', format: 'h3'}, {title: 'Header 4', format: 'h4'}, {title: 'Header 5', format: 'h5'}, {title: 'Header 6', format: 'h6'} ]}, {title: 'Inline', items: [ {title: 'Bold', icon: 'bold', format: 'bold'}, {title: 'Italic', icon: 'italic', format: 'italic'}, {title: 'Underline', icon: 'underline', format: 'underline'}, {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'}, {title: 'Superscript', icon: 'superscript', format: 'superscript'}, {title: 'Subscript', icon: 'subscript', format: 'subscript'}, {title: 'Code', icon: 'code', format: 'code'} ]}, {title: 'Blocks', items: [ {title: 'Paragraph', format: 'p'}, {title: 'Blockquote', format: 'blockquote'}, {title: 'Div', format: 'div'}, {title: 'Pre', format: 'pre'} ]}, {title: 'Alignment', items: [ {title: 'Left', icon: 'alignleft', format: 'alignleft'}, {title: 'Center', icon: 'aligncenter', format: 'aligncenter'}, {title: 'Right', icon: 'alignright', format: 'alignright'}, {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'} ]} ]; function createMenu(formats) { var menu = []; if (!formats) { return; } each(formats, function(format) { var menuItem = { text: format.title, icon: format.icon }; if (format.items) { menuItem.menu = createMenu(format.items); } else { var formatName = format.format || "custom" + count++; if (!format.format) { format.name = formatName; newFormats.push(format); } menuItem.format = formatName; } menu.push(menuItem); }); return menu; } editor.on('init', function() { each(newFormats, function(format) { editor.formatter.register(format.name, format); }); }); var menu = createMenu(editor.settings.style_formats || defaultStyleFormats); menu = { type: 'menu', items: menu, onPostRender: function(e) { editor.fire('renderFormatsMenu', {control: e.control}); }, itemDefaults: { preview: true, textStyle: function() { if (this.settings.format) { return getPreviewCss(this.settings.format); } }, onPostRender: function() { var self = this, formatName = this.settings.format; if (formatName) { self.parent().on('show', function() { self.disabled(!editor.formatter.canApply(formatName)); self.active(editor.formatter.match(formatName)); }); } }, onclick: function() { if (this.settings.format) { toggleFormat(this.settings.format); } } } }; return menu; } formatMenu = createFormatMenu(); // Simple format controls' + self.settings.title + '') : '') + '' + (self.settings.html || '') + layout.renderHtml(self) + '' + ': each({ bold: 'Bold', italic: 'Italic', underline: 'Underline', strikethrough: 'Strikethrough', subscript: 'Subscript', superscript: 'Superscript' }, function(text, name) { editor.addButton(name, { tooltip: text, onPostRender: function() { var self = this; // TODO: Fix this if (editor.formatter) { editor.formatter.formatChanged(name, function(state) { self.active(state); }); } else { editor.on('init', function() { editor.formatter.formatChanged(name, function(state) { self.active(state); }); }); } }, onclick: function() { toggleFormat(name); } }); }); // Simple command controls :[ , ] each({ outdent: ['Decrease indent', 'Outdent'], indent: ['Increase indent', 'Indent'], cut: ['Cut', 'Cut'], copy: ['Copy', 'Copy'], paste: ['Paste', 'Paste'], help: ['Help', 'mceHelp'], selectall: ['Select all', 'SelectAll'], hr: ['Insert horizontal rule', 'InsertHorizontalRule'], removeformat: ['Clear formatting', 'RemoveFormat'], visualaid: ['Visual aids', 'mceToggleVisualAid'], newdocument: ['New document', 'mceNewDocument'] }, function(item, name) { editor.addButton(name, { tooltip: item[0], cmd: item[1] }); }); // Simple command controls with format state each({ blockquote: ['Toggle blockquote', 'mceBlockQuote'], numlist: ['Numbered list', 'InsertOrderedList'], bullist: ['Bullet list', 'InsertUnorderedList'], subscript: ['Subscript', 'Subscript'], superscript: ['Superscript', 'Superscript'], alignleft: ['Align left', 'JustifyLeft'], aligncenter: ['Align center', 'JustifyCenter'], alignright: ['Align right', 'JustifyRight'], alignjustify: ['Justify', 'JustifyFull'] }, function(item, name) { editor.addButton(name, { tooltip: item[0], cmd: item[1], onPostRender: function() { var self = this; // TODO: Fix this if (editor.formatter) { editor.formatter.formatChanged(name, function(state) { self.active(state); }); } else { editor.on('init', function() { editor.formatter.formatChanged(name, function(state) { self.active(state); }); }); } } }); }); function hasUndo() { return editor.undoManager ? editor.undoManager.hasUndo() : false; } function hasRedo() { return editor.undoManager ? editor.undoManager.hasRedo() : false; } function toggleUndoState() { var self = this; self.disabled(!hasUndo()); editor.on('Undo Redo AddUndo TypingUndo', function() { self.disabled(!hasUndo()); }); } function toggleRedoState() { var self = this; self.disabled(!hasRedo()); editor.on('Undo Redo AddUndo TypingUndo', function() { self.disabled(!hasRedo()); }); } function toggleVisualAidState() { var self = this; editor.on('VisualAid', function(e) { self.active(e.hasVisual); }); self.active(editor.hasVisual); } editor.addButton('undo', { tooltip: 'Undo', onPostRender: toggleUndoState, cmd: 'undo' }); editor.addButton('redo', { tooltip: 'Redo', onPostRender: toggleRedoState, cmd: 'redo' }); editor.addMenuItem('newdocument', { text: 'New document', shortcut: 'Ctrl+N', icon: 'newdocument', cmd: 'mceNewDocument' }); editor.addMenuItem('undo', { text: 'Undo', icon: 'undo', shortcut: 'Ctrl+Z', onPostRender: toggleUndoState, cmd: 'undo' }); editor.addMenuItem('redo', { text: 'Redo', icon: 'redo', shortcut: 'Ctrl+Y', onPostRender: toggleRedoState, cmd: 'redo' }); editor.addMenuItem('visualaid', { text: 'Visual aids', selectable: true, onPostRender: toggleVisualAidState, cmd: 'mceToggleVisualAid' }); each({ cut: ['Cut', 'Cut', 'Ctrl+X'], copy: ['Copy', 'Copy', 'Ctrl+C'], paste: ['Paste', 'Paste', 'Ctrl+V'], selectall: ['Select all', 'SelectAll', 'Ctrl+A'], bold: ['Bold', 'Bold', 'Ctrl+B'], italic: ['Italic', 'Italic', 'Ctrl+I'], underline: ['Underline', 'Underline'], strikethrough: ['Strikethrough', 'Strikethrough'], subscript: ['Subscript', 'Subscript'], superscript: ['Superscript', 'Superscript'], removeformat: ['Clear formatting', 'RemoveFormat'] }, function(item, name) { editor.addMenuItem(name, { text: item[0], icon: name, shortcut: item[2], cmd: item[1] }); }); editor.on('mousedown', function() { FloatPanel.hideAll(); }); function toggleFormat(fmt) { if (fmt.control) { fmt = fmt.control.value(); } if (fmt) { editor.execCommand('mceToggleFormat', false, fmt); } } editor.addButton('styleselect', { type: 'menubutton', text: 'Formats', menu: formatMenu }); editor.addButton('formatselect', function() { var items = [], blocks = createFormats(editor.settings.block_formats || 'Paragraph=p;' + 'Address=address;' + 'Pre=pre;' + 'Header 1=h1;' + 'Header 2=h2;' + 'Header 3=h3;' + 'Header 4=h4;' + 'Header 5=h5;' + 'Header 6=h6' ); each(blocks, function(block) { items.push({ text: block[0], value: block[1], textStyle: function() { return getPreviewCss(block[1]); } }); }); return { type: 'listbox', text: {raw: blocks[0][0]}, values: items, fixedWidth: true, onselect: toggleFormat, onPostRender: createListBoxChangeHandler(items) }; }); editor.addButton('fontselect', function() { var defaultFontsFormats = 'Andale Mono=andale mono,times;' + 'Arial=arial,helvetica,sans-serif;' + 'Arial Black=arial black,avant garde;' + 'Book Antiqua=book antiqua,palatino;' + 'Comic Sans MS=comic sans ms,sans-serif;' + 'Courier New=courier new,courier;' + 'Georgia=georgia,palatino;' + 'Helvetica=helvetica;' + 'Impact=impact,chicago;' + 'Symbol=symbol;' + 'Tahoma=tahoma,arial,helvetica,sans-serif;' + 'Terminal=terminal,monaco;' + 'Times New Roman=times new roman,times;' + 'Trebuchet MS=trebuchet ms,geneva;' + 'Verdana=verdana,geneva;' + 'Webdings=webdings;' + 'Wingdings=wingdings,zapf dingbats'; var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats); each(fonts, function(font) { items.push({ text: {raw: font[0]}, value: font[1], textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : '' }); }); return { type: 'listbox', text: 'Font Family', tooltip: 'Font Family', values: items, fixedWidth: true, onPostRender: createListBoxChangeHandler(items, 'fontname'), onselect: function(e) { if (e.control.settings.value) { editor.execCommand('FontName', false, e.control.settings.value); } } }; }); editor.addButton('fontsizeselect', function() { var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt'; var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats; each(fontsize_formats.split(' '), function(item) { items.push({text: item, value: item}); }); return { type: 'listbox', text: 'Font Sizes', tooltip: 'Font Sizes', values: items, fixedWidth: true, onPostRender: createListBoxChangeHandler(items, 'fontsize'), onclick: function(e) { if (e.control.settings.value) { editor.execCommand('FontSize', false, e.control.settings.value); } } }; }); editor.addMenuItem('formats', { text: 'Formats', menu: formatMenu }); } }); // Included from: js/tinymce/classes/ui/GridLayout.js /** * GridLayout.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This layout manager places controls in a grid. * * @setting {Number} spacing Spacing between controls. * @setting {Number} spacingH Horizontal spacing between controls. * @setting {Number} spacingV Vertical spacing between controls. * @setting {Number} columns Number of columns to use. * @setting {String/Array} alignH start|end|center|stretch or array of values for each column. * @setting {String/Array} alignV start|end|center|stretch or array of values for each column. * @setting {String} pack start|end * * @class tinymce.ui.GridLayout * @extends tinymce.ui.AbsoluteLayout */ define("tinymce/ui/GridLayout", [ "tinymce/ui/AbsoluteLayout" ], function(AbsoluteLayout) { "use strict"; return AbsoluteLayout.extend({ /** * Recalculates the positions of the controls in the specified container. * * @method recalc * @param {tinymce.ui.Container} container Container instance to recalc. */ recalc: function(container) { var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect, ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY, colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, alignX, alignY, availableWidth, availableHeight; // Get layout settings settings = container.settings; items = container.items().filter(':visible'); contLayoutRect = container.layoutRect(); cols = settings.columns || Math.ceil(Math.sqrt(items.length)); rows = Math.ceil(items.length / cols); spacingH = settings.spacingH || settings.spacing || 0; spacingV = settings.spacingV || settings.spacing || 0; alignH = settings.alignH || settings.align; alignV = settings.alignV || settings.align; contPaddingBox = container._paddingBox; if (alignH && typeof(alignH) == "string") { alignH = [alignH]; } if (alignV && typeof(alignV) == "string") { alignV = [alignV]; } // Zero padd columnWidths for (x = 0; x < cols; x++) { colWidths.push(0); } // Zero padd rowHeights for (y = 0; y < rows; y++) { rowHeights.push(0); } // Calculate columnWidths and rowHeights for (y = 0; y < rows; y++) { for (x = 0; x < cols; x++) { ctrl = items[y * cols + x]; // Out of bounds if (!ctrl) { break; } ctrlLayoutRect = ctrl.layoutRect(); ctrlMinWidth = ctrlLayoutRect.minW; ctrlMinHeight = ctrlLayoutRect.minH; colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x]; rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y]; } } // Calculate maxX availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right; for (maxX = 0, x = 0; x < cols; x++) { maxX += colWidths[x] + (x > 0 ? spacingH : 0); availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x]; } // Calculate maxY availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom; for (maxY = 0, y = 0; y < rows; y++) { maxY += rowHeights[y] + (y > 0 ? spacingV : 0); availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y]; } maxX += contPaddingBox.left + contPaddingBox.right; maxY += contPaddingBox.top + contPaddingBox.bottom; // Calculate minW/minH rect = {}; rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW); rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH); rect.contentW = rect.minW - contLayoutRect.deltaW; rect.contentH = rect.minH - contLayoutRect.deltaH; rect.minW = Math.min(rect.minW, contLayoutRect.maxW); rect.minH = Math.min(rect.minH, contLayoutRect.maxH); rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth); rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight); // Resize container container if minSize was changed if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { rect.w = rect.minW; rect.h = rect.minH; container.layoutRect(rect); this.recalc(container); // Forced recalc for example if items are hidden/shown if (container._lastRect === null) { var parentCtrl = container.parent(); if (parentCtrl) { parentCtrl._lastRect = null; parentCtrl.recalc(); } } return; } // Update contentW/contentH so absEnd moves correctly if (contLayoutRect.autoResize) { rect = container.layoutRect(rect); rect.contentW = rect.minW - contLayoutRect.deltaW; rect.contentH = rect.minH - contLayoutRect.deltaH; } var flexV; if (settings.packV == 'start') { flexV = 0; } else { flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0; } // Calculate totalFlex var totalFlex = 0; var flexWidths = settings.flexWidths; if (flexWidths) { for (x = 0; x < flexWidths.length; x++) { totalFlex += flexWidths[x]; } } else { totalFlex = cols; } // Calculate new column widths based on flex values var ratio = availableWidth / totalFlex; for (x = 0; x < cols; x++) { colWidths[x] += flexWidths ? Math.ceil(flexWidths[x] * ratio) : ratio; } // Move/resize controls posY = contPaddingBox.top; for (y = 0; y < rows; y++) { posX = contPaddingBox.left; height = rowHeights[y] + flexV; for (x = 0; x < cols; x++) { ctrl = items[y * cols + x]; // No more controls to render then break if (!ctrl) { break; } // Get control settings and calculate x, y ctrlSettings = ctrl.settings; ctrlLayoutRect = ctrl.layoutRect(); width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth); alignX = alignY = 0; ctrlLayoutRect.x = posX; ctrlLayoutRect.y = posY; // Align control horizontal align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null); if (align == "center") { ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2); } else if (align == "right") { ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w; } else if (align == "stretch") { ctrlLayoutRect.w = width; } // Align control vertical align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null); if (align == "center") { ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2); } else if (align == "bottom") { ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h; } else if (align == "stretch") { ctrlLayoutRect.h = height; } ctrl.layoutRect(ctrlLayoutRect); posX += width + spacingH; if (ctrl.recalc) { ctrl.recalc(); } } posY += height + spacingV; } } }); }); // Included from: js/tinymce/classes/ui/Iframe.js /** * Iframe.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /*jshint scripturl:true */ /** * This class creates an iframe. * * @setting {String} url Url to open in the iframe. * * @-x-less Iframe.less * @class tinymce.ui.Iframe * @extends tinymce.ui.Widget */ define("tinymce/ui/Iframe", [ "tinymce/ui/Widget" ], function(Widget) { "use strict"; return Widget.extend({ /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this; self.addClass('iframe'); self.canFocus = false; return ( '' ); }, /** * Setter for the iframe source. * * @method src * @param {String} src Source URL for iframe. */ src: function(src) { this.getEl().src = src; }, /** * Inner HTML for the iframe. * * @method html * @param {String} html HTML string to set as HTML inside the iframe. * @param {function} callback Optional callback to execute when the iframe body is filled with contents. * @return {tinymce.ui.Iframe} Current iframe control. */ html: function(html, callback) { var self = this, body = this.getEl().contentWindow.document.body; // Wait for iframe to initialize IE 10 takes time if (!body) { setTimeout(function() { self.html(html); }, 0); } else { body.innerHTML = html; if (callback) { callback(); } } return this; } }); }); // Included from: js/tinymce/classes/ui/Label.js /** * Label.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class creates a label element. A label is a simple text control * that can be bound to other controls. * * @-x-less Label.less * @class tinymce.ui.Label * @extends tinymce.ui.Widget */ define("tinymce/ui/Label", [ "tinymce/ui/Widget", "tinymce/ui/DomUtils" ], function(Widget, DomUtils) { "use strict"; return Widget.extend({ /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @param {Boolean} multiline Multiline label. */ init: function(settings) { var self = this; self._super(settings); self.addClass('widget'); self.addClass('label'); self.canFocus = false; if (settings.multiline) { self.addClass('autoscroll'); } if (settings.strong) { self.addClass('strong'); } }, /** * 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, layoutRect = self._super(); if (self.settings.multiline) { var size = DomUtils.getSize(self.getEl()); // Check if the text fits within maxW if not then try word wrapping it if (size.width > layoutRect.maxW) { layoutRect.minW = layoutRect.maxW; self.addClass('multiline'); } self.getEl().style.width = layoutRect.minW + 'px'; layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height); } return layoutRect; }, /** * Repaints the control after a layout operation. * * @method repaint */ repaint: function() { var self = this; if (!self.settings.multiline) { self.getEl().style.lineHeight = self.layoutRect().h + 'px'; } return self._super(); }, /** * Sets/gets the current label text. * * @method text * @param {String} [text] New label text. * @return {String|tinymce.ui.Label} Current text or current label instance. */ text: function(text) { var self = this; if (self._rendered && text) { this.innerHtml(self.encode(text)); } return self._super(text); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, forId = self.settings.forId; return ( '' ); } }); }); // Included from: js/tinymce/classes/ui/Toolbar.js /** * Toolbar.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new toolbar. * * @class tinymce.ui.Toolbar * @extends tinymce.ui.Container */ define("tinymce/ui/Toolbar", [ "tinymce/ui/Container", "tinymce/ui/KeyboardNavigation" ], function(Container, KeyboardNavigation) { "use strict"; return Container.extend({ Defaults: { role: 'toolbar', layout: 'flow' }, /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { var self = this; self._super(settings); self.addClass('toolbar'); }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this; self.items().addClass('toolbar-item'); self.keyNav = new KeyboardNavigation({ root: self, enableLeftRight: true }); return self._super(); } }); }); // Included from: js/tinymce/classes/ui/MenuBar.js /** * MenuBar.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new menubar. * * @-x-less MenuBar.less * @class tinymce.ui.MenuBar * @extends tinymce.ui.Container */ define("tinymce/ui/MenuBar", [ "tinymce/ui/Toolbar" ], function(Toolbar) { "use strict"; return Toolbar.extend({ Defaults: { role: 'menubar', containerCls: 'menubar', defaults: { type: 'menubutton' } } }); }); // Included from: js/tinymce/classes/ui/MenuButton.js /** * MenuButton.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new menu button. * * @-x-less MenuButton.less * @class tinymce.ui.MenuButton * @extends tinymce.ui.Button */ define("tinymce/ui/MenuButton", [ "tinymce/ui/Button", "tinymce/ui/Factory", "tinymce/ui/MenuBar" ], function(Button, Factory, MenuBar) { "use strict"; // TODO: Maybe add as some global function function isChildOf(node, parent) { while (node) { if (parent === node) { return true; } node = node.parentNode; } return false; } var MenuButton = Button.extend({ /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { var self = this; self._renderOpen = true; self._super(settings); self.addClass('menubtn'); if (settings.fixedWidth) { self.addClass('fixed-width'); } self.aria('haspopup', true); self.hasPopup = true; }, /** * Shows the menu for the button. * * @method showMenu */ showMenu: function() { var self = this, settings = self.settings, menu; if (self.menu && self.menu.visible()) { return self.hideMenu(); } if (!self.menu) { menu = settings.menu || []; // Is menu array then auto constuct menu control if (menu.length) { menu = { type: 'menu', items: menu }; } else { menu.type = menu.type || 'menu'; } self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); self.fire('createmenu'); self.menu.reflow(); self.menu.on('cancel', function(e) { if (e.control === self.menu) { self.focus(); } }); self.menu.on('show hide', function(e) { if (e.control == self.menu) { self.activeMenu(e.type == 'show'); } }).fire('show'); self.aria('expanded', true); } self.menu.show(); self.menu.layoutRect({w: self.layoutRect().w}); self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); }, /** * Hides the menu for the button. * * @method hideMenu */ hideMenu: function() { var self = this; if (self.menu) { self.menu.items().each(function(item) { if (item.hideMenu) { item.hideMenu(); } }); self.menu.hide(); self.aria('expanded', false); } }, /** * Sets the active menu state. * * @private */ activeMenu: function(state) { this.toggleClass('active', state); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, prefix = self.classPrefix; var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button'); return ( ' ' + '' + '' ); }, /** * Gets invoked after the control has been rendered. * * @method postRender */ postRender: function() { var self = this; self.on('click', function(e) { if (e.control === self && isChildOf(e.target, self.getEl())) { self.showMenu(); if (e.keyboard) { self.menu.items()[0].focus(); } } }); self.on('mouseenter', function(e) { var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu; if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) { parent.items().filter('MenuButton').each(function(ctrl) { if (ctrl.hideMenu && ctrl != overCtrl) { if (ctrl.menu && ctrl.menu.visible()) { hasVisibleSiblingMenu = true; } ctrl.hideMenu(); } }); if (hasVisibleSiblingMenu) { overCtrl.focus(); // Fix for: #5887 overCtrl.showMenu(); } } }); return self._super(); }, /** * Sets/gets the current button text. * * @method text * @param {String} [text] New button text. * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance. */ text: function(text) { var self = this, i, children; if (self._rendered) { children = self.getEl('open').getElementsByTagName('span'); for (i = 0; i < children.length; i++) { children[i].innerHTML = self.encode(text); } } return this._super(text); }, /** * Removes the control and it's menus. * * @method remove */ remove: function() { this._super(); if (this.menu) { this.menu.remove(); } } }); return MenuButton; }); // Included from: js/tinymce/classes/ui/ListBox.js /** * ListBox.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new list box control. * * @-x-less ListBox.less * @class tinymce.ui.ListBox * @extends tinymce.ui.MenuButton */ define("tinymce/ui/ListBox", [ "tinymce/ui/MenuButton" ], function(MenuButton) { "use strict"; return MenuButton.extend({ /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {Array} values Array with values to add to list box. */ init: function(settings) { var self = this, values, i, selected, selectedText, lastItemCtrl; self._values = values = settings.values; if (values) { for (i = 0; i < values.length; i++) { selected = values[i].selected || settings.value === values[i].value; if (selected) { selectedText = selectedText || values[i].text; self._value = values[i].value; } } settings.menu = values; } settings.text = settings.text || selectedText || values[0].text; self._super(settings); self.addClass('listbox'); self.on('select', function(e) { var ctrl = e.control; if (lastItemCtrl) { e.lastControl = lastItemCtrl; } if (settings.multiple) { ctrl.active(!ctrl.active()); } else { self.value(e.control.settings.value); } lastItemCtrl = ctrl; }); }, /** * disable/enable 某一list的item * leanote ace life ace * value = convert, state = true | false */ diableValue: function(value, state) { var self = this; var menu = self.settings.menu; if (self.menu) { self.menu.items().each(function(ctrl) {// menuitem if(ctrl.value() === value) { ctrl.disabled(state); return; } }); } else { for (var i = 0; i < menu.length; i++) { if(menu[i].value == value) { menu[i].disabled = state; return; } } } }, /** * Getter/setter function for the control value. * * @method value * @param {String} [value] Value to be set. * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation. */ value: function(value) { var self = this, active, selectedText, menu, i; function activateByValue(menu, value) { menu.items().each(function(ctrl) {// menuitem active = ctrl.value() === value; if (active) { selectedText = selectedText || ctrl.text(); } ctrl.active(active); if (ctrl.menu) { activateByValue(ctrl.menu, value); } }); } if (typeof(value) != "undefined") { if (self.menu) { activateByValue(self.menu, value); } else { menu = self.settings.menu; for (i = 0; i < menu.length; i++) { active = menu[i].value == value; if (active) { selectedText = selectedText || menu[i].text; } menu[i].active = active; } } self.text(selectedText || this.settings.text); } return self._super(value); } }); }); // Included from: js/tinymce/classes/ui/MenuItem.js /** * MenuItem.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new menu item. * * @-x-less MenuItem.less * @class tinymce.ui.MenuItem * @extends tinymce.ui.Control */ define("tinymce/ui/MenuItem", [ "tinymce/ui/Widget", "tinymce/ui/Factory" ], function(Widget, Factory) { "use strict"; return Widget.extend({ Defaults: { border: 0, role: 'menuitem' }, /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {Boolean} selectable Selectable menu. * @setting {Array} menu Submenu array with items. * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X */ init: function(settings) { var self = this; self.hasPopup = true; self._super(settings); settings = self.settings; self.addClass('menu-item'); if (settings.menu) { self.addClass('menu-item-expand'); } if (settings.preview) { self.addClass('menu-item-preview'); } if (self._text === '-' || self._text === '|') { self.addClass('menu-item-sep'); self.aria('role', 'separator'); self.canFocus = false; self._text = '-'; } if (settings.selectable) { self.aria('role', 'menuitemcheckbox'); self.aria('checked', true); self.addClass('menu-item-checkbox'); settings.icon = 'selected'; } if (!settings.preview && !settings.selectable) { self.addClass('menu-item-normal'); } self.on('mousedown', function(e) { e.preventDefault(); }); self.on('mouseenter click', function(e) { if (e.control === self) { if (!settings.menu && e.type === 'click') { self.parent().hideAll(); self.fire('cancel'); self.fire('select'); } else { self.showMenu(); if (e.keyboard) { setTimeout(function() { self.menu.items()[0].focus(); }, 0); } } } }); if (settings.menu) { self.aria('haspopup', true); } }, /** * Returns true/false if the menuitem has sub menu. * * @method hasMenus * @return {Boolean} True/false state if it has submenu. */ hasMenus: function() { return !!this.settings.menu; }, /** * Shows the menu for the menu item. * * @method showMenu */ showMenu: function() { var self = this, settings = self.settings, menu, parent = self.parent(); parent.items().each(function(ctrl) { if (ctrl !== self) { ctrl.hideMenu(); } }); if (settings.menu) { menu = self.menu; if (!menu) { menu = settings.menu; // Is menu array then auto constuct menu control if (menu.length) { menu = { type: 'menu', items: menu }; } else { menu.type = menu.type || 'menu'; } if (parent.settings.itemDefaults) { menu.itemDefaults = parent.settings.itemDefaults; } menu = self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); menu.reflow(); menu.fire('show'); menu.on('cancel', function() { self.focus(); }); menu.on('hide', function(e) { if (e.control === menu) { self.removeClass('selected'); } }); } else { menu.show(); } menu._parentMenu = parent; menu.addClass('menu-sub'); var rel = menu.testMoveRel( self.getEl(), self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br'] ); menu.moveRel(self.getEl(), rel); menu.rel = rel; rel = 'menu-sub-' + rel; menu.removeClass(menu._lastRel); menu.addClass(rel); menu._lastRel = rel; self.addClass('selected'); self.aria('expanded', true); } }, /** * Hides the menu for the menu item. * * @method hideMenu */ hideMenu: function() { var self = this; if (self.menu) { self.menu.items().each(function(item) { if (item.hideMenu) { item.hideMenu(); } }); self.menu.hide(); self.aria('expanded', false); } return self; }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text); var icon = self.settings.icon, image = ''; if (icon) { self.parent().addClass('menu-has-icons'); } if (settings.image) { icon = 'none'; image = ' style="background-image: url(\'' + settings.image + '\')"'; } icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none'); return ( '' + (text !== '-' ? ' ' : '') + (text !== '-' ? '' + text + '' : '') + (settings.shortcut ? ' ' : '') + (settings.menu ? '' : '') + '' ); }, /** * Gets invoked after the control has been rendered. * * @method postRender */ postRender: function() { var self = this, settings = self.settings; var textStyle = settings.textStyle; if (typeof(textStyle) == "function") { textStyle = textStyle.call(this); } if (textStyle) { var textElm = self.getEl('text'); if (textElm) { textElm.setAttribute('style', textStyle); } } return self._super(); }, /** * Removes the control and it's menus. * * @method remove */ remove: function() { this._super(); if (this.menu) { this.menu.remove(); } } }); }); // Included from: js/tinymce/classes/ui/Menu.js /** * Menu.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new menu. * * @-x-less Menu.less * @class tinymce.ui.Menu * @extends tinymce.ui.FloatPanel */ define("tinymce/ui/Menu", [ "tinymce/ui/FloatPanel", "tinymce/ui/KeyboardNavigation", "tinymce/ui/MenuItem", "tinymce/util/Tools" ], function(FloatPanel, KeyboardNavigation, MenuItem, Tools) { "use strict"; var Menu = FloatPanel.extend({ Defaults: { defaultType: 'menuitem', border: 1, layout: 'stack', role: 'menu' }, /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init: function(settings) { var self = this; settings.autohide = true; settings.constrainToViewport = true; if (settings.itemDefaults) { var items = settings.items, i = items.length; while (i--) { items[i] = Tools.extend({}, settings.itemDefaults, items[i]); } } self._super(settings); self.addClass('menu'); self.keyNav = new KeyboardNavigation({ root: self, enableUpDown: true, enableLeftRight: true, leftAction: function() { if (self.parent() instanceof MenuItem) { self.keyNav.cancel(); } }, onCancel: function() { self.fire('cancel', {}, false); self.hide(); } }); }, /** * Repaints the control after a layout operation. * * @method repaint */ repaint: function() { this.toggleClass('menu-align', true); this._super(); this.getEl().style.height = ''; this.getEl('body').style.height = ''; return this; }, /** * Hides/closes the menu. * * @method cancel */ cancel: function() { var self = this; self.hideAll(); self.fire('cancel'); self.fire('select'); }, /** * Hide menu and all sub menus. * * @method hideAll */ hideAll: function() { var self = this; this.find('menuitem').exec('hideMenu'); return self._super(); }, /** * Invoked before the menu is rendered. * * @method preRender */ preRender: function() { var self = this; self.items().each(function(ctrl) { var settings = ctrl.settings; if (settings.icon || settings.selectable) { self._hasIcons = true; return false; } }); return self._super(); } }); return Menu; }); // Included from: js/tinymce/classes/ui/Radio.js /** * Radio.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new radio button. * * @-x-less Radio.less * @class tinymce.ui.Radio * @extends tinymce.ui.Checkbox */ define("tinymce/ui/Radio", [ "tinymce/ui/Checkbox" ], function(Checkbox) { "use strict"; return Checkbox.extend({ Defaults: { classes: "radio", role: "radio" } }); }); // Included from: js/tinymce/classes/ui/ResizeHandle.js /** * ResizeHandle.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events. * * @-x-less ResizeHandle.less * @class tinymce.ui.ResizeHandle * @extends tinymce.ui.Widget */ define("tinymce/ui/ResizeHandle", [ "tinymce/ui/Widget", "tinymce/ui/DragHelper" ], function(Widget, DragHelper) { "use strict"; return Widget.extend({ /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, prefix = self.classPrefix; self.addClass('resizehandle'); if (self.settings.direction == "both") { self.addClass('resizehandle-both'); } self.canFocus = false; return ( '' + '' + '' ); }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this; self._super(); self.resizeDragHelper = new DragHelper(this._id, { start: function() { self.fire('ResizeStart'); }, drag: function(e) { if (self.settings.direction != "both") { e.deltaX = 0; } self.fire('Resize', e); }, end: function() { self.fire('ResizeEnd'); } }); }, remove: function() { if (this.resizeDragHelper) { this.resizeDragHelper.destroy(); } return this._super(); } }); }); // Included from: js/tinymce/classes/ui/Spacer.js /** * Spacer.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a spacer. This control is used in flex layouts for example. * * @-x-less Spacer.less * @class tinymce.ui.Spacer * @extends tinymce.ui.Widget */ define("tinymce/ui/Spacer", [ "tinymce/ui/Widget" ], function(Widget) { "use strict"; return Widget.extend({ /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this; self.addClass('spacer'); self.canFocus = false; return ''; } }); }); // Included from: js/tinymce/classes/ui/SplitButton.js /** * SplitButton.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a split button. * * @-x-less SplitButton.less * @class tinymce.ui.SplitButton * @extends tinymce.ui.Button */ define("tinymce/ui/SplitButton", [ "tinymce/ui/MenuButton", "tinymce/ui/DomUtils" ], function(MenuButton, DomUtils) { return MenuButton.extend({ Defaults: { classes: "widget btn splitbtn", role: "splitbutton" }, /** * Repaints the control after a layout operation. * * @method repaint */ repaint: function() { var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm; self._super(); mainButtonElm = elm.firstChild; menuButtonElm = elm.lastChild; DomUtils.css(mainButtonElm, { width: rect.w - DomUtils.getSize(menuButtonElm).width, height: rect.h - 2 }); DomUtils.css(menuButtonElm, { height: rect.h - 2 }); return self; }, /** * Sets the active menu state. * * @private */ activeMenu: function(state) { var self = this; DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, prefix = self.classPrefix; var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; return ( '' + '' + '' + '' ); }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this, onClickHandler = self.settings.onclick; self.on('click', function(e) { var node = e.target; if (e.control == this) { // Find clicks that is on the main button while (node) { if (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1) { e.stopImmediatePropagation(); onClickHandler.call(this, e); return; } node = node.parentNode; } } }); delete self.settings.onclick; return self._super(); } }); }); // Included from: js/tinymce/classes/ui/StackLayout.js /** * StackLayout.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This layout uses the browsers layout when the items are blocks. * * @-x-less StackLayout.less * @class tinymce.ui.StackLayout * @extends tinymce.ui.FlowLayout */ define("tinymce/ui/StackLayout", [ "tinymce/ui/FlowLayout" ], function(FlowLayout) { "use strict"; return FlowLayout.extend({ Defaults: { containerClass: 'stack-layout', controlClass: 'stack-layout-item', endClass : 'break' } }); }); // Included from: js/tinymce/classes/ui/TabPanel.js /** * TabPanel.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a tab panel control. * * @-x-less TabPanel.less * @class tinymce.ui.TabPanel * @extends tinymce.ui.Panel * * @setting {Number} activeTab Active tab index. */ define("tinymce/ui/TabPanel", [ "tinymce/ui/Panel", "tinymce/ui/DomUtils" ], function(Panel, DomUtils) { "use strict"; return Panel.extend({ lastIdx: 0, Defaults: { layout: 'absolute', defaults: { type: 'panel' } }, /** * Activates the specified tab by index. * * @method activateTab * @param {Number} idx Index of the tab to activate. */ activateTab: function(idx) { if (this.activeTabId) { DomUtils.removeClass(this.getEl(this.activeTabId), this.classPrefix + 'active'); } this.activeTabId = 't' + idx; DomUtils.addClass(this.getEl('t' + idx), this.classPrefix + 'active'); if (idx != this.lastIdx) { this.items()[this.lastIdx].hide(); this.lastIdx = idx; } this.items()[idx].show().fire('showtab'); this.reflow(); }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix; self.preRender(); layout.preRender(self); self.items().each(function(ctrl, i) { tabsHtml += ( '' + self.encode(ctrl.settings.title) + '' ); }); return ( '' + '' ); }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this; self._super(); self.settings.activeTab = self.settings.activeTab || 0; self.activateTab(self.settings.activeTab); this.on('click', function(e) { var targetParent = e.target.parentNode; if (e.target.parentNode.id == self._id + '-head') { var i = targetParent.childNodes.length; while (i--) { if (targetParent.childNodes[i] == e.target) { self.activateTab(i); } } } }); }, /** * 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, rect, minW, minH; minW = DomUtils.getSize(self.getEl('head')).width; minW = minW < 0 ? 0 : minW; minH = 0; self.items().each(function(item, i) { minW = Math.max(minW, item.layoutRect().minW); minH = Math.max(minH, item.layoutRect().minH); if (self.settings.activeTab != i) { item.hide(); } }); self.items().each(function(ctrl) { ctrl.settings.x = 0; ctrl.settings.y = 0; ctrl.settings.w = minW; ctrl.settings.h = minH; ctrl.layoutRect({ x: 0, y: 0, w: minW, h: minH }); }); var headH = DomUtils.getSize(self.getEl('head')).height; self.settings.minWidth = minW; self.settings.minHeight = minH + headH; rect = self._super(); rect.deltaH += headH; rect.innerH = rect.h - rect.deltaH; return rect; } }); }); // Included from: js/tinymce/classes/ui/TextBox.js /** * TextBox.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * Creates a new textbox. * * @-x-less TextBox.less * @class tinymce.ui.TextBox * @extends tinymce.ui.Widget */ define("tinymce/ui/TextBox", [ "tinymce/ui/Widget", "tinymce/ui/DomUtils" ], function(Widget, DomUtils) { "use strict"; return Widget.extend({ /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. * @setting {Boolean} multiline True if the textbox is a multiline control. * @setting {Number} maxLength Max length for the textbox. * @setting {Number} size Size of the textbox in characters. */ init: function(settings) { var self = this; self._super(settings); self._value = settings.value || ''; self.addClass('textbox'); if (settings.multiline) { self.addClass('multiline'); } else { // TODO: Rework this self.on('keydown', function(e) { if (e.keyCode == 13) { self.parents().reverse().each(function(ctrl) { e.preventDefault(); if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { ctrl.fire('submit', {data: ctrl.toJSON()}); return false; } }); } }); } }, /** * Getter/setter function for the disabled state. * * @method value * @param {Boolean} [state] State to be set. * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation. */ disabled: function(state) { var self = this; if (self._rendered && typeof(state) != 'undefined') { self.getEl().disabled = state; } return self._super(state); }, /** * Getter/setter function for the control value. * * @method value * @param {String} [value] Value to be set. * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation. */ value: function(value) { var self = this; if (typeof(value) != "undefined") { self._value = value; if (self._rendered) { self.getEl().value = value; } return self; } if (self._rendered) { return self.getEl().value; } return self._value; }, /** * Repaints the control after a layout operation. * * @method repaint */ repaint: function() { var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect; style = self.getEl().style; rect = self._layoutRect; lastRepaintRect = self._lastRepaintRect || {}; // Detect old IE 7+8 add lineHeight to align caret vertically in the middle var doc = document; if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) { style.lineHeight = (rect.h - borderH) + 'px'; } borderBox = self._borderBox; borderW = borderBox.left + borderBox.right + 8; borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0); if (rect.x !== lastRepaintRect.x) { style.left = rect.x + 'px'; lastRepaintRect.x = rect.x; } if (rect.y !== lastRepaintRect.y) { style.top = rect.y + 'px'; lastRepaintRect.y = rect.y; } if (rect.w !== lastRepaintRect.w) { style.width = (rect.w - borderW) + 'px'; lastRepaintRect.w = rect.w; } if (rect.h !== lastRepaintRect.h) { style.height = (rect.h - borderH) + 'px'; lastRepaintRect.h = rect.h; } self._lastRepaintRect = lastRepaintRect; self.fire('repaint', {}, false); return self; }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml: function() { var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = ''; if ("spellcheck" in settings) { extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; } if (settings.maxLength) { extraAttrs += ' maxlength="' + settings.maxLength + '"'; } if (settings.size) { extraAttrs += ' size="' + settings.size + '"'; } if (settings.subtype) { extraAttrs += ' type="' + settings.subtype + '"'; } if (self.disabled()) { extraAttrs += ' disabled="disabled"'; } if (settings.multiline) { return ( '' ); } return ''; }, /** * Called after the control has been rendered. * * @method postRender */ postRender: function() { var self = this; DomUtils.on(self.getEl(), 'change', function(e) { self.fire('change', e); }); return self._super(); }, remove: function() { DomUtils.off(this.getEl()); this._super(); } }); }); // Included from: js/tinymce/classes/ui/Throbber.js /** * Throbber.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class enables you to display a Throbber for any element. * * @-x-less Throbber.less * @class tinymce.ui.Throbber */ define("tinymce/ui/Throbber", [ "tinymce/ui/DomUtils" ], function(DomUtils) { "use strict"; /** * Constructs a new throbber. * * @constructor * @param {Element} elm DOM Html element to display throbber in. */ return function(elm) { var self = this, state; /** * Shows the throbber. * * @method show * @param {Number} [time] Time to wait before showing. * @return {tinymce.ui.Throbber} Current throbber instance. */ self.show = function(time) { self.hide(); state = true; window.setTimeout(function() { if (state) { elm.appendChild(DomUtils.createFragment('')); } }, time || 0); return self; }; /** * Hides the throbber. * * @method hide * @return {tinymce.ui.Throbber} Current throbber instance. */ self.hide = function() { var child = elm.lastChild; if (child && child.className.indexOf('throbber') != -1) { child.parentNode.removeChild(child); } state = false; return self; }; }; }); expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/Selection","tinymce/dom/RangeUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/KeyboardNavigation","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]); })(this);' + tabsHtml + '' + '' + layout.renderHtml(self) + '' + '