2021-02-10 22:03:48 +00:00
|
|
|
import fs from "fs";
|
|
|
|
import path from "path";
|
|
|
|
import escape from "escape-html";
|
2021-01-30 16:32:44 +00:00
|
|
|
|
2021-02-11 02:24:00 +00:00
|
|
|
export const GMI_REGEX = /^((=>\s?(?<href>[^\s]+)(\s(?<title>.+))?)|(?<pre>```\s?(?<alt>.+)?)|(###\s?(?<h3>.+))|(##\s?(?<h2>.+))|(#\s?(?<h1>.+))|(\*\s?(?<li>.+))|(>\s?(?<quote>.+))|(?<text>(.+)?))$/;
|
2021-01-29 00:24:22 +00:00
|
|
|
|
2021-02-11 02:24:00 +00:00
|
|
|
export function html(file, options) {
|
|
|
|
const tokens = file.contents
|
|
|
|
.toString("utf8")
|
|
|
|
.split("\n")
|
|
|
|
.map((line) => GMI_REGEX.exec(line).groups);
|
|
|
|
|
|
|
|
if (options.body) return body(tokens, options);
|
2021-02-10 22:03:48 +00:00
|
|
|
|
2021-01-29 23:17:25 +00:00
|
|
|
return `<!DOCTYPE html>
|
2021-02-11 02:24:00 +00:00
|
|
|
<html lang="${options.language}" style="${options.styles}">
|
|
|
|
<head>${head(
|
|
|
|
Object.assign(options, {
|
|
|
|
title: tokens[0].h1,
|
|
|
|
charset: "utf-8",
|
|
|
|
})
|
|
|
|
)}</head>
|
|
|
|
<body>${body(tokens, options)}</body>
|
2021-01-29 22:32:30 +00:00
|
|
|
</html>
|
2021-01-29 23:17:25 +00:00
|
|
|
`;
|
2021-02-11 02:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const BASE_CSS =
|
|
|
|
"p,a,pre,h1,h2,h3,ul,blockquote,img,audio,video{display:block;max-width:100%;margin:0;padding:0;overflow-wrap:anywhere;}";
|
|
|
|
|
|
|
|
export const GMI_CSS = fs.readFileSync(
|
|
|
|
// TODO import.meta.resolve is supposed to accomplish this without "path"
|
|
|
|
path.resolve(
|
|
|
|
path.dirname(new URL(import.meta.url).pathname),
|
|
|
|
"./gmi.min.css"
|
|
|
|
),
|
|
|
|
"utf8"
|
|
|
|
);
|
2021-01-29 00:24:22 +00:00
|
|
|
|
2021-02-11 02:24:00 +00:00
|
|
|
export function head(options) {
|
|
|
|
return `
|
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
|
|
<meta charset="${options.charset}">
|
2021-01-29 23:17:25 +00:00
|
|
|
<meta language="${options.language}">
|
2021-02-11 02:24:00 +00:00
|
|
|
<title>${options.title}</title>${
|
|
|
|
!options.description
|
|
|
|
? ""
|
|
|
|
: `<meta name="description" content="${options.description}}">`
|
|
|
|
}${
|
2021-01-29 22:32:30 +00:00
|
|
|
!options.css
|
2021-02-11 02:24:00 +00:00
|
|
|
? `<style>${BASE_CSS}</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
|
|
|
|
? ""
|
2021-02-10 22:03:48 +00:00
|
|
|
: `<link rel="canonical" href="${options.canonical}}">`
|
2021-01-28 23:13:02 +00:00
|
|
|
}
|
2021-01-29 22:32:30 +00:00
|
|
|
`;
|
|
|
|
}
|
2021-01-28 23:07:57 +00:00
|
|
|
|
2021-02-11 02:24:00 +00:00
|
|
|
export function body(tokens, options) {
|
|
|
|
let lines = [];
|
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-11 02:24:00 +00:00
|
|
|
lines.push(`<pre${cursor.alt ? ` title="${cursor.alt}"` : ""}>`);
|
2021-01-28 23:13:02 +00:00
|
|
|
const closing = tokens.findIndex((token) => token.pre);
|
2021-02-11 02:24:00 +00:00
|
|
|
lines = lines.concat(tokens.slice(0, closing).map(({ text }) => text));
|
|
|
|
lines.push("</pre>");
|
2021-01-28 23:13:02 +00:00
|
|
|
tokens = tokens.slice(closing + 1);
|
2021-01-30 21:28:30 +00:00
|
|
|
} else if (cursor.li) {
|
2021-02-11 02:24:00 +00:00
|
|
|
lines.push(`<ul>`);
|
2021-01-28 23:13:02 +00:00
|
|
|
const closing = tokens.findIndex((token) => !token.li);
|
2021-02-11 02:24:00 +00:00
|
|
|
lines = lines
|
2021-02-02 22:02:09 +00:00
|
|
|
.concat([line(cursor)])
|
|
|
|
.concat(tokens.slice(0, closing).map(line));
|
2021-02-11 02:24:00 +00:00
|
|
|
lines.push("</ul>");
|
2021-02-02 22:02:09 +00:00
|
|
|
tokens = tokens.slice(closing);
|
2021-01-30 18:47:46 +00:00
|
|
|
} else {
|
2021-02-11 02:24:00 +00:00
|
|
|
lines.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-02-11 02:24:00 +00:00
|
|
|
return lines.join("\n");
|
2021-01-28 23:07:57 +00:00
|
|
|
}
|
|
|
|
|
2021-02-11 02:24:00 +00:00
|
|
|
export const IMAGE_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)$/;
|
|
|
|
|
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
|
|
|
) {
|
2021-01-30 18:47:46 +00:00
|
|
|
if (text) return `<p>${escape(text)}</p>`;
|
2021-01-29 00:24:22 +00:00
|
|
|
if (href) {
|
2021-02-10 22:03:48 +00:00
|
|
|
const titleProp = title ? ` title="${title}"` : "";
|
2021-02-11 02:24:00 +00:00
|
|
|
if (images && IMAGE_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
|
|
|
}
|
2021-01-30 18:47:46 +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>`;
|
|
|
|
}
|
2021-02-11 02:24:00 +00:00
|
|
|
|
|
|
|
export const GMI_EXT = /\.gmi$/;
|
|
|
|
|
|
|
|
export default (options) => (file, cb) => {
|
|
|
|
if (!GMI_EXT.test(file.path)) return cb(null);
|
|
|
|
file.contents = Buffer.from(html(file, options));
|
|
|
|
file.path = file.path.replace(GMI_EXT, ".html");
|
|
|
|
if (options.verbose) console.log(file.path);
|
|
|
|
return cb(null, file);
|
|
|
|
};
|