jodit
Version:
Jodit is awesome and usefully wysiwyg editor with filebrowser
280 lines (246 loc) • 5.42 kB
text/typescript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Released under MIT see LICENSE.txt in the project root for license information.
* Copyright (c) 2013-2020 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
*/
import {
IExtraPlugin,
IDictionary,
IJodit,
IPlugin,
IPluginSystem,
PluginInstance,
PluginType
} from '../types';
import {
isInitable,
isDestructable,
isFunction,
appendScriptAsync,
splitArray,
appendStyleAsync,
isString
} from './helpers';
/**
* Jodit plugin system
* @example
* ```js
* Jodit.plugins.add('emoji2', {
* init() {
* alert('emoji Inited2')
* },
* destruct() {}
* });
* ```
*/
export class PluginSystem implements IPluginSystem {
private items = new Map<string, PluginType>();
/**
* Add plugin in store
*
* @param name
* @param plugin
*/
add(name: string, plugin: PluginType): void {
this.items.set(name.toLowerCase(), plugin);
}
/**
* Get plugin from store
* @param name
*/
get(name: string): PluginType | void {
return this.items.get(name.toLowerCase());
}
/**
* Remove plugin from store
* @param name
*/
remove(name: string): void {
this.items.delete(name.toLowerCase());
}
/**
* Public methos for async init all plugins
* @param jodit
*/
async init(jodit: IJodit): Promise<void> {
const extrasList: IExtraPlugin[] = jodit.o.extraPlugins.map(s => {
return isString(s) ? { name: s.toLowerCase() } : s;
}),
disableList = splitArray(jodit.o.disablePlugins).map(s =>
s.toLowerCase()
),
doneList: string[] = [],
promiseList: IDictionary<PluginInstance | undefined> = {},
plugins: PluginInstance[] = [],
pluginsMap: IDictionary<PluginInstance> = {},
makeAndInit = (plugin: PluginType, name: string) => {
if (
disableList.includes(name) ||
doneList.includes(name) ||
promiseList[name]
) {
return;
}
const instance = PluginSystem.makePluginInstance(jodit, plugin);
this.initOrWait(jodit, name, instance, doneList, promiseList);
plugins.push(instance);
pluginsMap[name] = instance;
};
if (extrasList && extrasList.length) {
try {
const needLoadExtras = extrasList.filter(
extra => !this.items.has(extra.name)
);
if (needLoadExtras.length) {
await this.load(jodit, needLoadExtras);
}
} catch (e) {
if (!isProd) {
throw e;
}
}
}
if (jodit.isInDestruct) {
return;
}
this.items.forEach(makeAndInit);
this.addListenerOnBeforeDestruct(jodit, plugins);
(jodit as any).__plugins = pluginsMap;
}
/**
* Create instance of plugin
*
* @param jodit
* @param plugin
*/
static makePluginInstance(
jodit: IJodit,
plugin: PluginType
): PluginInstance {
return isFunction(plugin) ? new plugin(jodit) : plugin;
}
/**
* Init plugin if it has not dependencies in another case wait requires plugins will be init
*
* @param jodit
* @param pluginName
* @param instance
* @param doneList
* @param promiseList
*/
private initOrWait(
jodit: IJodit,
pluginName: string,
instance: PluginInstance,
doneList: string[],
promiseList: IDictionary<PluginInstance | undefined>
) {
const initPlugin = (name: string, plugin: PluginInstance): boolean => {
if ((plugin as IPlugin).hasStyle) {
PluginSystem.loadStyle(jodit, name);
}
if (isInitable(plugin)) {
const req = (plugin as IPlugin).requires;
if (
!req ||
!req.length ||
req.every(name => doneList.includes(name))
) {
plugin.init(jodit);
doneList.push(name);
} else {
promiseList[name] = plugin;
return false;
}
} else {
doneList.push(name);
}
return true;
};
initPlugin(pluginName, instance);
Object.keys(promiseList).forEach(name => {
const plugin = promiseList[name];
if (!plugin) {
return;
}
if (initPlugin(name, instance)) {
promiseList[name] = undefined;
delete promiseList[name];
}
});
}
/**
* Destroy all plugins before - Jodit will be destroyed
*
* @param jodit
* @param plugins
*/
private addListenerOnBeforeDestruct(
jodit: IJodit,
plugins: PluginInstance[]
) {
jodit.e.on('beforeDestruct', () => {
plugins.forEach(instance => {
if (isDestructable(instance)) {
instance.destruct(jodit);
}
});
plugins.length = 0;
delete (jodit as any).__plugins;
});
}
/**
* Download plugins
*
* @param jodit
* @param pluginList
*/
private load(jodit: IJodit, pluginList: IExtraPlugin[]): Promise<any> {
const reflect = (p: Promise<any>) =>
p.then(
(v: any) => ({ v, status: 'fulfilled' }),
(e: any) => ({ e, status: 'rejected' })
);
return Promise.all(
pluginList.map(extra => {
const url =
extra.url || PluginSystem.getFullUrl(jodit, name, true);
return reflect(appendScriptAsync(jodit, url));
})
);
}
/**
*
*
* @param jodit
* @param pluginName
*/
private static loadStyle(jodit: IJodit, pluginName: string): Promise<void> {
return appendStyleAsync(
jodit,
PluginSystem.getFullUrl(jodit, pluginName, false)
);
}
/**
* Call full url to the script or style file
*
* @param jodit
* @param name
* @param js
*/
private static getFullUrl(
jodit: IJodit,
name: string,
js: boolean
): string {
return (
jodit.basePath +
'plugins/' +
name +
'/' +
name +
'.' +
(js ? 'js' : 'css')
);
}
}