react-tinymce-mention
Version:
@Mention functionality for TinyMCE, built with React and Redux.
2,073 lines (1,749 loc) • 1.01 MB
JavaScript
// 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);
});
}