impress.me
Version:
Create impress.js presentations from markdown documents with style
214 lines (213 loc) • 8.77 kB
JavaScript
;
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 }));
};