/** * AddOnManager.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ import { Arr } from '@ephox/katamari'; import ScriptLoader from './dom/ScriptLoader'; import Tools from './util/Tools'; import { Editor } from 'tinymce/core/api/Editor'; /** * This class handles the loading of themes/plugins or other add-ons and their language packs. * * @class tinymce.AddOnManager */ /** * TinyMCE theme class. * * @class tinymce.Theme */ /** * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc. * * @method renderUI * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance. * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight. */ /** * Plugin base class, this is a pseudo class that describes how a plugin is to be created for TinyMCE. The methods below are all optional. * * @class tinymce.Plugin * @example * tinymce.PluginManager.add('example', function(editor, url) { * // Add a button that opens a window * editor.addButton('example', { * text: 'My button', * icon: false, * onclick: function() { * // Open window * editor.windowManager.open({ * title: 'Example plugin', * body: [ * {type: 'textbox', name: 'title', label: 'Title'} * ], * onsubmit: function(e) { * // Insert content when the window form is submitted * editor.insertContent('Title: ' + e.data.title); * } * }); * } * }); * * // Adds a menu item to the tools menu * editor.addMenuItem('example', { * text: 'Example plugin', * context: 'tools', * onclick: function() { * // Open window with a specific url * editor.windowManager.open({ * title: 'TinyMCE site', * url: 'http://www.tinymce.com', * width: 800, * height: 600, * buttons: [{ * text: 'Close', * onclick: 'close' * }] * }); * } * }); * }); */ const each = Tools.each; export interface UrlObject { prefix: string; resource: string; suffix: string; } export interface AddOnManager { items: any[]; urls: Record; lookup: {}; _listeners: any[]; get: (name: string) => any; dependencies: (name: string) => any; requireLangPack: (name: string, languages: string) => void; add: (id: string, addOn: (editor: Editor, url: string) => any, dependencies?: any) => (editor: Editor, url: string) => any; remove: (name: string) => void; createUrl: (baseUrl: UrlObject, dep: string | UrlObject) => UrlObject; addComponents: (pluginName: string, scripts: string[]) => void; load: (name: string, addOnUrl: string | UrlObject, success?: any, scope?: any, failure?: any) => void; waitFor: (name: string, callback: Function) => void; } export function AddOnManager(): AddOnManager { const items = []; const urls: Record = {}; const lookup = {}; let _listeners = []; const get = (name: string) => { if (lookup[name]) { return lookup[name].instance; } return undefined; }; const dependencies = (name: string) => { let result; if (lookup[name]) { result = lookup[name].dependencies; } return result || []; }; const requireLangPack = (name: string, languages: string) => { let language = AddOnManager.language; if (language && AddOnManager.languageLoad !== false) { if (languages) { languages = ',' + languages + ','; // Load short form sv.js or long form sv_SE.js if (languages.indexOf(',' + language.substr(0, 2) + ',') !== -1) { language = language.substr(0, 2); } else if (languages.indexOf(',' + language + ',') === -1) { return; } } ScriptLoader.ScriptLoader.add(urls[name] + '/langs/' + language + '.js'); } }; const add = (id: string, addOn: (editor: Editor, url: string) => any, dependencies?) => { items.push(addOn); lookup[id] = { instance: addOn, dependencies }; const result = Arr.partition(_listeners, function (listener) { return listener.name === id; }); _listeners = result.fail; each(result.pass, function (listener) { listener.callback(); }); return addOn; }; const remove = (name: string) => { delete urls[name]; delete lookup[name]; }; const createUrl = (baseUrl: string | UrlObject, dep: string | UrlObject): UrlObject => { if (typeof dep === 'object') { return dep; } return typeof baseUrl === 'string' ? { prefix: '', resource: dep, suffix: '' } : { prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix }; }; const addComponents = (pluginName: string, scripts: string[]) => { const pluginUrl = this.urls[pluginName]; each(scripts, function (script) { ScriptLoader.ScriptLoader.add(pluginUrl + '/' + script); }); }; const loadDependencies = function (addOnUrl: string | UrlObject, success: Function, scope: any) { const deps = dependencies(name); each(deps, function (dep) { const newUrl = createUrl(addOnUrl, dep); load(newUrl.resource, newUrl, undefined, undefined); }); if (success) { if (scope) { success.call(scope); } else { success.call(ScriptLoader); } } }; const load = (name: string, addOnUrl: string | UrlObject, success?: Function, scope?: any, failure?: Function) => { if (urls[name]) { return; } let urlString = typeof addOnUrl === 'string' ? addOnUrl : addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix; if (urlString.indexOf('/') !== 0 && urlString.indexOf('://') === -1) { urlString = AddOnManager.baseURL + '/' + urlString; } urls[name] = urlString.substring(0, urlString.lastIndexOf('/')); if (lookup[name]) { loadDependencies(addOnUrl, success, scope); } else { ScriptLoader.ScriptLoader.add(urlString, () => loadDependencies(addOnUrl, success, scope), scope, failure); } }; const waitFor = (name: string, callback: Function) => { if (lookup.hasOwnProperty(name)) { callback(); } else { _listeners.push({ name, callback }); } }; return { items, urls, lookup, _listeners, /** * Returns the specified add on by the short name. * * @method get * @param {String} name Add-on to look for. * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined. */ get, dependencies, /** * Loads a language pack for the specified add-on. * * @method requireLangPack * @param {String} name Short name of the add-on. * @param {String} languages Optional comma or space separated list of languages to check if it matches the name. */ requireLangPack, /** * Adds a instance of the add-on by it's short name. * * @method add * @param {String} id Short name/id for the add-on. * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add. * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in. * @example * // Create a simple plugin * tinymce.create('tinymce.plugins.TestPlugin', { * TestPlugin: function(ed, url) { * ed.on('click', function(e) { * ed.windowManager.alert('Hello World!'); * }); * } * }); * * // Register plugin using the add method * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin); * * // Initialize TinyMCE * tinymce.init({ * ... * plugins: '-test' // Init the plugin but don't try to load it * }); */ add, remove, createUrl, /** * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. * This should be used in development mode. A new compressor/javascript munger process will ensure that the * components are put together into the plugin.js file and compressed correctly. * * @method addComponents * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins). * @param {Array} scripts Array containing the names of the scripts to load. */ addComponents, /** * Loads an add-on from a specific url. * * @method load * @param {String} name Short name of the add-on that gets loaded. * @param {String} addOnUrl URL to the add-on that will get loaded. * @param {function} success Optional success callback to execute when an add-on is loaded. * @param {Object} scope Optional scope to execute the callback in. * @param {function} failure Optional failure callback to execute when an add-on failed to load. * @example * // Loads a plugin from an external URL * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js'); * * // Initialize TinyMCE * tinymce.init({ * ... * plugins: '-myplugin' // Don't try to load it again * }); */ load, waitFor }; } export namespace AddOnManager { export let language; export let languageLoad; export let baseURL; export const PluginManager = AddOnManager(); export const ThemeManager = AddOnManager(); }