1
0
Files

321 lines
12 KiB
JavaScript

"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.<Theme>}
*/
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;