2017-01-18 01:52:23 -05:00
|
|
|
/**
|
|
|
|
* @file plugin.js
|
|
|
|
*/
|
|
|
|
import evented from './mixins/evented';
|
|
|
|
import stateful from './mixins/stateful';
|
|
|
|
import * as Events from './utils/events';
|
2017-03-02 11:17:42 -05:00
|
|
|
import log from './utils/log';
|
2017-01-18 01:52:23 -05:00
|
|
|
import Player from './player';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The base plugin name.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @constant
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
const BASE_PLUGIN_NAME = 'plugin';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The key on which a player's active plugins cache is stored.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @constant
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
const PLUGIN_CACHE_KEY = 'activePlugins_';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores registered plugins in a private space.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
const pluginStorage = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reports whether or not a plugin has been registered.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string} name
|
|
|
|
* The name of a plugin.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {boolean}
|
2017-01-18 01:52:23 -05:00
|
|
|
* Whether or not the plugin has been registered.
|
|
|
|
*/
|
|
|
|
const pluginExists = (name) => pluginStorage.hasOwnProperty(name);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a single registered plugin by name.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string} name
|
|
|
|
* The name of a plugin.
|
|
|
|
*
|
2023-01-24 22:07:30 +01:00
|
|
|
* @return {typeof Plugin|Function|undefined}
|
2017-01-18 01:52:23 -05:00
|
|
|
* The plugin (or undefined).
|
|
|
|
*/
|
|
|
|
const getPlugin = (name) => pluginExists(name) ? pluginStorage[name] : undefined;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Marks a plugin as "active" on a player.
|
|
|
|
*
|
|
|
|
* Also, ensures that the player has an object for tracking active plugins.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {Player} player
|
|
|
|
* A Video.js player instance.
|
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* The name of a plugin.
|
|
|
|
*/
|
|
|
|
const markPluginAsActive = (player, name) => {
|
|
|
|
player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
|
|
|
|
player[PLUGIN_CACHE_KEY][name] = true;
|
|
|
|
};
|
|
|
|
|
2017-05-11 17:13:22 -04:00
|
|
|
/**
|
|
|
|
* Triggers a pair of plugin setup events.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {Player} player
|
|
|
|
* A Video.js player instance.
|
|
|
|
*
|
|
|
|
* @param {Plugin~PluginEventHash} hash
|
|
|
|
* A plugin event hash.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @param {boolean} [before]
|
2017-05-11 17:13:22 -04:00
|
|
|
* If true, prefixes the event name with "before". In other words,
|
|
|
|
* use this to trigger "beforepluginsetup" instead of "pluginsetup".
|
|
|
|
*/
|
|
|
|
const triggerSetupEvent = (player, hash, before) => {
|
|
|
|
const eventName = (before ? 'before' : '') + 'pluginsetup';
|
|
|
|
|
|
|
|
player.trigger(eventName, hash);
|
|
|
|
player.trigger(eventName + ':' + hash.name, hash);
|
|
|
|
};
|
|
|
|
|
2017-01-18 01:52:23 -05:00
|
|
|
/**
|
|
|
|
* Takes a basic plugin function and returns a wrapper function which marks
|
|
|
|
* on the player that the plugin has been activated.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string} name
|
|
|
|
* The name of the plugin.
|
|
|
|
*
|
|
|
|
* @param {Function} plugin
|
|
|
|
* The basic plugin.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {Function}
|
2017-01-18 01:52:23 -05:00
|
|
|
* A wrapper function for the given plugin.
|
|
|
|
*/
|
2017-02-21 15:49:42 -05:00
|
|
|
const createBasicPlugin = function(name, plugin) {
|
|
|
|
const basicPluginWrapper = function() {
|
2017-05-11 17:13:22 -04:00
|
|
|
|
|
|
|
// We trigger the "beforepluginsetup" and "pluginsetup" events on the player
|
|
|
|
// regardless, but we want the hash to be consistent with the hash provided
|
|
|
|
// for advanced plugins.
|
|
|
|
//
|
|
|
|
// The only potentially counter-intuitive thing here is the `instance` in
|
|
|
|
// the "pluginsetup" event is the value returned by the `plugin` function.
|
|
|
|
triggerSetupEvent(this, {name, plugin, instance: null}, true);
|
|
|
|
|
2017-02-21 15:49:42 -05:00
|
|
|
const instance = plugin.apply(this, arguments);
|
2017-01-18 01:52:23 -05:00
|
|
|
|
2017-02-21 15:49:42 -05:00
|
|
|
markPluginAsActive(this, name);
|
2017-05-11 17:13:22 -04:00
|
|
|
triggerSetupEvent(this, {name, plugin, instance});
|
2017-01-18 01:52:23 -05:00
|
|
|
|
2017-02-21 15:49:42 -05:00
|
|
|
return instance;
|
|
|
|
};
|
|
|
|
|
|
|
|
Object.keys(plugin).forEach(function(prop) {
|
|
|
|
basicPluginWrapper[prop] = plugin[prop];
|
|
|
|
});
|
|
|
|
|
|
|
|
return basicPluginWrapper;
|
2017-01-18 01:52:23 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a plugin sub-class and returns a factory function for generating
|
|
|
|
* instances of it.
|
|
|
|
*
|
|
|
|
* This factory function will replace itself with an instance of the requested
|
|
|
|
* sub-class of Plugin.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string} name
|
|
|
|
* The name of the plugin.
|
|
|
|
*
|
|
|
|
* @param {Plugin} PluginSubClass
|
|
|
|
* The advanced plugin.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {Function}
|
2017-01-18 01:52:23 -05:00
|
|
|
*/
|
|
|
|
const createPluginFactory = (name, PluginSubClass) => {
|
|
|
|
|
|
|
|
// Add a `name` property to the plugin prototype so that each plugin can
|
|
|
|
// refer to itself by name.
|
|
|
|
PluginSubClass.prototype.name = name;
|
|
|
|
|
|
|
|
return function(...args) {
|
2017-05-11 17:13:22 -04:00
|
|
|
triggerSetupEvent(this, {name, plugin: PluginSubClass, instance: null}, true);
|
|
|
|
|
2017-01-18 01:52:23 -05:00
|
|
|
const instance = new PluginSubClass(...[this, ...args]);
|
|
|
|
|
|
|
|
// The plugin is replaced by a function that returns the current instance.
|
|
|
|
this[name] = () => instance;
|
|
|
|
|
2017-05-11 17:13:22 -04:00
|
|
|
triggerSetupEvent(this, instance.getEventHash());
|
|
|
|
|
2017-01-18 01:52:23 -05:00
|
|
|
return instance;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parent class for all advanced plugins.
|
|
|
|
*
|
|
|
|
* @mixes module:evented~EventedMixin
|
|
|
|
* @mixes module:stateful~StatefulMixin
|
2017-05-11 17:13:22 -04:00
|
|
|
* @fires Player#beforepluginsetup
|
|
|
|
* @fires Player#beforepluginsetup:$name
|
2017-01-18 01:52:23 -05:00
|
|
|
* @fires Player#pluginsetup
|
2017-05-11 17:13:22 -04:00
|
|
|
* @fires Player#pluginsetup:$name
|
2017-01-18 01:52:23 -05:00
|
|
|
* @listens Player#dispose
|
|
|
|
* @throws {Error}
|
|
|
|
* If attempting to instantiate the base {@link Plugin} class
|
|
|
|
* directly instead of via a sub-class.
|
|
|
|
*/
|
|
|
|
class Plugin {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an instance of this class.
|
|
|
|
*
|
|
|
|
* Sub-classes should call `super` to ensure plugins are properly initialized.
|
|
|
|
*
|
|
|
|
* @param {Player} player
|
|
|
|
* A Video.js player instance.
|
|
|
|
*/
|
|
|
|
constructor(player) {
|
|
|
|
if (this.constructor === Plugin) {
|
|
|
|
throw new Error('Plugin must be sub-classed; not directly instantiated.');
|
|
|
|
}
|
|
|
|
|
2017-05-11 17:13:22 -04:00
|
|
|
this.player = player;
|
|
|
|
|
2020-07-10 11:57:37 -04:00
|
|
|
if (!this.log) {
|
|
|
|
this.log = this.player.log.createLogger(this.name);
|
|
|
|
}
|
|
|
|
|
2017-01-18 01:52:23 -05:00
|
|
|
// Make this object evented, but remove the added `trigger` method so we
|
|
|
|
// use the prototype version instead.
|
|
|
|
evented(this);
|
|
|
|
delete this.trigger;
|
|
|
|
|
|
|
|
stateful(this, this.constructor.defaultState);
|
|
|
|
markPluginAsActive(player, this.name);
|
|
|
|
|
|
|
|
// Auto-bind the dispose method so we can use it as a listener and unbind
|
|
|
|
// it later easily.
|
2021-03-24 15:38:27 -04:00
|
|
|
this.dispose = this.dispose.bind(this);
|
2017-01-18 01:52:23 -05:00
|
|
|
|
|
|
|
// If the player is disposed, dispose the plugin.
|
|
|
|
player.on('dispose', this.dispose);
|
|
|
|
}
|
|
|
|
|
2017-11-07 20:48:24 +00:00
|
|
|
/**
|
|
|
|
* Get the version of the plugin that was set on <pluginName>.VERSION
|
|
|
|
*/
|
|
|
|
version() {
|
|
|
|
return this.constructor.VERSION;
|
|
|
|
}
|
|
|
|
|
2017-01-18 01:52:23 -05:00
|
|
|
/**
|
|
|
|
* Each event triggered by plugins includes a hash of additional data with
|
|
|
|
* conventional properties.
|
|
|
|
*
|
|
|
|
* This returns that object or mutates an existing hash.
|
|
|
|
*
|
|
|
|
* @param {Object} [hash={}]
|
|
|
|
* An object to be used as event an event hash.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {Plugin~PluginEventHash}
|
2017-01-18 01:52:23 -05:00
|
|
|
* An event hash object with provided properties mixed-in.
|
|
|
|
*/
|
|
|
|
getEventHash(hash = {}) {
|
|
|
|
hash.name = this.name;
|
|
|
|
hash.plugin = this.constructor;
|
|
|
|
hash.instance = this;
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers an event on the plugin object and overrides
|
|
|
|
* {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
|
|
|
|
*
|
|
|
|
* @param {string|Object} event
|
|
|
|
* An event type or an object with a type property.
|
|
|
|
*
|
|
|
|
* @param {Object} [hash={}]
|
|
|
|
* Additional data hash to merge with a
|
|
|
|
* {@link Plugin~PluginEventHash|PluginEventHash}.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {boolean}
|
2017-01-18 01:52:23 -05:00
|
|
|
* Whether or not default was prevented.
|
|
|
|
*/
|
|
|
|
trigger(event, hash = {}) {
|
|
|
|
return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles "statechanged" events on the plugin. No-op by default, override by
|
|
|
|
* subclassing.
|
|
|
|
*
|
|
|
|
* @abstract
|
|
|
|
* @param {Event} e
|
|
|
|
* An event object provided by a "statechanged" event.
|
|
|
|
*
|
|
|
|
* @param {Object} e.changes
|
|
|
|
* An object describing changes that occurred with the "statechanged"
|
|
|
|
* event.
|
|
|
|
*/
|
|
|
|
handleStateChanged(e) {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disposes a plugin.
|
|
|
|
*
|
|
|
|
* Subclasses can override this if they want, but for the sake of safety,
|
|
|
|
* it's probably best to subscribe the "dispose" event.
|
|
|
|
*
|
|
|
|
* @fires Plugin#dispose
|
|
|
|
*/
|
|
|
|
dispose() {
|
|
|
|
const {name, player} = this;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Signals that a advanced plugin is about to be disposed.
|
|
|
|
*
|
|
|
|
* @event Plugin#dispose
|
2023-03-02 06:41:57 +01:00
|
|
|
* @type {Event}
|
2017-01-18 01:52:23 -05:00
|
|
|
*/
|
|
|
|
this.trigger('dispose');
|
|
|
|
this.off();
|
|
|
|
player.off('dispose', this.dispose);
|
|
|
|
|
|
|
|
// Eliminate any possible sources of leaking memory by clearing up
|
|
|
|
// references between the player and the plugin instance and nulling out
|
|
|
|
// the plugin's state and replacing methods with a function that throws.
|
|
|
|
player[PLUGIN_CACHE_KEY][name] = false;
|
|
|
|
this.player = this.state = null;
|
|
|
|
|
|
|
|
// Finally, replace the plugin name on the player with a new factory
|
|
|
|
// function, so that the plugin is ready to be set up again.
|
|
|
|
player[name] = createPluginFactory(name, pluginStorage[name]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
|
|
|
|
*
|
|
|
|
* @param {string|Function} plugin
|
|
|
|
* If a string, matches the name of a plugin. If a function, will be
|
|
|
|
* tested directly.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {boolean}
|
2017-01-18 01:52:23 -05:00
|
|
|
* Whether or not a plugin is a basic plugin.
|
|
|
|
*/
|
|
|
|
static isBasic(plugin) {
|
|
|
|
const p = (typeof plugin === 'string') ? getPlugin(plugin) : plugin;
|
|
|
|
|
|
|
|
return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a Video.js plugin.
|
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* The name of the plugin to be registered. Must be a string and
|
|
|
|
* must not match an existing plugin or a method on the `Player`
|
|
|
|
* prototype.
|
|
|
|
*
|
2023-01-24 22:07:30 +01:00
|
|
|
* @param {typeof Plugin|Function} plugin
|
2017-01-18 01:52:23 -05:00
|
|
|
* A sub-class of `Plugin` or a function for basic plugins.
|
|
|
|
*
|
2023-01-24 22:07:30 +01:00
|
|
|
* @return {typeof Plugin|Function}
|
2017-01-18 01:52:23 -05:00
|
|
|
* For advanced plugins, a factory function for that plugin. For
|
|
|
|
* basic plugins, a wrapper function that initializes the plugin.
|
|
|
|
*/
|
|
|
|
static registerPlugin(name, plugin) {
|
|
|
|
if (typeof name !== 'string') {
|
|
|
|
throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
|
|
|
|
}
|
|
|
|
|
2017-03-02 11:17:42 -05:00
|
|
|
if (pluginExists(name)) {
|
|
|
|
log.warn(`A plugin named "${name}" already exists. You may want to avoid re-registering plugins!`);
|
|
|
|
} else if (Player.prototype.hasOwnProperty(name)) {
|
|
|
|
throw new Error(`Illegal plugin name, "${name}", cannot share a name with an existing player method!`);
|
2017-01-18 01:52:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof plugin !== 'function') {
|
|
|
|
throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
pluginStorage[name] = plugin;
|
|
|
|
|
|
|
|
// Add a player prototype method for all sub-classed plugins (but not for
|
|
|
|
// the base Plugin class).
|
|
|
|
if (name !== BASE_PLUGIN_NAME) {
|
|
|
|
if (Plugin.isBasic(plugin)) {
|
|
|
|
Player.prototype[name] = createBasicPlugin(name, plugin);
|
|
|
|
} else {
|
|
|
|
Player.prototype[name] = createPluginFactory(name, plugin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return plugin;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* De-register a Video.js plugin.
|
|
|
|
*
|
2018-09-28 16:21:18 -04:00
|
|
|
* @param {string} name
|
|
|
|
* The name of the plugin to be de-registered. Must be a string that
|
|
|
|
* matches an existing plugin.
|
|
|
|
*
|
|
|
|
* @throws {Error}
|
|
|
|
* If an attempt is made to de-register the base plugin.
|
2017-01-18 01:52:23 -05:00
|
|
|
*/
|
|
|
|
static deregisterPlugin(name) {
|
|
|
|
if (name === BASE_PLUGIN_NAME) {
|
|
|
|
throw new Error('Cannot de-register base plugin.');
|
|
|
|
}
|
|
|
|
if (pluginExists(name)) {
|
|
|
|
delete pluginStorage[name];
|
|
|
|
delete Player.prototype[name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets an object containing multiple Video.js plugins.
|
|
|
|
*
|
|
|
|
* @param {Array} [names]
|
|
|
|
* If provided, should be an array of plugin names. Defaults to _all_
|
|
|
|
* plugin names.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {Object|undefined}
|
2017-01-18 01:52:23 -05:00
|
|
|
* An object containing plugin(s) associated with their name(s) or
|
|
|
|
* `undefined` if no matching plugins exist).
|
|
|
|
*/
|
|
|
|
static getPlugins(names = Object.keys(pluginStorage)) {
|
|
|
|
let result;
|
|
|
|
|
|
|
|
names.forEach(name => {
|
|
|
|
const plugin = getPlugin(name);
|
|
|
|
|
|
|
|
if (plugin) {
|
|
|
|
result = result || {};
|
|
|
|
result[name] = plugin;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a plugin's version, if available
|
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* The name of a plugin.
|
|
|
|
*
|
2018-09-28 14:58:15 -04:00
|
|
|
* @return {string}
|
2017-01-18 01:52:23 -05:00
|
|
|
* The plugin's version or an empty string.
|
|
|
|
*/
|
|
|
|
static getPluginVersion(name) {
|
|
|
|
const plugin = getPlugin(name);
|
|
|
|
|
|
|
|
return plugin && plugin.VERSION || '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a plugin by name if it exists.
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @method getPlugin
|
|
|
|
* @memberOf Plugin
|
|
|
|
* @param {string} name
|
|
|
|
* The name of a plugin.
|
|
|
|
*
|
2023-01-24 22:07:30 +01:00
|
|
|
* @returns {typeof Plugin|Function|undefined}
|
2017-01-18 01:52:23 -05:00
|
|
|
* The plugin (or `undefined`).
|
|
|
|
*/
|
|
|
|
Plugin.getPlugin = getPlugin;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The name of the base plugin class as it is registered.
|
|
|
|
*
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
|
|
|
|
|
|
|
|
Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Documented in player.js
|
|
|
|
*
|
|
|
|
* @ignore
|
|
|
|
*/
|
|
|
|
Player.prototype.usingPlugin = function(name) {
|
|
|
|
return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Documented in player.js
|
|
|
|
*
|
|
|
|
* @ignore
|
|
|
|
*/
|
|
|
|
Player.prototype.hasPlugin = function(name) {
|
|
|
|
return !!pluginExists(name);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Plugin;
|
|
|
|
|
2017-05-11 17:13:22 -04:00
|
|
|
/**
|
|
|
|
* Signals that a plugin is about to be set up on a player.
|
|
|
|
*
|
|
|
|
* @event Player#beforepluginsetup
|
|
|
|
* @type {Plugin~PluginEventHash}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Signals that a plugin is about to be set up on a player - by name. The name
|
|
|
|
* is the name of the plugin.
|
|
|
|
*
|
|
|
|
* @event Player#beforepluginsetup:$name
|
|
|
|
* @type {Plugin~PluginEventHash}
|
|
|
|
*/
|
|
|
|
|
2017-01-18 01:52:23 -05:00
|
|
|
/**
|
|
|
|
* Signals that a plugin has just been set up on a player.
|
|
|
|
*
|
|
|
|
* @event Player#pluginsetup
|
|
|
|
* @type {Plugin~PluginEventHash}
|
|
|
|
*/
|
|
|
|
|
2017-05-11 17:13:22 -04:00
|
|
|
/**
|
|
|
|
* Signals that a plugin has just been set up on a player - by name. The name
|
|
|
|
* is the name of the plugin.
|
|
|
|
*
|
|
|
|
* @event Player#pluginsetup:$name
|
|
|
|
* @type {Plugin~PluginEventHash}
|
|
|
|
*/
|
|
|
|
|
2017-01-18 01:52:23 -05:00
|
|
|
/**
|
|
|
|
* @typedef {Object} Plugin~PluginEventHash
|
|
|
|
*
|
|
|
|
* @property {string} instance
|
|
|
|
* For basic plugins, the return value of the plugin function. For
|
|
|
|
* advanced plugins, the plugin instance on which the event is fired.
|
|
|
|
*
|
|
|
|
* @property {string} name
|
|
|
|
* The name of the plugin.
|
|
|
|
*
|
|
|
|
* @property {string} plugin
|
|
|
|
* For basic plugins, the plugin function. For advanced plugins, the
|
|
|
|
* plugin class/constructor.
|
|
|
|
*/
|