import {
access,
copyFile,
mkdir,
readdir,
readFile,
writeFile,
} from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const blogs = [
{
slug: "research",
label: "Research",
summary: "Notes from papers, algorithms, computational social choice, and mini-surveys of themes I am learning about.",
},
{
slug: "vibes",
label: "Vibes",
summary: "Loose experiments, AI conversations, and work-in-progress thoughts that do not need a formal frame.",
},
{
slug: "puzzles",
label: "Puzzles",
summary: "Problems, teaching prompts, contest-style curiosities, and small mathematical rabbit holes.",
},
{
slug: "reflections",
label: "Reflections",
summary: "Personal notes on teaching, academia, writing, attention, and the rhythms of intellectual work.",
},
{
slug: "poetry",
label: "Poetry",
summary: "Poems, tiny forms, and playful writing around mathematics, code, and ordinary life.",
},
{
slug: "reviews",
label: "Reviews",
summary: "Notes on tools, workflows, books, hardware, and objects that shape everyday work.",
},
{
slug: "art",
label: "Art",
summary: "Algorithmic sketches, generative art, visual experiments, and mathematical images.",
},
];
const socialLinks = [
{
href: "https://x.com/home",
label: "X",
color: "#525252",
svg: ``,
},
{
href: "https://bsky.app/profile/neeldhara.bsky.social",
label: "Bluesky",
color: "#4f83b8",
svg: ``,
},
{
href: "https://www.linkedin.com/in/neeldhara-misra-a54b6920/",
label: "LinkedIn",
color: "#496f9e",
svg: ``,
},
{
href: "https://mathstodon.xyz/@neeldhara",
label: "Mastodon",
color: "#7464a8",
svg: ``,
},
{
href: "https://www.youtube.com/@neeldhara",
label: "YouTube",
color: "#b55252",
svg: ``,
},
{
href: "https://www.instagram.com/neeldharamisra",
label: "Instagram",
color: "#9a6b8c",
svg: ``,
},
];
const publicHostSuffix = process.env.PUBLIC_HOST_SUFFIX ?? "neeldhara.blog";
const allblogsRoot = new URL("../../allblogs/", import.meta.url);
const contentIndex = new URL("../content/blog-index.json", import.meta.url);
const outDir = new URL("../dist/", import.meta.url);
const fontDir = new URL("../webfonts/", import.meta.url);
const shouldWriteContentIndex = process.argv.includes("--write-content-index");
const contentSource = process.env.BLOG_HOME_CONTENT_SOURCE ?? "auto";
const fontFiles = [
"heliotrope_3_regular.woff2",
"heliotrope_3_italic.woff2",
"heliotrope_3_bold.woff2",
"heliotrope_3_bold_italic.woff2",
];
function escapeHtml(value) {
return value
.toString()
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function parseScalar(value) {
const trimmed = value.trim();
if (trimmed === "true") return true;
if (trimmed === "false") return false;
if (
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
(trimmed.startsWith("'") && trimmed.endsWith("'"))
) {
return trimmed.slice(1, -1);
}
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
return trimmed
.slice(1, -1)
.split(",")
.map((item) => parseScalar(item))
.filter(Boolean);
}
return trimmed;
}
function parseFrontmatter(markdown) {
if (!markdown.startsWith("---")) return {};
const end = markdown.indexOf("\n---", 3);
if (end === -1) return {};
const yaml = markdown.slice(3, end).trim();
const data = {};
for (const line of yaml.split(/\r?\n/)) {
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
if (!match) continue;
data[match[1]] = parseScalar(match[2]);
}
return data;
}
async function pathExists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
async function findMarkdownFiles(dir) {
const files = [];
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await findMarkdownFiles(fullPath)));
continue;
}
if (entry.isFile() && /\.(md|mdx)$/i.test(entry.name)) {
files.push(fullPath);
}
}
return files;
}
function slugFromFile(filePath, contentDir) {
const relative = path.relative(contentDir, filePath).replaceAll(path.sep, "/");
return relative.replace(/\/index\.mdx?$/i, "").replace(/\.mdx?$/i, "");
}
async function getLocalPosts(blog) {
const contentDir = fileURLToPath(
new URL(`sites/${blog.slug}/src/content/${blog.slug}/`, allblogsRoot),
);
if (!(await pathExists(contentDir))) {
return null;
}
const files = await findMarkdownFiles(contentDir);
const posts = [];
for (const file of files) {
const data = parseFrontmatter(await readFile(file, "utf8"));
const pubDate = new Date(`${data.pubDate}T00:00:00Z`);
if (!data.title || Number.isNaN(pubDate.valueOf())) {
continue;
}
const slug = slugFromFile(file, contentDir);
posts.push({
title: data.title,
link: `https://${blog.slug}.${publicHostSuffix}/${slug}/`,
date: pubDate.toISOString().slice(0, 10),
featured: data["main-feature"] === true || data.mainFeature === true,
});
}
return posts.sort((a, b) => b.date.localeCompare(a.date));
}
async function getIndexedPosts() {
const index = JSON.parse(await readFile(contentIndex, "utf8"));
return blogs.map((blog) => [blog, index[blog.slug] ?? []]);
}
async function getEntries() {
if (contentSource === "index") {
return getIndexedPosts();
}
const localEntries = await Promise.all(
blogs.map(async (blog) => [blog, await getLocalPosts(blog)]),
);
if (localEntries.every(([, posts]) => posts !== null)) {
return localEntries;
}
return getIndexedPosts();
}
function renderPost(post) {
const date = post.date ? new Date(`${post.date}T00:00:00Z`) : null;
const dateLabel = date
? date.toLocaleDateString("en", { month: "short", day: "numeric", timeZone: "UTC" })
: "";
return `
${escapeHtml(dateLabel)}${escapeHtml(post.title)}`;
}
function renderFeaturedPost(post) {
if (!post) return "";
return `Featured${escapeHtml(post.title)}
`;
}
function renderRssIcon(blog) {
const href = `https://${blog.slug}.${publicHostSuffix}/rss.xml`;
return `
`;
}
function renderSiteIcon(blog) {
const href = `https://${blog.slug}.${publicHostSuffix}/`;
return `
`;
}
function renderSocialLinks() {
return ``;
}
function renderBlog(blog, posts) {
const featured = posts.find((post) => post.featured);
const latest = posts.filter((post) => post !== featured).slice(0, 2);
return `
${escapeHtml(blog.label)}
${renderSiteIcon(blog)}
${renderRssIcon(blog)}
${escapeHtml(blog.summary)}
${renderFeaturedPost(featured)}
${latest.map(renderPost).join("\n ")}
`;
}
const entries = await getEntries();
if (shouldWriteContentIndex) {
await mkdir(new URL("../content/", import.meta.url), { recursive: true });
await writeFile(
contentIndex,
`${JSON.stringify(
Object.fromEntries(entries.map(([blog, posts]) => [blog.slug, posts])),
null,
2,
)}\n`,
);
}
const html = `
neeldhara.blog
${entries.map(([blog, posts]) => renderBlog(blog, posts)).join("\n ")}
`;
await mkdir(outDir, { recursive: true });
await writeFile(new URL("index.html", outDir), html);
await mkdir(new URL("webfonts/", outDir), { recursive: true });
await Promise.all(
fontFiles.map((file) => copyFile(new URL(file, fontDir), new URL(`webfonts/${file}`, outDir))),
);