${escapeHtml(entry.title)}
${escapeHtml(entry.summary || "")}
${renderTags(entry.tags || [], "tag-list")} ${renderLinks(entry, "card-link")}(function() { const CONTENT_INDEX_PATH = "./public/content/index.json"; const escapeHtml = (value) => String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/\"/g, """) .replace(/'/g, "'"); const formatDate = (value) => { if (!value) { return ""; } const date = new Date(`${value}T00:00:00`); if (Number.isNaN(date.getTime())) { return value; } return new Intl.DateTimeFormat("en-CA", { year: "numeric", month: "short", day: "numeric" }).format(date); }; const getIndex = async () => { if (window.location.protocol === "file:") { throw new Error("This content page needs a local web server. Run python3 -m http.server and open http://localhost:8000."); } const response = await fetch(CONTENT_INDEX_PATH); if (!response.ok) { throw new Error("Unable to load content index."); } return response.json(); }; const stripFrontMatter = (markdown) => { if (!markdown.startsWith("---")) { return markdown; } const parts = markdown.split("---"); if (parts.length < 3) { return markdown; } return parts.slice(2).join("---").trim(); }; const renderTags = (tags, className) => { if (!tags || !tags.length) { return ""; } return `
${escapeHtml(entry.summary || "")}
${renderTags(entry.tags || [], "tag-list")} ${renderLinks(entry, "card-link")}${escapeHtml(emptyMessage)}
`; return; } container.innerHTML = entries.map(createCard).join(""); }; const initListPage = async () => { try { const index = await getIndex(); renderSection("projects-grid", index.projects || [], "No projects added yet."); renderSection("updates-grid", index.updates || [], "No updates added yet."); } catch (error) { renderSection("projects-grid", [], error.message); renderSection("updates-grid", [], error.message); } }; const initEntryPage = async () => { const shell = document.getElementById("entry-shell"); if (!shell) { return; } try { const params = new URLSearchParams(window.location.search); const type = params.get("type"); const slug = params.get("slug"); if (!type || !slug) { shell.innerHTML = ` `; return; } const index = await getIndex(); const entries = [...(index.projects || []), ...(index.updates || [])]; const entry = entries.find((item) => item.type === type && item.slug === slug); if (!entry) { throw new Error("Entry not found."); } const response = await fetch(entry.path); if (!response.ok) { throw new Error("Unable to load markdown entry."); } const rawMarkdown = await response.text(); const markdown = stripFrontMatter(rawMarkdown); shell.innerHTML = `${escapeHtml(entry.summary || "")}
${renderTags(entry.tags || [], "entry-tag-list")}