321 lines
12 KiB
JavaScript
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; |