UNPKG

impress.me

Version:

Create impress.js presentations from markdown documents with style

214 lines (213 loc) 8.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderTemplate = exports.mergeJs = exports.mergeCss = exports.insertCssVars = exports.extractUri = exports.urlToDataUri = exports.fileToDataUri = exports.toDataUri = exports.contentTypeOf = exports.includeSlide = exports.flattenNodes = exports.findIndex = exports.parseInput = exports.parseYamlConfig = exports.splitConfigAndContent = exports.findRoot = exports.resolvePath = exports.toOutputFilename = exports.logEnd = exports.logStep = exports.log = exports.logInit = exports.attrItemPattern = exports.attrPattern = exports.excludeSlideClasses = void 0; const path = require("path"); const fs = require("fs"); const fs_1 = require("fs"); const CleanCss = require("clean-css"); const pug_1 = require("pug"); const loglevel_1 = require("loglevel"); const uglify_es_1 = require("uglify-es"); const sass = require("sass"); const shape_1 = require("./shape"); const sync_request_1 = require("sync-request"); const jsyaml = require("js-yaml"); exports.excludeSlideClasses = ['title', 'overview', 'background', 'end']; exports.attrPattern = /(.*\S)\s*(\[]\(|<a href=")([^"]*)(\)|"><\/a>)\s*/; exports.attrItemPattern = /([^=,]+)\s*=\s*([^=,]+)/g; const modulePath = path.dirname(__dirname); let startTimestamp = new Date().getTime(); let timestamp = startTimestamp; exports.logInit = () => { startTimestamp = new Date().getTime(); timestamp = startTimestamp; }; exports.log = (message) => { const now = new Date().getTime(); loglevel_1.debug(message + ' - took ' + (now - timestamp) + 'ms'); timestamp = now; }; exports.logStep = (message) => input => { exports.log(message); return input; }; exports.logEnd = (message) => () => { const now = new Date().getTime(); loglevel_1.debug(message + ' took ' + (now - startTimestamp) + 'ms'); }; exports.toOutputFilename = (input) => { return `${input.replace(/\.[^/.]+$/, '')}.html`; }; const pathPrefixes = [ modulePath, '', 'node_modules', '..', path.join(__dirname, '..', 'node_modules'), ]; exports.resolvePath = (p, prefixes = []) => { var _a; return (_a = [...prefixes, ...pathPrefixes].map(prefix => path.join(prefix, p)).find(fs.existsSync)) !== null && _a !== void 0 ? _a : p; }; exports.findRoot = (node) => { if (node.parent === undefined) { // already root return node; } if (node.parent.parent === undefined) { return node.parent; } return node.parent.parent; }; const configPattern = /^(---+\n+((.|[\r\n])*?)\n---+\n)/m; exports.splitConfigAndContent = (content) => { const match = content.match(configPattern); if (match) { return [match[2], content.substring(match[1].length)]; } return ['', content]; }; exports.parseYamlConfig = (yaml) => { if (yaml === undefined || yaml.trim() === '') { return undefined; } const parsed = jsyaml.load(yaml); if (!parsed || typeof parsed !== 'object') { return undefined; } return Object.assign(Object.assign({}, parsed), { hasInlineConfig: true }); }; exports.parseInput = (file) => fs_1.promises.readFile(file, 'utf8') .then(exports.logStep(`Markdown file "${file}" read`)) .then(exports.splitConfigAndContent) .then(([yaml, content]) => [content, exports.parseYamlConfig(yaml)]); exports.findIndex = (root, node) => { if (root === node) { return 0; } let index = 0; for (const child of root.children) { index += 1; if (child === node) { return index; } for (const child2 of child.children) { index += 1; if (child2 === node) { return index; } } } return -1; }; exports.flattenNodes = (node) => { return node.children.reduce((acc, child) => [...acc, ...exports.flattenNodes(child)], [node]); }; exports.includeSlide = (node) => exports.excludeSlideClasses.find(cls => (node.classes || []).includes(cls)) === undefined; exports.contentTypeOf = (data) => { if (data.startsWith('<svg') || data.startsWith('<?xml')) { return 'image/svg+xml'; } return 'image/png'; }; exports.toDataUri = (data) => { const contentType = exports.contentTypeOf(data.toString()); return `data:${contentType};base64,${data.toString('base64')}`; }; exports.fileToDataUri = (file) => { const data = fs_1.readFileSync(file); return exports.toDataUri(data); }; /** * This is a blocking operation, and will not scale well with large amounts of downloads! * @param {string} url - the URL to send a GET request to * @return {string} the response body from the request to the given URL */ exports.urlToDataUri = (url) => { const data = sync_request_1.default('GET', url, {}).body; if (typeof data === 'string') { return exports.toDataUri(Buffer.from(data, 'utf8')); } return exports.toDataUri(data); }; exports.extractUri = (uri) => uri.replace(/^\s*(url\s*\(\s*)?['"]?([^\s'")]+)['"]?(\s*\))?\s*$/, '$2'); const cssPropertyValueToDataUri = (propertyName, propertyValue, includePaths) => { if (propertyName === 'background-image') { if (!propertyValue.startsWith('data:') && !propertyValue.startsWith('url(data:') && !propertyValue.startsWith('http:') && !propertyValue.startsWith('url(http:') && !propertyValue.startsWith('https:') && !propertyValue.startsWith('url(https:')) { const uri = exports.extractUri(propertyValue); const filename = includePaths.map(includePath => path.relative('.', `${includePath}/${uri}`)) .find(file => fs.existsSync(file)); if (filename !== undefined) { try { return 'url(' + exports.fileToDataUri(filename) + ')'; } catch (error) { console.warn(`Error while inlining ${filename}: ${error.message}`); } } } } return propertyValue; }; const cssMinify = (data, includePaths) => { return new CleanCss({ level: { 1: { transform: (name, value) => cssPropertyValueToDataUri(name, value, includePaths), }, }, }).minify(data).styles; }; const shapeCssVars = shape_1.shapes.map(shape => [ ['shape-' + shape.toLowerCase() + '-width', () => `${shape_1.shapeConfig[shape].width}px`], ['shape-' + shape.toLowerCase() + '-height', () => `${shape_1.shapeConfig[shape].height}px`], ['shape-' + shape.toLowerCase() + '-offset-x', () => `${shape_1.shapeConfig[shape].offset.x}px`], ['shape-' + shape.toLowerCase() + '-offset-y', () => `${shape_1.shapeConfig[shape].offset.y}px`], ]) .reduce((arr, curr) => [...arr, ...curr], []); const cssVars = [ ['transition-duration', config => `${config.transitionDuration}ms`], ...shapeCssVars, ]; exports.insertCssVars = (config) => (css) => cssVars.map(([name, fn]) => `$${name}: ${fn(config)}`) .concat([css]) .join(';'); const sassRender = (data, includePaths) => { return new Promise((resolve, reject) => { try { const result = sass.renderSync({ data, outputStyle: 'compressed', includePaths: includePaths.map(path => exports.resolvePath(path)), }); return resolve(result.css.toString('utf8')); } catch (error) { console.log('Error while rendering stylesheet: ' + error.message, includePaths); return reject(error); } }); }; const distinct = (value, index, self) => { return self.indexOf(value) === index; }; const extractFilePaths = (files) => { return files.map(filename => path.dirname(filename)).filter(distinct); }; exports.mergeCss = (cssFiles, preProcess) => { const includePaths = extractFilePaths(cssFiles); return Promise.all(cssFiles.map(filename => fs_1.promises.readFile(filename, { encoding: 'utf8' }))) .then(outputs => outputs.join('\n')) .then(preProcess) .then(output => sassRender(output, includePaths)) .then((data) => cssMinify(data, includePaths)); }; exports.mergeJs = (jsFiles) => Promise.all(jsFiles.map(file => fs_1.promises.readFile(file, 'utf8') .then(exports.logStep(`JavaScript file read: "${file}"`)) .then((data) => uglify_es_1.minify(data).code) .then(exports.logStep(`JavaScript file minified: "${file}"`)))) .then(outputs => outputs.join(';')); // eslint-disable-next-line max-params exports.renderTemplate = (template, html, js, css, config) => { return pug_1.compileFile(exports.resolvePath(template))(Object.assign(Object.assign({ title: 'Impress.me Slides' }, config), { js, css, marked: html })); };