gmi-web/to-html.js

105 lines
3.3 KiB
JavaScript
Raw Normal View History

import fs from "fs";
import path from "path";
import escape from "escape-html";
2021-01-30 16:32:44 +00:00
2021-01-28 23:07:57 +00:00
const TOKENS_EXT = /\.tokens\.json$/;
2021-01-29 00:24:22 +00:00
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats
export const IMG_EXT = /\.(apng|avif|gif|jpg|jpeg|jfif|pjpeg|pjp|png|svg|webp)$/;
export const AUDIO_EXT = /\.(mp3|wav|aac|aacp|mpeg|off|flac)$/;
export const VIDEO_EXT = /\.(mp4|webm)$/;
export const 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>";
export const GMI_CSS = fs.readFileSync(
path.resolve(path.dirname(new URL(import.meta.url).pathname), "./gmi.min.css"),
2021-02-02 22:02:09 +00:00
"utf8"
);
2021-01-29 00:24:22 +00:00
export default (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);
};
export function toHTML (file, options) {
2021-01-29 23:17:25 +00:00
const tokens = JSON.parse(file.contents.toString("utf8"));
const title = tokens[0].h1;
return `<!DOCTYPE html>
<html lang="${options.language}" style="${options.override}">
2021-02-02 22:02:09 +00:00
${head(file.path, title, options)}<body>
2021-01-29 23:17:25 +00:00
${gemtext(tokens, options)}
2021-01-29 22:32:30 +00:00
</body>
</html>
2021-01-29 23:17:25 +00:00
`;
2021-01-29 22:32:30 +00:00
};
2021-01-29 00:24:22 +00:00
export function head(file, title, options) {
2021-01-29 22:32:30 +00:00
return `<head>
2021-01-28 23:07:57 +00:00
<meta charset="utf-8">
2021-01-29 23:17:25 +00:00
<meta language="${options.language}">
2021-01-28 23:13:02 +00:00
<meta name="viewport" content="width=device-width,initial-scale=1">${
2021-01-29 22:32:30 +00:00
!options.css
? BASE_STYLE
2021-02-02 22:02:09 +00:00
: `\n<meta name="color-scheme" content="dark light">\n<style>${GMI_CSS}</style>`
2021-01-29 23:17:25 +00:00
}${
!options.canonical
? ""
: `<link rel="canonical" href="${options.canonical}}">`
2021-01-28 23:13:02 +00:00
}
2021-01-29 23:17:25 +00:00
<title>${title}</title>
2021-01-29 22:32:30 +00:00
</head>
`;
}
2021-01-28 23:07:57 +00:00
export function gemtext(tokens, options) {
2021-01-28 23:13:02 +00:00
let body = [];
2021-01-28 23:07:57 +00:00
2021-01-28 23:13:02 +00:00
let cursor = tokens.shift();
2021-01-28 23:07:57 +00:00
while (tokens.length) {
if (cursor.pre) {
2021-02-02 22:02:09 +00:00
body.push(`<pre${cursor.alt ? ` title="${cursor.alt}"` : ""}>`);
2021-01-28 23:13:02 +00:00
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);
2021-01-30 21:28:30 +00:00
} else if (cursor.li) {
2021-01-28 23:13:02 +00:00
body.push(`<ul>`);
const closing = tokens.findIndex((token) => !token.li);
2021-02-02 22:02:09 +00:00
body = body
.concat([line(cursor)])
.concat(tokens.slice(0, closing).map(line));
2021-01-28 23:13:02 +00:00
body.push("</ul>");
2021-02-02 22:02:09 +00:00
tokens = tokens.slice(closing);
} else {
body.push(line(cursor, options));
2021-01-28 23:07:57 +00:00
}
2021-01-28 23:13:02 +00:00
cursor = tokens.shift();
2021-01-28 23:07:57 +00:00
}
2021-01-28 23:13:02 +00:00
return body.join("\n");
2021-01-28 23:07:57 +00:00
}
2021-01-29 00:24:22 +00:00
function line(
{ text, href, title, pre, alt, h1, h2, h3, li, quote },
2021-02-02 22:02:09 +00:00
{ images, audio, video } = {}
2021-01-29 00:24:22 +00:00
) {
if (text) return `<p>${escape(text)}</p>`;
2021-01-29 00:24:22 +00:00
if (href) {
const titleProp = title ? ` title="${title}"` : "";
if (images && IMG_EXT.test(href)) return `<img src="${href}"${titleProp}/>`;
2021-01-29 23:17:25 +00:00
if (audio && AUDIO_EXT.test(href))
2021-02-02 22:34:26 +00:00
return `<audio controls src="${href}"${titleProp}></audio>`;
2021-01-29 23:17:25 +00:00
if (video && VIDEO_EXT.test(href))
2021-02-02 22:34:26 +00:00
return `<video controls src="${href}"${titleProp}/></video>`;
2021-01-29 00:24:22 +00:00
2021-01-30 21:28:30 +00:00
return `<a href="${href}">${title ? escape(title) : href}</a>`;
2021-01-29 00:24:22 +00:00
}
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>`;
2021-01-28 23:07:57 +00:00
return `<p><br></p>`;
}