You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
2.9 KiB
TypeScript
132 lines
2.9 KiB
TypeScript
import { readdir, mkdir, stat, readFile, writeFile } from "node:fs/promises";
|
|
import { HTMLElement, parse, NodeType } from "node-html-parser";
|
|
|
|
import { render as renderNow } from "./now";
|
|
import { render as renderHomeQuote } from "./quote";
|
|
import { render as renderLogs } from "./logs";
|
|
import { render as renderWork, renderResume } from "./work";
|
|
|
|
const walkdir = async (path: string) => {
|
|
const paths = await readdir(path);
|
|
const out = [] as string[];
|
|
|
|
for (const p of paths) {
|
|
const p_ = `${path}/${p}`;
|
|
const st = await stat(p_);
|
|
if (st.isDirectory()) {
|
|
// TODO: do something special
|
|
continue;
|
|
}
|
|
|
|
out.push(p_);
|
|
}
|
|
|
|
return out;
|
|
};
|
|
|
|
const RENDERERS = {
|
|
"x-meta": () => "",
|
|
"x-now": renderNow,
|
|
"x-logs": renderLogs,
|
|
"x-home-quote": renderHomeQuote,
|
|
"x-work": renderWork,
|
|
};
|
|
const renderElement = (el: HTMLElement) => {
|
|
if (el.nodeType == NodeType.TEXT_NODE) {
|
|
return el.rawText;
|
|
}
|
|
|
|
const renderer = RENDERERS[el.tagName.toLowerCase()] ?? renderBasic;
|
|
|
|
return renderer(el);
|
|
};
|
|
|
|
const renderBasic = (el: HTMLElement) => {
|
|
const tag = el.tagName.toLowerCase();
|
|
|
|
let out = `<${tag}`;
|
|
for (const attr in el.attributes) {
|
|
out += ` ${attr}="${el.attributes[attr]}"`;
|
|
}
|
|
out += ">";
|
|
|
|
const children = el.childNodes.map(renderElement).join("");
|
|
out += children;
|
|
out += `</${tag}>`;
|
|
|
|
return out;
|
|
};
|
|
|
|
const buildPage = async (pagePath: string) => {
|
|
const contents = (await readFile(pagePath)).toString();
|
|
const parsed = parse(contents);
|
|
|
|
const pageOpts = (() => {
|
|
const el = parsed.querySelector("x-meta");
|
|
if (!el) {
|
|
return {
|
|
title: null,
|
|
};
|
|
}
|
|
|
|
const title = el.querySelector("title");
|
|
|
|
return {
|
|
title: title?.innerText ?? null,
|
|
};
|
|
})();
|
|
|
|
const els = parsed.childNodes.map(renderElement).join("");
|
|
const rendered = `
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta charset="utf-8" />
|
|
<link rel="stylesheet" href="/style.css" />
|
|
<script src="/bundle.js"></script>
|
|
${
|
|
pageOpts.title
|
|
? `<title>${pageOpts.title} - idylls.net</title>`
|
|
: "<title>idylls.net</title>"
|
|
}
|
|
</head>
|
|
<body>
|
|
<nav>
|
|
<ul>
|
|
<li><a href="/index.html">Home</a></li>
|
|
<li><a href="/about.html">About</a></li>
|
|
<li><a href="/about.html#contact">Contact</a></li>
|
|
<li><a href="/work.html">Work</a></li>
|
|
<li><a href="https://git.idylls.net">Git</a></li>
|
|
<li><a href="https://status.idylls.net">Services</a></li>
|
|
<li><a href="/nav.html">More</a></li>
|
|
</ul>
|
|
</nav>
|
|
<main>
|
|
${els}
|
|
</main>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
const [, p] = pagePath.split("content/pages/");
|
|
const outPath = `build/${p}`;
|
|
|
|
return writeFile(outPath, rendered);
|
|
};
|
|
|
|
export const buildPages = async () => {
|
|
await mkdir("build/", { recursive: true });
|
|
|
|
const paths = await walkdir("content/pages");
|
|
|
|
for (const p of paths) {
|
|
await buildPage(p);
|
|
}
|
|
|
|
const resumeContent = renderResume();
|
|
|
|
await writeFile("build/resume.html", resumeContent);
|
|
};
|