From 19c2515437b151fe8f436ddebfce866db0e10d85 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Fri, 1 May 2026 09:31:04 +0800 Subject: [PATCH] feat: add content loading utilities for updates --- lib/updates.ts | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 lib/updates.ts diff --git a/lib/updates.ts b/lib/updates.ts new file mode 100644 index 0000000..e790df6 --- /dev/null +++ b/lib/updates.ts @@ -0,0 +1,107 @@ +import fs from "fs" +import path from "path" +import matter from "gray-matter" +import { remark } from "remark" +import remarkHtml from "remark-html" +import rehypeSlug from "rehype-slug" +import rehypeAutolinkHeadings from "rehype-autolink-headings" + +export interface UpdateMeta { + slug: string + title: string + date: string + version: string + summary: string +} + +export interface UpdateHeading { + id: string + text: string + level: number +} + +export interface UpdateContent { + meta: UpdateMeta + html: string + headings: UpdateHeading[] +} + +const UPDATES_DIR = path.join(process.cwd(), "content", "updates") + +function getSlugs(): string[] { + if (!fs.existsSync(UPDATES_DIR)) return [] + return fs + .readdirSync(UPDATES_DIR) + .filter((file) => file.endsWith(".md")) + .map((file) => file.replace(/\.md$/, "")) +} + +export function getAllUpdates(): UpdateMeta[] { + const slugs = getSlugs() + const updates = slugs.map((slug) => { + const { meta } = getUpdateMeta(slug) + return meta + }) + // Sort by date descending (newest first) + return updates.sort( + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), + ) +} + +function getUpdateMeta(slug: string): { meta: UpdateMeta } { + const filePath = path.join(UPDATES_DIR, `${slug}.md`) + const fileContents = fs.readFileSync(filePath, "utf8") + const { data } = matter(fileContents) + + return { + meta: { + slug, + title: data.title ?? "", + date: data.date ?? "", + version: data.version ?? "", + summary: data.summary ?? "", + }, + } +} + +export async function getUpdateBySlug(slug: string): Promise { + const filePath = path.join(UPDATES_DIR, `${slug}.md`) + const fileContents = fs.readFileSync(filePath, "utf8") + const { data, content } = matter(fileContents) + + const processedContent = await remark() + .use(remarkHtml, { sanitize: false }) + .use(rehypeSlug) + .use(rehypeAutolinkHeadings) + .process(content) + + const html = processedContent.toString() + + // Extract headings from the rendered HTML + const headingRegex = /]*id=["']([^"']+)["'][^>]*>(.*?)<\/h[2-3]>/g + const headings: UpdateHeading[] = [] + let match: RegExpExecArray | null + while ((match = headingRegex.exec(html)) !== null) { + headings.push({ + level: parseInt(match[1], 10), + text: match[3].replace(/<[^>]*>/g, ""), // Strip any inner HTML tags + id: match[2], + }) + } + + return { + meta: { + slug, + title: data.title ?? "", + date: data.date ?? "", + version: data.version ?? "", + summary: data.summary ?? "", + }, + html, + headings, + } +} + +export function getAllUpdateSlugs(): string[] { + return getSlugs() +} \ No newline at end of file