This commit is contained in:
commit
aa78b43c34
8 changed files with 192 additions and 0 deletions
129
scripts/build.mjs
Normal file
129
scripts/build.mjs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
|
||||
const blogs = [
|
||||
["research", "research"],
|
||||
["vibes", "vibes"],
|
||||
["puzzles", "puzzles"],
|
||||
["reflections", "reflections"],
|
||||
["poetry", "poetry"],
|
||||
["reviews", "reviews"],
|
||||
["art", "art"],
|
||||
];
|
||||
|
||||
const rssHostSuffix = process.env.RSS_HOST_SUFFIX ?? "neeldhara.email";
|
||||
const outDir = new URL("../dist/", import.meta.url);
|
||||
|
||||
function decodeXml(value) {
|
||||
return value
|
||||
.replace(/'/g, "'")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/>/g, ">")
|
||||
.replace(/</g, "<")
|
||||
.replace(/&/g, "&");
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function textFrom(block, tag) {
|
||||
const match = block.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, "i"));
|
||||
return match ? decodeXml(match[1].trim()) : "";
|
||||
}
|
||||
|
||||
function parseItems(xml) {
|
||||
return [...xml.matchAll(/<item>([\s\S]*?)<\/item>/gi)].slice(0, 2).map((match) => {
|
||||
const block = match[1];
|
||||
const pubDate = new Date(textFrom(block, "pubDate"));
|
||||
|
||||
return {
|
||||
title: textFrom(block, "title"),
|
||||
link: textFrom(block, "link"),
|
||||
date: Number.isNaN(pubDate.valueOf()) ? "" : pubDate.toISOString().slice(0, 10),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getPosts(slug) {
|
||||
const url = `https://${slug}.${rssHostSuffix}/rss.xml`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`${url} returned ${response.status}`);
|
||||
}
|
||||
|
||||
return parseItems(await response.text());
|
||||
}
|
||||
|
||||
function renderPost(post) {
|
||||
const date = post.date ? `${escapeHtml(post.date)} ` : "";
|
||||
return `<li>${date}<a href="${escapeHtml(post.link)}">${escapeHtml(post.title)}</a></li>`;
|
||||
}
|
||||
|
||||
function renderBlog([slug, label], posts) {
|
||||
return `<li>${escapeHtml(label)}
|
||||
<ul>
|
||||
${posts.map(renderPost).join("\n ")}
|
||||
</ul>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
const entries = await Promise.all(
|
||||
blogs.map(async (blog) => [blog, await getPosts(blog[0])]),
|
||||
);
|
||||
|
||||
const html = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>neeldhara.blog</title>
|
||||
<style>
|
||||
html { color-scheme: light; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
color: #111;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
main {
|
||||
width: min(640px, calc(100% - 40px));
|
||||
margin: 86px auto;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 18px;
|
||||
font-size: 28px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-left: 1.45rem;
|
||||
}
|
||||
li { margin: 0.18rem 0; }
|
||||
a { color: #00e; }
|
||||
a:visited { color: #551a8b; }
|
||||
@media (max-width: 640px) {
|
||||
main { margin: 42px auto; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>neeldhara.blog</h1>
|
||||
<ul>
|
||||
${entries.map(([blog, posts]) => renderBlog(blog, posts)).join("\n ")}
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
await mkdir(outDir, { recursive: true });
|
||||
await writeFile(new URL("index.html", outDir), html);
|
||||
Loading…
Add table
Add a link
Reference in a new issue