256 lines
6.9 KiB
JavaScript
Raw Normal View History

2020-02-21 20:05:52 -08:00
const path = require('path');
const fs = require('fs-extra');
const log = require('fancy-log');
2020-03-07 18:04:37 -08:00
const { resolve, readFile, ENGINE, TYPE } = require('./resolve');
2020-02-21 20:05:52 -08:00
2020-03-07 18:04:37 -08:00
const Handlebars = require('handlebars');
2020-02-21 20:05:52 -08:00
const HandlebarsKit = require('hbs-kit');
2020-03-07 18:04:37 -08:00
HandlebarsKit.load(Handlebars);
2020-02-21 20:05:52 -08:00
2020-04-07 10:32:23 -07:00
const slugify = require('./lib/slugify');
2020-02-21 20:05:52 -08:00
const striptags = require('string-strip-html');
const markdownIt = require('markdown-it');
const markdownEngines = {
full: markdownIt({
html: true,
linkify: true,
typographer: true,
})
.enable('image')
.use(require('markdown-it-anchor'), {
permalink: true,
permalinkClass: 'header-link',
permalinkSymbol: '<img src="/images/svg/paragraph.svg">',
slugify,
})
.use(require('./lib/markdown-raw-html'), { debug: false }),
2020-02-21 20:05:52 -08:00
preview: markdownIt({
html: false,
linkify: false,
typographer: true,
})
2020-02-28 10:31:13 -08:00
.use(require('./lib/markdown-token-filter')),
2020-02-21 20:05:52 -08:00
};
function markdown (mode, input, env) {
if (mode === 'preview') {
input = striptags(input
.replace(/<!--\[[\s\S]*?\]-->/g, '')
.replace(/æææ[\s\S]*?æææ/gi, '')
.replace(/\{!\{([\s\S]*?)\}!\}/mg, ''),
2020-02-21 20:05:52 -08:00
).trim();
if (input.length > 1000) input = input.slice(0, 1000) + '…';
2020-02-21 20:05:52 -08:00
} else {
input = input.replace(/\{!\{([\s\S]*?)\}!\}/mg, (match, contents) => {
try {
const result = Handlebars.compile(contents)(env);
return 'æææ' + result + 'æææ';
} catch (e) {
log.error(e);
return '';
}
});
2020-02-21 20:05:52 -08:00
input = input.replace(/<!--[[\]]-->/g, '');
}
2020-03-15 16:49:03 -07:00
try {
return input ? markdownEngines[mode].render(input, env) : '';
} catch (e) {
log(input);
throw e;
}
2020-02-21 20:05:52 -08:00
}
2020-03-07 18:04:37 -08:00
function handlebars (input, env) {
const template = Handlebars.compile(input);
return template(env);
}
2020-02-21 20:05:52 -08:00
function stripIndent (input) {
const match = input.match(/^[^\S\n]*(?=\S)/gm);
const indent = match && Math.min(...match.map((el) => el.length));
if (indent) {
const regexp = new RegExp(`^.{${indent}}`, 'gm');
input = input.replace(regexp, '');
}
return input;
}
const HANDLEBARS_PARTIALS = {
layout: 'templates/layout.hbs',
list: 'templates/list.hbs',
2020-03-07 18:04:37 -08:00
page: 'templates/page.hbs',
post: 'templates/post.hbs',
2020-02-21 20:05:52 -08:00
};
module.exports = exports = async function (prod) {
2020-03-07 18:04:37 -08:00
const templates = {};
2020-02-21 20:05:52 -08:00
for (const [ name, file ] of Object.entries(HANDLEBARS_PARTIALS)) {
try {
2020-02-25 19:37:10 -08:00
const contents = await readFile(file);
2020-03-07 18:04:37 -08:00
const template = Handlebars.compile(contents.toString('utf8'));
templates[name] = template;
Handlebars.registerPartial(name, template);
2020-02-21 20:05:52 -08:00
} catch (e) {
2020-02-25 19:37:10 -08:00
log.error('Could not execute load partial ' + file, e);
2020-02-21 20:05:52 -08:00
}
}
2020-02-25 19:37:10 -08:00
const revManifest = prod && await fs.readJson(resolve('rev-manifest.json')).catch(() => {}).then((r) => r || {});
2020-02-21 20:05:52 -08:00
const helpers = new Injectables(prod, revManifest);
2020-03-07 18:04:37 -08:00
Handlebars.registerHelper('import', helpers.import());
Handlebars.registerHelper('markdown', helpers.markdown());
Handlebars.registerHelper('icon', helpers.icon());
Handlebars.registerHelper('prod', helpers.production());
Handlebars.registerHelper('rev', helpers.rev());
2020-02-21 20:05:52 -08:00
const result = {
2020-03-07 18:04:37 -08:00
[TYPE.HANDLEBARS]: handlebars,
[TYPE.MARKDOWN]: (source, env) => markdown('full', source, env),
[TYPE.OTHER]: (source) => source,
[ENGINE.LIST]: (source, env) => templates.list({ ...env, contents: markdown('full', source, env) }),
[ENGINE.PAGE]: (source, env) => templates.page({ ...env, contents: markdown('full', source, env) }),
[ENGINE.POST]: (source, env) => templates.post({ ...env, contents: markdown('full', source, env) }),
[ENGINE.HTML]: (source) => source,
2020-03-07 18:04:37 -08:00
[ENGINE.OTHER]: (source) => source,
preview: (source, env) => markdown('preview', source, env),
2020-02-21 20:05:52 -08:00
};
return result;
};
class Injectables {
constructor (prod, revManifest) {
this.prod = prod;
this.revManifest = revManifest;
this.injections = {};
}
_parsePath (tpath, local, type) {
2020-02-25 19:37:10 -08:00
if (tpath[0] === '/') tpath = resolve(tpath.slice(1));
else if (tpath[0] === '~') tpath = resolve('templates', tpath.slice(2));
2020-02-21 20:05:52 -08:00
else tpath = path.resolve(local.cwd, tpath);
if (type && !tpath.endsWith(type)) tpath += '.' + type;
return tpath;
}
_template (tpath, make) {
2020-02-27 18:57:39 -08:00
if (!tpath) throw new Error('Received an empty template path: ' + tpath);
2020-02-21 20:05:52 -08:00
if (this.injections[tpath]) return this.injections[tpath];
if (!fs.existsSync(tpath)) {
2020-02-27 18:57:39 -08:00
throw new Error('Injectable does not exist: ' + tpath);
2020-02-21 20:05:52 -08:00
}
let contents;
try {
contents = fs.readFileSync(tpath).toString('utf8');
if (make) contents = make(contents);
this.injections[tpath] = contents;
return contents;
} catch (e) {
2020-02-25 19:37:10 -08:00
log.error(e, 'An error occured while loading the injectable: ' + tpath);
2020-02-21 20:05:52 -08:00
}
return '';
}
rev () {
const self = this;
return function (url) {
if (!url) return '';
if (url[0] === '/') url = url.substr(1);
if (self.prod && self.revManifest[url]) return '/' + self.revManifest[url];
return '/' + url;
};
}
production () {
const self = this;
return function (options) {
if (!options.fn) return self.prod;
return self.prod ? options.fn(this) : options.inverse(this);
};
}
markdown () {
const self = this;
return function (...args) {
const { fn, data } = args.pop();
2020-02-21 20:05:52 -08:00
let contents;
if (fn) {
2020-03-05 19:40:18 -08:00
contents = stripIndent(fn(data.root));
2020-02-21 20:05:52 -08:00
} else {
let tpath = args.shift();
tpath = self._parsePath(tpath, data.root.local, 'md');
2020-02-21 20:05:52 -08:00
contents = self._template(tpath);
}
contents = markdown('full', contents, data);
2020-02-21 20:05:52 -08:00
2020-03-07 18:04:37 -08:00
return new Handlebars.SafeString(contents);
2020-02-21 20:05:52 -08:00
};
}
import () {
const self = this;
return function (tpath, ...args) {
const { hash, data } = args.pop();
const value = args.shift() || this;
const frame = Handlebars.createFrame(data);
const context = (typeof value === 'object')
? { ...value, ...(hash || {}), _parent: this }
: value;
2020-02-21 20:05:52 -08:00
tpath = self._parsePath(tpath, data.root.local, 'hbs');
2020-02-21 20:05:52 -08:00
try {
const contents = self._template(tpath, Handlebars.compile)(context, { data: frame });
2020-03-07 18:04:37 -08:00
return new Handlebars.SafeString(contents);
2020-02-21 20:05:52 -08:00
} catch (e) {
2020-02-27 18:57:39 -08:00
log.error('Could not execute import template ' + tpath, e);
2020-02-21 20:05:52 -08:00
return '';
}
};
}
icon () {
const self = this;
return function (name, ...args) {
const { hash, data } = args.pop();
const tpath = path.join(data.root.local.root, 'svg', name + '.svg');
2020-02-21 20:05:52 -08:00
try {
const contents = self._template(tpath, (s) =>
2020-03-07 18:04:37 -08:00
Handlebars.compile(`<span class="svg-icon" {{#if size}}style="width:{{size}}px;height:{{size}}px"{{/if}}>${s}</span>`),
2020-02-21 20:05:52 -08:00
)({ size: hash && hash.size });
2020-03-07 18:04:37 -08:00
return new Handlebars.SafeString(contents);
2020-02-21 20:05:52 -08:00
} catch (e) {
2020-02-27 18:57:39 -08:00
log.error('Could not execute import template ' + tpath, e);
2020-02-21 20:05:52 -08:00
return '';
}
};
}
}