UNPKG

react-tinymce-mention

Version:

@Mention functionality for TinyMCE, built with React and Redux.

2,073 lines (1,749 loc) 1.01 MB
// 4.2.4 (2015-08-17) /** * 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 * * Released under LGPL License. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /*jshint loopfunc:true*/ /*eslint no-loop-func:0 */ /** * This class wraps the browsers native event logic with more convenient methods. * * @class tinymce.dom.EventUtils */ 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, webkitMovementX: 1, webkitMovementY: 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; } // Add missing metaKey for IE 8 if (typeof event.metaKey == 'undefined') { event.metaKey = false; } return event; } /** * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized. * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times. */ function bindOnReady(win, callback, eventUtils) { var doc = win.document, event = {type: 'ready'}; if (eventUtils.domLoaded) { callback(event); return; } // Gets called when the DOM is ready function readyHandler() { if (!eventUtils.domLoaded) { eventUtils.domLoaded = true; callback(event); } } function waitForDomLoaded() { // Check complete or interactive state if there is a body // element on some iframes IE 8 will produce a null body if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) { removeEvent(doc, "readystatechange", waitForDomLoaded); readyHandler(); } } function tryScroll() { try { // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. // http://javascript.nwbox.com/IEContentLoaded/ doc.documentElement.doScroll("left"); } catch (ex) { setTimeout(tryScroll, 0); return; } readyHandler(); } // Use W3C method if (doc.addEventListener) { if (doc.readyState === "complete") { readyHandler(); } else { addEvent(win, 'DOMContentLoaded', readyHandler); } } else { // Use IE method addEvent(doc, "readystatechange", waitForDomLoaded); // Wait until we can scroll, when we can the DOM is initialized if (doc.documentElement.doScroll && win.self === win.top) { tryScroll(); } } // Fallback if any of the above methods should fail for some odd reason addEvent(win, 'load', readyHandler); } /** * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers. */ function EventUtils() { var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; expando = eventExpandoPrefix + (+new Date()).toString(32); hasMouseEnterLeave = "onmouseenter" in document.documentElement; hasFocusIn = "onfocusin" in document.documentElement; mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; count = 1; // State if the DOMContentLoaded was executed or not self.domLoaded = false; self.events = events; /** * Executes all event handler callbacks for a specific event. * * @private * @param {Event} evt Event object. * @param {String} id Expando id value to look for. */ function executeHandlers(evt, id) { var callbackList, i, l, callback, container = events[id]; callbackList = container && container[evt.type]; if (callbackList) { for (i = 0, l = callbackList.length; i < l; i++) { callback = callbackList[i]; // Check if callback exists might be removed if a unbind is called inside the callback if (callback && callback.func.call(callback.scope, evt) === false) { evt.preventDefault(); } // Should we stop propagation to immediate listeners if (evt.isImmediatePropagationStopped()) { return; } } } } /** * Binds a callback to an event on the specified target. * * @method bind * @param {Object} target Target node/window or custom object. * @param {String} names Name of the event to bind. * @param {function} callback Callback function to execute when the event occurs. * @param {Object} scope Scope to call the callback function on, defaults to target. * @return {function} Callback function that got bound. */ self.bind = function(target, names, callback, scope) { var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; // Native event handler function patches the event and executes the callbacks for the expando function defaultNativeHandler(evt) { executeHandlers(fix(evt || win.event), id); } // Don't bind to text nodes or comments if (!target || target.nodeType === 3 || target.nodeType === 8) { return; } // Create or get events id for the target if (!target[expando]) { id = count++; target[expando] = id; events[id] = {}; } else { id = target[expando]; } // Setup the specified scope or use the target as a default scope = scope || target; // Split names and bind each event, enables you to bind multiple events with one call names = names.split(' '); i = names.length; while (i--) { name = names[i]; nativeHandler = defaultNativeHandler; fakeName = capture = false; // Use ready instead of DOMContentLoaded if (name === "DOMContentLoaded") { name = "ready"; } // DOM is already ready if (self.domLoaded && name === "ready" && target.readyState == 'complete') { callback.call(scope, fix({type: name})); continue; } // Handle mouseenter/mouseleaver if (!hasMouseEnterLeave) { fakeName = mouseEnterLeave[name]; if (fakeName) { nativeHandler = function(evt) { var current, related; current = evt.currentTarget; related = evt.relatedTarget; // Check if related is inside the current target if it's not then the event should // be ignored since it's a mouseover/mouseout inside the element if (related && current.contains) { // Use contains for performance related = current.contains(related); } else { while (related && related !== current) { related = related.parentNode; } } // Fire fake event if (!related) { evt = fix(evt || win.event); evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; evt.target = current; executeHandlers(evt, id); } }; } } // Fake bubbeling of focusin/focusout if (!hasFocusIn && (name === "focusin" || name === "focusout")) { capture = true; fakeName = name === "focusin" ? "focus" : "blur"; nativeHandler = function(evt) { evt = fix(evt || win.event); evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; executeHandlers(evt, id); }; } // Setup callback list and bind native event callbackList = events[id][name]; if (!callbackList) { events[id][name] = callbackList = [{func: callback, scope: scope}]; callbackList.fakeName = fakeName; callbackList.capture = capture; //callbackList.callback = callback; // Add the nativeHandler to the callback list so that we can later unbind it callbackList.nativeHandler = nativeHandler; // Check if the target has native events support if (name === "ready") { bindOnReady(target, nativeHandler, self); } else { addEvent(target, fakeName || name, nativeHandler, capture); } } else { if (name === "ready" && self.domLoaded) { callback({type: name}); } else { // If it already has an native handler then just push the callback callbackList.push({func: callback, scope: scope}); } } } target = callbackList = 0; // Clean memory for IE return callback; }; /** * Unbinds the specified event by name, name and callback or all events on the target. * * @method unbind * @param {Object} target Target node/window or custom object. * @param {String} names Optional event name to unbind. * @param {function} callback Optional callback function to unbind. * @return {EventUtils} Event utils instance. */ self.unbind = function(target, names, callback) { var id, callbackList, i, ci, name, eventMap; // Don't bind to text nodes or comments if (!target || target.nodeType === 3 || target.nodeType === 8) { return self; } // Unbind event or events if the target has the expando id = target[expando]; if (id) { eventMap = events[id]; // Specific callback if (names) { names = names.split(' '); i = names.length; while (i--) { name = names[i]; callbackList = eventMap[name]; // Unbind the event if it exists in the map if (callbackList) { // Remove specified callback if (callback) { ci = callbackList.length; while (ci--) { if (callbackList[ci].func === callback) { var nativeHandler = callbackList.nativeHandler; var fakeName = callbackList.fakeName, capture = callbackList.capture; // Clone callbackList since unbind inside a callback would otherwise break the handlers loop callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1)); callbackList.nativeHandler = nativeHandler; callbackList.fakeName = fakeName; callbackList.capture = capture; eventMap[name] = callbackList; } } } // Remove all callbacks if there isn't a specified callback or there is no callbacks left if (!callback || callbackList.length === 0) { delete eventMap[name]; removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); } } } } else { // All events for a specific element for (name in eventMap) { callbackList = eventMap[name]; removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); } eventMap = {}; } // Check if object is empty, if it isn't then we won't remove the expando map for (name in eventMap) { return self; } // Delete event object delete events[id]; // Remove expando from target try { // IE will fail here since it can't delete properties from window delete target[expando]; } catch (ex) { // IE will set it to null target[expando] = null; } } return self; }; /** * Fires the specified event on the specified target. * * @method fire * @param {Object} target Target node/window or custom object. * @param {String} name Event name to fire. * @param {Object} args Optional arguments to send to the observers. * @return {EventUtils} Event utils instance. */ self.fire = function(target, name, args) { var id; // Don't bind to text nodes or comments if (!target || target.nodeType === 3 || target.nodeType === 8) { return self; } // Build event object by patching the args args = fix(null, args); args.type = name; args.target = target; do { // Found an expando that means there is listeners to execute id = target[expando]; if (id) { executeHandlers(args, id); } // Walk up the DOM target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; } while (target && !args.isPropagationStopped()); return self; }; /** * Removes all bound event listeners for the specified target. This will also remove any bound * listeners to child nodes within that target. * * @method clean * @param {Object} target Target node/window object. * @return {EventUtils} Event utils instance. */ self.clean = function(target) { var i, children, unbind = self.unbind; // Don't bind to text nodes or comments if (!target || target.nodeType === 3 || target.nodeType === 8) { return self; } // Unbind any element on the specificed target if (target[expando]) { unbind(target); } // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children if (!target.getElementsByTagName) { target = target.document; } // Remove events from each child element if (target && target.getElementsByTagName) { unbind(target); children = target.getElementsByTagName('*'); i = children.length; while (i--) { target = children[i]; if (target[expando]) { unbind(target); } } } return self; }; /** * Destroys the event object. Call this on IE to remove memory leaks. */ self.destroy = function() { events = {}; }; // Legacy function for canceling events self.cancel = function(e) { if (e) { e.preventDefault(); e.stopImmediatePropagation(); } return false; }; } EventUtils.Event = new EventUtils(); EventUtils.Event.bind(window, 'ready', function() {}); return EventUtils; }); // Included from: js/tinymce/classes/dom/Sizzle.jQuery.js /** * Sizzle.jQuery.js * * Released under LGPL License. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /*global jQuery:true */ /* * Fake Sizzle using jQuery. */ define("tinymce/dom/Sizzle", [], function() { // Detect if jQuery is loaded if (!window.jQuery) { throw new Error("Load jQuery first"); } return jQuery.find; }); // Included from: js/tinymce/classes/Env.js /** * Env.js * * Released under LGPL License. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class contains various environment constants like browser versions etc. * Normally you don't want to sniff specific browser versions but sometimes you have * to when it's impossible to feature detect. So use this with care. * * @class tinymce.Env * @static */ define("tinymce/Env", [], function() { var nav = navigator, userAgent = nav.userAgent; var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi; opera = window.opera && window.opera.buildNumber; android = /Android/.test(userAgent); webkit = /WebKit/.test(userAgent); ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName); ie = ie && /MSIE (\w+)\./.exec(userAgent)[1]; ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false; ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false; ie = ie || ie11 || ie12; gecko = !webkit && !ie11 && /Gecko/.test(userAgent); mac = userAgent.indexOf('Mac') != -1; iDevice = /(iPad|iPhone)/.test(userAgent); fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL; if (ie12) { webkit = false; } // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions // says it has contentEditable support but there is no visible caret. var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534; return { /** * Constant that is true if the browser is Opera. * * @property opera * @type Boolean * @final */ opera: opera, /** * Constant that is true if the browser is WebKit (Safari/Chrome). * * @property webKit * @type Boolean * @final */ webkit: webkit, /** * Constant that is more than zero if the browser is IE. * * @property ie * @type Boolean * @final */ ie: ie, /** * Constant that is true if the browser is Gecko. * * @property gecko * @type Boolean * @final */ gecko: gecko, /** * Constant that is true if the os is Mac OS. * * @property mac * @type Boolean * @final */ mac: mac, /** * Constant that is true if the os is iOS. * * @property iOS * @type Boolean * @final */ iOS: iDevice, /** * Constant that is true if the os is android. * * @property android * @type Boolean * @final */ android: android, /** * Constant that is true if the browser supports editing. * * @property contentEditable * @type Boolean * @final */ contentEditable: contentEditable, /** * Transparent image data url. * * @property transparentSrc * @type Boolean * @final */ transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", /** * Returns true/false if the browser can or can't place the caret after a inline block like an image. * * @property noCaretAfter * @type Boolean * @final */ caretAfter: ie != 8, /** * Constant that is true if the browser supports native DOM Ranges. IE 9+. * * @property range * @type Boolean */ range: window.getSelection && "Range" in window, /** * Returns the IE document mode for non IE browsers this will fake IE 10. * * @property documentMode * @type Number */ documentMode: ie && !ie12 ? (document.documentMode || 7) : 10, /** * Constant that is true if the browser has a modern file api. * * @property fileApi * @type Boolean */ fileApi: fileApi }; }); // Included from: js/tinymce/classes/util/Tools.js /** * Tools.js * * Released under LGPL License. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class contains various utlity functions. These are also exposed * directly on the tinymce namespace. * * @class tinymce.util.Tools */ define("tinymce/util/Tools", [ "tinymce/Env" ], function(Env) { /** * Removes whitespace from the beginning and end of a string. * * @method trim * @param {String} s String to remove whitespace from. * @return {String} New string with removed whitespace. */ var whiteSpaceRegExp = /^\s*|\s*$/g; function trim(str) { return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); } /** * Returns true/false if the object is an array or not. * * @method isArray * @param {Object} obj Object to check. * @return {boolean} true/false state if the object is an array or not. */ var isArray = Array.isArray || function(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; /** * Checks if a object is of a specific type for example an array. * * @method is * @param {Object} obj Object to check type of. * @param {string} type Optional type to check for. * @return {Boolean} true/false if the object is of the specified type. */ function is(obj, type) { if (!type) { return obj !== undefined; } if (type == 'array' && isArray(obj)) { return true; } return typeof obj == type; } /** * 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 = obj, i, l; if (!isArray(obj)) { array = []; for (i = 0, l = obj.length; i < l; i++) { array[i] = obj[i]; } } return array; } /** * Makes a name/object map out of an array with names. * * @method makeMap * @param {Array/String} items Items to make map out of. * @param {String} delim Optional delimiter to split string by. * @param {Object} map Optional map to add items to. * @return {Object} Name/value map of items. */ function makeMap(items, delim, map) { var i; items = items || []; delim = delim || ','; if (typeof items == "string") { items = items.split(delim); } map = map || {}; i = items.length; while (i--) { map[items[i]] = {}; } return map; } /** * Performs an iteration of all items in a collection such as an object or array. This method will execure the * callback function for each item in the collection, if the callback returns false the iteration will terminate. * The callback has the following format: cb(value, key_or_index). * * @method each * @param {Object} o Collection to iterate. * @param {function} cb Callback function to execute for each item. * @param {Object} s Optional scope to execute the callback in. * @example * // Iterate an array * tinymce.each([1,2,3], function(v, i) { * console.debug("Value: " + v + ", Index: " + i); * }); * * // Iterate an object * tinymce.each({a: 1, b: 2, c: 3], function(v, k) { * console.debug("Value: " + v + ", Key: " + k); * }); */ function each(o, cb, s) { var n, l; if (!o) { return 0; } s = s || o; if (o.length !== undefined) { // Indexed arrays, needed for Safari for (n = 0, l = o.length; n < l; n++) { if (cb.call(s, o[n], n, o) === false) { return 0; } } } else { // Hashtables for (n in o) { if (o.hasOwnProperty(n)) { if (cb.call(s, o[n], n, o) === false) { return 0; } } } } return 1; } /** * Creates a new array by the return value of each iteration function call. This enables you to convert * one array list into another. * * @method map * @param {Array} array Array of items to iterate. * @param {function} callback Function to call for each item. It's return value will be the new value. * @return {Array} Array with new values based on function return values. */ function map(array, callback) { var out = []; each(array, function(item) { out.push(callback(item)); }); return out; } /** * Filters out items from the input array by calling the specified function for each item. * If the function returns false the item will be excluded if it returns true it will be included. * * @method grep * @param {Array} a Array of items to loop though. * @param {function} f Function to call for each item. Include/exclude depends on it's return value. * @return {Array} New array with values imported and filtered based in input. * @example * // Filter out some items, this will return an array with 4 and 5 * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;}); */ function grep(a, f) { var o = []; each(a, function(v) { if (!f || f(v)) { o.push(v); } }); return o; } /** * Creates a class, subclass or static singleton. * More details on this method can be found in the Wiki. * * @method create * @param {String} s Class name, inheritage and prefix. * @param {Object} p Collection of methods to add to the class. * @param {Object} root Optional root object defaults to the global window object. * @example * // Creates a basic class * tinymce.create('tinymce.somepackage.SomeClass', { * SomeClass: function() { * // Class constructor * }, * * method: function() { * // Some method * } * }); * * // Creates a basic subclass class * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', { * SomeSubClass: function() { * // Class constructor * this.parent(); // Call parent constructor * }, * * method: function() { * // Some method * this.parent(); // Call parent method * }, * * 'static': { * staticMethod: function() { * // Static method * } * } * }); * * // Creates a singleton/static class * tinymce.create('static tinymce.somepackage.SomeSingletonClass', { * method: function() { * // Some method * } * }); */ function create(s, p, root) { var self = this, sp, ns, cn, scn, c, de = 0; // Parse : <prefix> <class>:<super class> s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name // Create namespace for new class ns = self.createNS(s[3].replace(/\.\w+$/, ''), root); // Class already exists if (ns[cn]) { return; } // Make pure static class if (s[2] == 'static') { ns[cn] = p; if (this.onCreate) { this.onCreate(s[2], s[3], ns[cn]); } return; } // Create default constructor if (!p[cn]) { p[cn] = function() {}; de = 1; } // Add constructor and methods ns[cn] = p[cn]; self.extend(ns[cn].prototype, p); // Extend if (s[5]) { sp = self.resolve(s[5]).prototype; scn = s[5].match(/\.(\w+)$/i)[1]; // Class name // Extend constructor c = ns[cn]; if (de) { // Add passthrough constructor ns[cn] = function() { return sp[scn].apply(this, arguments); }; } else { // Add inherit constructor ns[cn] = function() { this.parent = sp[scn]; return c.apply(this, arguments); }; } ns[cn].prototype[cn] = ns[cn]; // Add super methods self.each(sp, function(f, n) { ns[cn].prototype[n] = sp[n]; }); // Add overridden methods self.each(p, function(f, n) { // Extend methods if needed if (sp[n]) { ns[cn].prototype[n] = function() { this.parent = sp[n]; return f.apply(this, arguments); }; } else { if (n != cn) { ns[cn].prototype[n] = f; } } }); } // Add static methods /*jshint sub:true*/ /*eslint dot-notation:0*/ self.each(p['static'], function(f, n) { ns[cn][n] = f; }); } /** * Returns the index of a value in an array, this method will return -1 if the item wasn't found. * * @method inArray * @param {Array} a Array/Object to search for value in. * @param {Object} v Value to check for inside the array. * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found. * @example * // Get index of value in array this will alert 1 since 2 is at that index * alert(tinymce.inArray([1,2,3], 2)); */ function inArray(a, v) { var i, l; if (a) { for (i = 0, l = a.length; i < l; i++) { if (a[i] === v) { return i; } } } return -1; } function extend(obj, ext) { var i, l, name, args = arguments, value; for (i = 1, l = args.length; i < l; i++) { ext = args[i]; for (name in ext) { if (ext.hasOwnProperty(name)) { value = ext[name]; if (value !== undefined) { obj[name] = value; } } } } return obj; } /** * Executed the specified function for each item in a object tree. * * @method walk * @param {Object} o Object tree to walk though. * @param {function} f Function to call for each item. * @param {String} n Optional name of collection inside the objects to walk for example childNodes. * @param {String} s Optional scope to execute the function in. */ function walk(o, f, n, s) { s = s || this; if (o) { if (n) { o = o[n]; } each(o, function(o, i) { if (f.call(s, o, i, n) === false) { return false; } walk(o, f, n, s); }); } } /** * Creates a namespace on a specific object. * * @method createNS * @param {String} n Namespace to create for example a.b.c.d. * @param {Object} o Optional object to add namespace to, defaults to window. * @return {Object} New namespace object the last item in path. * @example * // Create some namespace * tinymce.createNS('tinymce.somepackage.subpackage'); * * // Add a singleton * var tinymce.somepackage.subpackage.SomeSingleton = { * method: function() { * // Some method * } * }; */ function createNS(n, o) { var i, v; o = o || window; n = n.split('.'); for (i = 0; i < n.length; i++) { v = n[i]; if (!o[v]) { o[v] = {}; } o = o[v]; } return o; } /** * Resolves a string and returns the object from a specific structure. * * @method resolve * @param {String} n Path to resolve for example a.b.c.d. * @param {Object} o Optional object to search though, defaults to window. * @return {Object} Last object in path or null if it couldn't be resolved. * @example * // Resolve a path into an object reference * var obj = tinymce.resolve('a.b.c.d'); */ function resolve(n, o) { var i, l; o = o || window; n = n.split('.'); for (i = 0, l = n.length; i < l; i++) { o = o[n[i]]; if (!o) { break; } } return o; } /** * Splits a string but removes the whitespace before and after each value. * * @method explode * @param {string} s String to split. * @param {string} d Delimiter to split by. * @example * // Split a string into an array with a,b,c * var arr = tinymce.explode('a, b, c'); */ function explode(s, d) { if (!s || is(s, 'array')) { return s; } return map(s.split(d || ','), trim); } function constant(value) { return function() { return value; }; } function reduce(collection, iteratee, accumulator, thisArg) { var i = 0; if (arguments.length < 3) { accumulator = collection[0]; i = 1; } for (; i < collection.length; i++) { accumulator = iteratee.call(thisArg, accumulator, collection[i], i); } return accumulator; } function _addCacheSuffix(url) { var cacheSuffix = Env.cacheSuffix; if (cacheSuffix) { url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix; } return url; } return { trim: trim, isArray: isArray, is: is, toArray: toArray, makeMap: makeMap, each: each, map: map, grep: grep, filter: grep, inArray: inArray, extend: extend, create: create, walk: walk, createNS: createNS, resolve: resolve, explode: explode, constant: constant, reduce: reduce, _addCacheSuffix: _addCacheSuffix }; }); // Included from: js/tinymce/classes/dom/DomQuery.js /** * DomQuery.js * * Released under LGPL License. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** * This class mimics most of the jQuery API: * * This is whats currently implemented: * - Utility functions * - DOM traversial * - DOM manipulation * - Event binding * * This is not currently implemented: * - Dimension * - Ajax * - Animation * - Advanced chaining * * @example * var $ = tinymce.dom.DomQuery; * $('p').attr('attr', 'value').addClass('class'); * * @class tinymce.dom.DomQuery */ define("tinymce/dom/DomQuery", [ "tinymce/dom/EventUtils", "tinymce/dom/Sizzle", "tinymce/util/Tools", "tinymce/Env" ], function(EventUtils, Sizzle, Tools, Env) { var doc = document, push = Array.prototype.push, slice = Array.prototype.slice; var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/; var Event = EventUtils.Event, undef; function isDefined(obj) { return typeof obj !== 'undefined'; } function isString(obj) { return typeof obj === 'string'; } function isWindow(obj) { return obj && obj == obj.window; } function createFragment(html, fragDoc) { var frag, node, container; fragDoc = fragDoc || doc; container = fragDoc.createElement('div'); frag = fragDoc.createDocumentFragment(); container.innerHTML = html; while ((node = container.firstChild)) { frag.appendChild(node); } return frag; } function domManipulate(targetNodes, sourceItem, callback, reverse) { var i; if (isString(sourceItem)) { sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0])); } else if (sourceItem.length && !sourceItem.nodeType) { sourceItem = DomQuery.makeArray(sourceItem); if (reverse) { for (i = sourceItem.length - 1; i >= 0; i--) { domManipulate(targetNodes, sourceItem[i], callback, reverse); } } else { for (i = 0; i < sourceItem.length; i++) { domManipulate(targetNodes, sourceItem[i], callback, reverse); } } return targetNodes; } if (sourceItem.nodeType) { i = targetNodes.length; while (i--) { callback.call(targetNodes[i], sourceItem); } } return targetNodes; } function hasClass(node, className) { return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1; } function wrap(elements, wrapper, all) { var lastParent, newWrapper; wrapper = DomQuery(wrapper)[0]; elements.each(function() { var self = this; if (!all || lastParent != self.parentNode) { lastParent = self.parentNode; newWrapper = wrapper.cloneNode(false); self.parentNode.insertBefore(newWrapper, self); newWrapper.appendChild(self); } else { newWrapper.appendChild(self); } }); return elements; } var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' '); var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref noshade nowrap readonly selected', ' '); var propFix = { 'for': 'htmlFor', 'class': 'className', 'readonly': 'readOnly' }; var cssFix = { 'float': 'cssFloat' }; var attrHooks = {}, cssHooks = {}; function DomQuery(selector, context) { /*eslint new-cap:0 */ return new DomQuery.fn.init(selector, context); } 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; } var whiteSpaceRegExp = /^\s*|\s*$/g; function trim(str) { return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); } 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, key, value) === false) { break; } } } } else { // Loop array items for (i = 0; i < length; i++) { value = obj[i]; if (callback.call(value, i, value) === false) { break; } } } } return obj; } function grep(array, callback) { var out = []; each(array, function(i, item) { if (callback(item, i)) { out.push(item); } }); return out; } function getElementDocument(element) { if (!element) { return doc; } if (element.nodeType == 9) { return element; } return element.ownerDocument; } DomQuery.fn = DomQuery.prototype = { constructor: DomQuery, /** * Selector for the current set. * * @property selector * @type String */ selector: "", /** * Context used to create the set. * * @property context * @type Element */ context: null, /** * Number of items in the current set. * * @property length * @type Number */ length: 0, /** * Constructs a new DomQuery instance with the specified selector or context. * * @constructor * @method init * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string. * @param {Document/Element} context Optional context to search in. */ 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 (context && context.nodeType) { self.context = context; } else { if (context) { return DomQuery(selector).attr(context); } self.context = context = document; } if (isString(selector)) { self.selector = 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, getElementDocument(context)).firstChild; while (node) { push.call(self, node); node = node.nextSibling; } } else { node = getElementDocument(context).getElementById(match[2]); if (!node) { return self; } if (node.id !== match[2]) { return self.find(selector); } self.length = 1; self[0] = node; } } else { return DomQuery(context).find(selector); } } else { this.add(selector, false); } return self; }, /** * Converts the current set to an array. * * @method toArray * @param {Array} Array of all nodes in set. */ toArray: function() { return Tools.toArray(this); }, /** * Adds new nodes to the set. * * @method add * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set. * @return {tinymce.dom.DomQuery} New instance with nodes added. */ add: function(items, sort) { var self = this, nodes, i; if (isString(items)) { return self.add(DomQuery(items)); } if (sort !== false) { nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items))); self.length = nodes.length; for (i = 0; i < nodes.length; i++) { self[i] = nodes[i]; } } else { push.apply(self, DomQuery.makeArray(items)); } return self; }, /** * Sets/gets attributes on the elements in the current set. * * @method attr * @param {String/Object} name Name of attribute to get or an object with attributes to set. * @param {String} value Optional value to set. * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified. */ attr: function(name, value) { var self = this, hook; if (typeof name === "object") { each(name, function(name, value) { self.attr(name, value); }); } else if (isDefined(value)) { this.each(function() { var hook; if (this.nodeType === 1) { hook = attrHooks[name]; if (hook && hook.set) { hook.set(this, value); return; } if (value === null) { this.removeAttribute(name, 2); } else { this.setAttribute(name, value, 2); } } }); } else { if (self[0] && self[0].nodeType === 1) { hook = attrHooks[name]; if (hook && hook.get) { return hook.get(self[0], name); } if (booleanMap[name]) { return self.prop(name) ? name : undef; } value = self[0].getAttribute(name, 2); if (value === null) { value = undef; } } return value; } return self; }, /** * Removes attributse on the elements in the current set. * * @method removeAttr * @param {String/Object} name Name of attribute to remove. * @return {tinymce.dom.DomQuery/String} Current set. */ removeAttr: function(name) { return this.attr(name, null); }, /** * Sets/gets properties on the elements in the current set. * * @method attr * @param {String/Object} name Name of property to get or an object with properties to set. * @param {String} value Optional value to set. * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified. */ prop: function(name, value) { var self = this; name = propFix[name] || name; if (typeof name === "object") { each(name, function(name, value) { self.prop(name, value); }); } else if (isDefined(value)) { this.each(function() { if (this.nodeType == 1) { this[name] = value; } }); } else { if (self[0] && self[0].nodeType && name in self[0]) { return self[0][name]; } return value; } return self; }, /** * Sets/gets styles on the elements in the current set. * * @method css * @param {String/Object} name Name of style to get or an object with styles to set. * @param {String} value Optional value to set. * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified. */ css: function(name, value) { var self = this, elm, hook; function camel(name) { return name.replace(/-(\D)/g, function(a, b) { return b.toUpperCase(); }); } function dashed(name) { return name.replace(/[A-Z]/g, function(a) { return '-' + a; }); } if (typeof name === "object") { each(name, function(name, value) { self.css(name, value); }); } else { if (isDefined(value)) { name = camel(name); // Default px suffix on these if (typeof value === 'number' && !numericCssMap[name]) { value += 'px'; } self.each(function() { var style = this.style; hook = cssHooks[name]; if (hook && hook.set) { hook.set(this, value); return; } try { this.style[cssFix[name] || name] = value; } catch (ex) { // Ignore } if (value === null || value === '') { if (style.removeProperty) { style.removeProperty(dashed(name)); } else { style.removeAttribute(name); } } }); } else { elm = self[0]; hook = cssHooks[name]; if (hook && hook.get) { return hook.get(elm); } if (elm.ownerDocument.defaultView) { try { return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name)); } catch (ex) { return undef; } } else if (elm.currentStyle) { return elm.currentStyle[camel(name)]; } } } return self; }, /** * Removes all nodes in set from the document. * * @method remove * @return {tinymce.dom.DomQuery} Current set with the removed nodes. */ 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; }, /** * Empties all elements in set. * * @method empty * @return {tinymce.dom.DomQuery} Current set with the empty nodes. */ empty: function() { var self = this, node, i = this.length; while (i--) { node = self[i]; while (node.firstChild) { node.removeChild(node.firstChild); } } return this; }, /** * Sets or gets the HTML of the current set or first set node. * * @method html * @param {String} value Optional innerHTML value to set on each element. * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element. */ html: function(value) { var self = this, i; if (isDefined(value)) { i = self.length; try { while (i--) { self[i].innerHTML = value; } } catch (ex) { // Workaround for "Unknown runtime error" when DIV is added to P on IE DomQuery(self[i]).empty().append(value); } return self; } return self[0] ? self[0].innerHTML : ''; }, /** * Sets or gets the text of the current set or first set node. * * @method text * @param {String} value Optional innerText value to set on each element. * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element. */ text: function(value) { var self = this, i; if (isDefined(value)) { i = self.length; while (i--) { if ("innerText" in self[i]) { self[i].innerText = value; } else { self[0].textContent = value; } } return self; } return self[0] ? (self[0].innerText || self[0].textContent) : ''; }, /** * Appends the specified node/html or node set to the current set nodes. * * @method append * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set. * @return {tinymce.dom.DomQuery} Current set. */ append: function() { return domManipulate(this, arguments, function(node) { if (this.nodeType === 1) { this.appendChild(node); } }); }, /** * Prepends the specified node/html or node set to the current set nodes. * * @method prepend * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to prepend to each element in set. * @return {tinymce.dom.DomQuery} Current set. */ prepend: function() { return domManipulate(this, arguments, function(node) { if (this.nodeType === 1) { this.insertBefore(node, this.firstChild); } }, true); }, /** * Adds the specified elements before current set nodes. * * @method before * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set. * @return {tinymce.dom.DomQuery} Current set. */ before: function() { var self = this; if (self[0] && self[0].parentNode) { return domManipulate(self, arguments, function(node) { this.parentNode.insertBefore(node, this); }); }