diff --git a/js/reading-list.js b/js/reading-list.js deleted file mode 100644 index 7c1657f..0000000 --- a/js/reading-list.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Reading list renderer — turns the curated reading list data file into - * category-grouped HTML. Pure string builder (no DOM), so it can be unit - * tested in Node; the browser wiring at the bottom is guarded accordingly. - */ -import { READING_LIST } from "../data/reading-list.js"; - -/** HTML-escape a value so it is safe to embed in markup. */ -export function escapeHtml(value) { - return String(value) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); -} - -/** True when the value is an absolute http(s) URL. */ -export function isValidUrl(value) { - try { - const url = new URL(String(value)); - return url.protocol === "http:" || url.protocol === "https:"; - } catch { - return false; - } -} - -/** True when the entry has everything needed to render. */ -export function isValidEntry(entry) { - return Boolean( - entry && - typeof entry.title === "string" && - entry.title.length > 0 && - isValidUrl(entry.url) - ); -} - -/** - * Group valid entries by category, preserving first-seen category order and - * entry order within each category. Entries without a category fall under - * "Uncategorized". Invalid entries are skipped. - */ -export function groupByCategory(entries) { - const groups = new Map(); - for (const entry of entries) { - if (!isValidEntry(entry)) continue; - const category = entry.category || "Uncategorized"; - if (!groups.has(category)) groups.set(category, []); - groups.get(category).push(entry); - } - return groups; -} - -/** Slugify a category name into a stable, unique HTML id. */ -function slugify(value) { - return value - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+|-+$/g, ""); -} - -/** Render one entry: a title link plus an optional one-line note. */ -function renderEntry(entry) { - const note = entry.note - ? `
${escapeHtml(entry.note)}
` - : ""; - // Every reading link points at an external http(s) URL (isValidUrl), so it - // carries rel="noopener noreferrer" as a hardening best practice. - return ( - `The reading list is empty.
'; - } - - const usedIds = new Set(); - const sections = []; - for (const [category, items] of groups) { - let id = slugify(category) || "category"; - while (usedIds.has(id)) id = `${id}-${usedIds.size + 1}`; - usedIds.add(id); - - sections.push( - `