UNPKG

europa-core

Version:

Europa core engine for converting HTML into valid Markdown

206 lines 9.71 kB
"use strict"; /* * Copyright (C) 2022 neocotic * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var _a, _b, _c, _d; Object.defineProperty(exports, "__esModule", { value: true }); exports.PluginManager = void 0; const PluginApi_1 = require("./PluginApi"); const _addProvidedPlugin = Symbol(); const _api = Symbol(); const _converters = Symbol(); const _hookCache = Symbol(); const _getHookCache = Symbol(); const _plugins = Symbol(); /** * A basic manager for plugins and presets (collections of plugins) that can be hooked into {@link EuropaCore}. */ class PluginManager { constructor() { this[_a] = new PluginApi_1.PluginApi(); this[_b] = {}; this[_c] = []; this[_d] = null; } /** * Invokes the specified plugin `provider` and adds the resulting plugin to this {@link PluginManager}. * * If the plugin contains any converters, they will be associated with their corresponding tag names, overriding any * previously converters associated with those tag names. * * If an error occurs when invoking `provider`, the plugin will not be added to this {@link PluginManager}. * * @param provider - The provider for the plugin to be added. * @return A reference to this {@link PluginManager} for chaining purposes. * @throws If a problem occurs while invoking `provider`. */ addPlugin(provider) { const plugin = provider(this[_api]); this[_addProvidedPlugin](plugin); return this; } /** * Invokes the specified preset `provider` and adds the resulting preset to this {@link PluginManager}. * * This method is effectively just a shortcut for calling {@link PluginManager#addPlugin} for multiple plugin * providers, however, the main benefit is that it supports the concept of presets, which are a useful mechanism for * bundling and distributing plugins. * * If an error occurs when invoking `provider`, the preset and all of its plugins will not be added to this * {@link PluginManager}. * * @param provider - The provider for the preset whose plugins are to be added. * @return A reference to this {@link PluginManager} for chaining purposes. * @throws If a problem occurs while invoking `provider`. */ addPreset(provider) { var _e; const preset = provider(this[_api]); (_e = preset.plugins) === null || _e === void 0 ? void 0 : _e.forEach((plugin) => this[_addProvidedPlugin](plugin)); return this; } /** * Returns whether this {@link PluginManager} contains a converter for the specified `tagName` and that the converter * has a hook with the name provided. * * @param tagName - The name of the tag to be checked. * @param hookName - The name of the hook to be checked. * @return `true` if a converter for `tagName` exists and has the named hook; otherwise `false`. */ hasConverterHook(tagName, hookName) { const converter = this[_converters][tagName]; return typeof (converter === null || converter === void 0 ? void 0 : converter[hookName]) === 'function'; } /** * Invokes the hook with the specified name on with the `conversion` and `context` provided on the converter for the * given `tagName` within this {@link PluginManager}. * * If there is no converter for `tagName` or the converter does not have the named hook, nothing happens and one of * the following values will be returned based on `hookName`: * * | Hook Name | Return Value | * | ---------- | ------------ | * | `endTag` | `undefined` | * | `startTag` | `false` | * * Otherwise, it will return the result of invoking the named hook. For this reason it is highly recommended that, * if the return value is important, {@link PluginManager#hasConverterHook} is called with the same `tagName` and * `hookName` combination to check whether the converter hook exists before calling * {@link PluginManager#invokeConverterHook}. * * @param tagName - The name of the tag whose converter (if any) the named hook is to be invoked on. * @param hookName - The name of the hook to be invoked. * @param conversion - The current {@link Conversion}. * @param context - The context for the current element within the {@link Conversion}. * @return The result of calling the hook or a default value based on `hookName` (see above table) if there is no * converter for `tagName` or the converter does not have the named hook. */ invokeConverterHook(tagName, hookName, conversion, context) { var _e; const hook = (_e = this[_converters][tagName]) === null || _e === void 0 ? void 0 : _e[hookName]; if (typeof hook !== 'function') { return hookName === 'startTag' ? false : undefined; } return hook(conversion, context); } /** * Invokes the hook with the specified name with the `conversion` provided on each of the plugins within this * {@link PluginManager}. * * Any plugins that do not have the named hook are skipped. * * @param hookName - The name of the hook to be invoked. * @param conversion - The current {@link Conversion}. */ invokeHook(hookName, conversion) { this[_plugins].forEach((plugin) => { const hook = plugin[hookName]; if (typeof hook === 'function') { hook(conversion); } }); } /** * Invokes a special hook on all plugins within this {@link PluginManager} that allows plugins to conditionally * perform an alternative conversion of the specified `value` which has been taken from a text node being converted by * the given `conversion`. * * This relies entirely on plugins ensuring that, if they perform any alternative conversion of the specified `value`, * that their hook returns `true` in order to prevent `conversion` from also processing `value` which would result in * duplication and would likely have other unexpected and unwanted side effects. * * @param value - The text value to potentially be converted. * @param conversion - The current {@link Conversion}. * @return `true` if `value` has been converted by a plugin; otherwise `false`. */ invokeTextConverterHook(value, conversion) { const hookCache = this[_getHookCache](); return hookCache.textConverters.some((textConverter) => textConverter(value, conversion)); } /** * Invokes a special hook on all plugins within this {@link PluginManager} that allows plugins to perform any * additional escaping of special characters within the specified `value` which has been taken from a text node being * converted by the given `conversion`. * * This relies on plugins ensuring that they do not double-escape characters that have either been previously escaped * by `conversion` or another plugin. * * @param value - The text value to be escaped. * @param conversion - The current {@link Conversion}. * @return The escaped `str` or `str` if no escaping is required. */ invokeTextEscaperHook(value, conversion) { const hookCache = this[_getHookCache](); return hookCache.textEscapers.reduce((acc, textEscaper) => textEscaper(acc, conversion), value); } [(_a = _api, _b = _converters, _c = _plugins, _d = _hookCache, _addProvidedPlugin)](plugin) { if (plugin.converters) { Object.entries(plugin.converters).forEach(([tagName, converter]) => { this[_converters][tagName.toUpperCase()] = converter; }); } if (typeof plugin.convertText === 'function' || typeof plugin.escapeText === 'function') { this[_hookCache] = null; } this[_plugins].push(plugin); } [_getHookCache]() { let hookCache = this[_hookCache]; if (hookCache) { return hookCache; } const textConverters = []; const textEscapers = []; this[_plugins].forEach((plugin) => { if (typeof plugin.convertText === 'function') { textConverters.push(plugin.convertText); } if (typeof plugin.escapeText === 'function') { textEscapers.push(plugin.escapeText); } }); hookCache = { textConverters, textEscapers }; this[_hookCache] = hookCache; return hookCache; } } exports.PluginManager = PluginManager; //# sourceMappingURL=PluginManager.js.map