"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _postcss = _interopRequireDefault(require("postcss")); var _postcss_plugin = _interopRequireDefault(require("./helpers/postcss_plugin")); var _advanced_background = _interopRequireDefault(require("./postcss/advanced_background")); var _container_query = _interopRequireWildcard(require("./postcss/container_query")); var _hoisting = _interopRequireDefault(require("./postcss/import/hoisting")); var _replace = _interopRequireDefault(require("./postcss/import/replace")); var _suppress = _interopRequireDefault(require("./postcss/import/suppress")); var _nesting = _interopRequireDefault(require("./postcss/nesting")); var _pagination = _interopRequireDefault(require("./postcss/pagination")); var _printable = _interopRequireWildcard(require("./postcss/printable")); var _prepend = _interopRequireDefault(require("./postcss/pseudo_selector/prepend")); var _replace2 = _interopRequireDefault(require("./postcss/pseudo_selector/replace")); var _font_size = _interopRequireDefault(require("./postcss/root/font_size")); var _increasing_specificity = _interopRequireWildcard(require("./postcss/root/increasing_specificity")); var _rem = _interopRequireDefault(require("./postcss/root/rem")); var _replace3 = _interopRequireDefault(require("./postcss/root/replace")); var _svg_backdrop = _interopRequireDefault(require("./postcss/svg_backdrop")); var _theme = _interopRequireDefault(require("./theme")); var _scaffold = _interopRequireDefault(require("./theme/scaffold")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const defaultOptions = { cssNesting: false }; /** * Marpit theme set class. */ class ThemeSet { /** * Create a ThemeSet instance. * * @param {Object} [opts] * @param {boolean} [opts.cssNesting=true] Enable CSS nesting support. */ constructor(opts = defaultOptions) { /** * An instance of default theme. * * While running {@link ThemeSet#pack}, ThemeSet will use this theme when * the definition of theme directive or the theme with specified name is not * found. * * By default, Marpit does not provide default theme (`undefined`). * * @type {Theme|undefined} */ this.default = undefined; /** * The default type settings for theme metadata added by * {@link ThemeSet#add}. * * A key of object is the name of metadata and a value is the type which of * `String` and `Array`. You have to set `Array` if the theme allows * multi-time definitions in same meta key. * * ```css * /** * * @theme example * * @foo Single value * * @foo allows only one string * * @bar Multiple value 1 * * @bar Multiple value 2 * * @bar Multiple value 3 * * ... * ``` * * ```js * const themeSet = new ThemeSet() * * themeSet.metaType = { * foo: String, * bar: Array, * } * * themeSet.add(css) * * console.log(themeSet.getThemeMeta('example', 'foo')) * // => 'allows only one string' * * console.log(themeSet.getThemeMeta('example', 'bar')) * // => ['Multiple value 1', 'Multiple value 2', 'Multiple value 3'] * ``` * * @type {Object} */ this.metaType = {}; /** * A boolean value indicating whether the theme set is enabling CSS nesting * or not. * * @type {boolean} */ this.cssNesting = !!opts.cssNesting; Object.defineProperty(this, 'themeMap', { value: new Map() }); } /** * Return the number of themes. * * @type {number} * @readonly */ get size() { return this.themeMap.size; } /** * Add theme CSS from string. * * @param {string} css The theme CSS string. * @returns {Theme} A created {@link Theme} instance. * @throws Will throw an error if the theme name is not specified by `@theme` * metadata. */ add(css) { const theme = _theme.default.fromCSS(css, { metaType: this.metaType, cssNesting: this.cssNesting }); this.addTheme(theme); return theme; } /** * Add theme instance. * * @param {Theme} theme The theme instance. * @throws Will throw an error if the theme name is not specified. */ addTheme(theme) { if (!(theme instanceof _theme.default)) throw new Error('ThemeSet can add only an instance of Theme.'); if (typeof theme.name !== 'string') throw new Error('An instance of Theme requires name.'); this.themeMap.set(theme.name, theme); } /** * Removes all themes from a {@link themeSet} object. */ clear() { return this.themeMap.clear(); } /** * Remove a specific named theme from a {@link themeSet} object. * * @param {string} name The theme name to delete. * @returns {boolean} Returns `true` if a theme in current {@link ThemeSet} * existed and has been removed, or `false` if the theme does not exist. */ delete(name) { return this.themeMap.delete(name); } /** * Returns a specific named theme. * * @param {string} name The theme name to get. * @param {boolean} [fallback=false] If true, return instance's default theme * or scaffold theme when specified theme cannot find. * @returns {Theme|undefined} Returns specified or fallbacked theme, or * `undefined` if `fallback` is false and the specified theme has not * existed. */ get(name, fallback = false) { const theme = this.themeMap.get(name); return fallback ? theme || this.default || _scaffold.default : theme; } /** * Returns value(s) of specified metadata from a theme. It considers `@import` * and `@import-theme` rules in getting meta value. On the other hand, the * default theme specified by the instance is not considered. * * To support metadata with array type, it will merge into a flatten array * when the all of got valid values that includes imported themes are array. * * @param {string|Theme} theme The theme name or instance. * @param {string} meta The meta name to get. * @returns {string|string[]|undefined} */ getThemeMeta(theme, meta) { const themeInstance = theme instanceof _theme.default ? theme : this.get(theme); const metas = themeInstance ? this.resolveImport(themeInstance).map(t => t.meta[meta]).filter(m => m) : []; // Flatten in order of definitions when the all of valid values are array if (metas.length > 0 && metas.every(m => Array.isArray(m))) { const mergedArray = []; for (const m of metas) mergedArray.unshift(...m); return mergedArray; } return metas[0]; } /** * Returns the value of specified property name from a theme. It considers * `@import` and `@import-theme` rules in getting value. * * It will fallback the reference object into the instance's default theme or * scaffold theme when the specified theme is `undefined`. * * @param {string|Theme} theme The theme name or instance. * @param {string} prop The property name to get. * @returns {*} */ getThemeProp(theme, prop) { const themeInstance = theme instanceof _theme.default ? theme : this.get(theme); const props = themeInstance ? this.resolveImport(themeInstance).map(t => t[prop]) : []; return [...props, this.default && this.default[prop], _scaffold.default[prop]].find(t => t); } /** * Returns a boolean indicating whether a specific named theme exists or not. * * @param {string} name The theme name. * @returns {boolean} Returns `true` if a specific named theme exists, * otherwise `false`. */ has(name) { return this.themeMap.has(name); } /** * Convert registered theme CSS into usable in the rendered markdown by * {@link Marpit#render}. * * **This method is designed for internal use by {@link Marpit} class.** Use * {@link Marpit#render} instead unless there is some particular reason. * * @param {string} name The theme name. It will use the instance's default * theme or scaffold theme when a specific named theme does not exist. * @param {Object} [opts] The option object passed by {@link Marpit#render}. * @param {string} [opts.after] A CSS string to append into after theme. * @param {string} [opts.before] A CSS string to prepend into before theme. * @param {Element[]} [opts.containers] Container elements wrapping whole * slide deck. * @param {boolean|string|string[]} [opts.containerQuery] Enable CSS container * query by setting `true`. You can also specify the name of container for * CSS container query used by the `@container` at-rule in child elements. * @param {boolean} [opts.printable] Make style printable to PDF. * @param {Marpit~InlineSVGOptions} [opts.inlineSVG] Apply a hierarchy of * inline SVG to CSS selector by setting `true`. _(Experimental)_ * @return {string} The converted CSS string. */ pack(name, opts = {}) { const slideElements = [{ tag: 'section' }]; const theme = this.get(name, true); const inlineSVGOpts = opts.inlineSVG || {}; if (inlineSVGOpts.enabled) { slideElements.unshift({ tag: 'svg' }, { tag: 'foreignObject' }); } const runPostCSS = (css, plugins) => (0, _postcss.default)([this.cssNesting && (0, _nesting.default)(), ...plugins].filter(p => p)).process(css).css; const additionalCSS = css => { if (!css) return undefined; try { return runPostCSS(css, [(0, _suppress.default)(this)]); } catch { return undefined; } }; const after = additionalCSS(opts.after); const before = additionalCSS(opts.before); const containerName = typeof opts.containerQuery === 'string' || Array.isArray(opts.containerQuery) ? opts.containerQuery : undefined; return runPostCSS(theme.css, [before && (0, _postcss_plugin.default)('marpit-pack-before', () => css => css.first.before(before)), after && (0, _postcss_plugin.default)('marpit-pack-after', () => css => { css.last.after(after); }), opts.containerQuery && (0, _container_query.default)(containerName), _hoisting.default, (0, _replace.default)(this), opts.printable && (0, _printable.default)({ width: this.getThemeProp(theme, 'width'), height: this.getThemeProp(theme, 'height') }), theme !== _scaffold.default && (0, _postcss_plugin.default)('marpit-pack-scaffold', () => css => css.first.before(_scaffold.default.css)), inlineSVGOpts.enabled && _advanced_background.default, inlineSVGOpts.enabled && inlineSVGOpts.backdropSelector && _svg_backdrop.default, _pagination.default, (0, _replace3.default)({ pseudoClass: _increasing_specificity.pseudoClass }), _font_size.default, _prepend.default, (0, _replace2.default)(opts.containers, slideElements), _increasing_specificity.default, opts.printable && _printable.postprocess, opts.containerQuery && _container_query.postprocess, _rem.default, _hoisting.default]); } /** * Returns a `Iterator` object that contains registered themes to current * instance. * * @returns {Iterator.} */ themes() { return this.themeMap.values(); } /** * Resolves `@import` and `@import-theme` and returns an array of using theme * instances. * * @private * @param {Theme} theme Theme instance * @returns {Theme[]} */ resolveImport(theme, importedThemes = []) { const { name } = theme; if (importedThemes.includes(name)) throw new Error(`Circular "${name}" theme import is detected.`); const resolvedThemes = [theme]; theme.importRules.forEach(m => { const importTheme = this.get(m.value); if (importTheme) resolvedThemes.push(...this.resolveImport(importTheme, [...importedThemes, name].filter(n => n))); }); return resolvedThemes.filter(v => v); } } var _default = exports.default = ThemeSet;