86e9e8b660
add test
112 lines
3.4 KiB
JavaScript
112 lines
3.4 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
const escape = require("escape-html");
|
|
|
|
const TOKENS_EXT = /\.tokens\.json$/;
|
|
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats
|
|
const IMG_EXT = (exports.IMG_EXT = /\.(apng|avif|gif|jpg|jpeg|jfif|pjpeg|pjp|png|svg|webp)$/);
|
|
const AUDIO_EXT = (exports.AUDIO_EXT = /\.(mp3|wav|aac|aacp|mpeg|off|flac)$/);
|
|
const VIDEO_EXT = (exports.VIDEO_EXT = /\.(mp4|webm)$/);
|
|
const BASE_STYLE = (exports.BASE_STYLE =
|
|
"<style>p,a,pre,h1,h2,h3,ul,blockquote,img,audio,video{display:block;max-width:100%;margin:0;padding:0;overflow-wrap:anywhere;}</style>");
|
|
const GMI_CSS = fs.readFileSync(
|
|
path.resolve(__dirname, "./gmi.min.css"),
|
|
"utf8"
|
|
);
|
|
|
|
const toHTML = (exports.toHTML = (file, options) => {
|
|
const tokens = JSON.parse(file.contents.toString("utf8"));
|
|
const title = tokens[0].h1;
|
|
return `<!DOCTYPE html>
|
|
<html style="${options.override}">
|
|
${head(file.path, title, options)}<body>
|
|
${gemtext(tokens, options)}
|
|
</body>
|
|
</html>
|
|
`;
|
|
});
|
|
|
|
module.exports = (options) => (file, cb) => {
|
|
if (!TOKENS_EXT.test(file.path)) return cb(null);
|
|
file.contents = Buffer.from(toHTML(file, options));
|
|
file.path = file.path.replace(TOKENS_EXT, ".html");
|
|
if (options.verbose) console.log(file.path);
|
|
return cb(null, file);
|
|
};
|
|
|
|
function head(file, title, options) {
|
|
return `<head>
|
|
<meta charset="utf-8">
|
|
<meta language="${options.language}">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">${
|
|
!options.css
|
|
? BASE_STYLE
|
|
: `\n<meta name="color-scheme" content="dark light">\n<style>${GMI_CSS}</style>`
|
|
}${
|
|
!options.canonical
|
|
? ""
|
|
: `<link rel="canonical" href="gemini://${
|
|
options.canonical
|
|
}/${file.replace(TOKENS_EXT, ".gmi")}">`
|
|
}
|
|
<title>${title}</title>
|
|
</head>
|
|
`;
|
|
}
|
|
|
|
function gemtext(tokens, options) {
|
|
let body = [];
|
|
|
|
let cursor = tokens.shift();
|
|
while (tokens.length) {
|
|
if (cursor.pre) {
|
|
body.push(`<pre${cursor.alt ? ` title="${cursor.alt}"` : ""}>`);
|
|
const closing = tokens.findIndex((token) => token.pre);
|
|
body = body.concat(tokens.slice(0, closing).map(({ text }) => text));
|
|
body.push("</pre>");
|
|
tokens = tokens.slice(closing + 1);
|
|
} else if (cursor.li) {
|
|
body.push(`<ul>`);
|
|
const closing = tokens.findIndex((token) => !token.li);
|
|
body = body
|
|
.concat([line(cursor)])
|
|
.concat(tokens.slice(0, closing).map(line));
|
|
body.push("</ul>");
|
|
tokens = tokens.slice(closing);
|
|
} else {
|
|
body.push(line(cursor, options));
|
|
}
|
|
cursor = tokens.shift();
|
|
}
|
|
|
|
return body.join("\n");
|
|
}
|
|
|
|
function line(
|
|
{ text, href, title, pre, alt, h1, h2, h3, li, quote },
|
|
{ images, audio, video } = {}
|
|
) {
|
|
if (text) return `<p>${escape(text)}</p>`;
|
|
if (href) {
|
|
if (images && IMG_EXT.test(href))
|
|
return `<img src="${href}" title="${title}"/>`;
|
|
if (audio && AUDIO_EXT.test(href))
|
|
return `<audio controls src="${href}" title="${title}"></audio>`;
|
|
if (video && VIDEO_EXT.test(href))
|
|
return `<video controls src="${href}" title="${title}"/></video>`;
|
|
|
|
return `<a href="${href}">${title ? escape(title) : href}</a>`;
|
|
}
|
|
if (h1) return `<h1>${escape(h1)}</h1>`;
|
|
if (h2) return `<h2>${escape(h2)}</h2>`;
|
|
if (h3) return `<h3>${escape(h3)}</h3>`;
|
|
if (li) return `<li>${escape(li)}</li>`;
|
|
if (quote) return `<blockquote>${escape(quote)}</blockquote>`;
|
|
return `<p><br></p>`;
|
|
}
|
|
|
|
function omit(key, obj) {
|
|
const { [key]: omitted, ...rest } = obj;
|
|
return rest;
|
|
}
|