157 lines
4.1 KiB
JavaScript
157 lines
4.1 KiB
JavaScript
/* global marked */
|
||
|
||
const DOCS = [
|
||
{
|
||
id: "submission",
|
||
title: "Submission Edition (Clean)",
|
||
file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_SUBMISSION_EDITION.md",
|
||
},
|
||
{
|
||
id: "submission-full",
|
||
title: "Submission Edition (Clean, Full) v2.0",
|
||
file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_SUBMISSION_EDITION_FULL.md",
|
||
},
|
||
{
|
||
id: "data",
|
||
title: "Data‑Driven Technical Report (Microlab)",
|
||
file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_DATA_DRIVEN_EDITION.md",
|
||
},
|
||
{
|
||
id: "data-full",
|
||
title: "Data‑Driven Edition (Full) v2.0",
|
||
file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_DATA_DRIVEN_EDITION_FULL.md",
|
||
},
|
||
{
|
||
id: "uncut",
|
||
title: "Uncut Dossier (Full)",
|
||
file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_UNCUT.md",
|
||
},
|
||
];
|
||
|
||
function slugify(value) {
|
||
return String(value)
|
||
.replace(/<[^>]+>/g, "")
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace(/[^\w\s-]/g, "")
|
||
.replace(/\s+/g, "-")
|
||
.replace(/-+/g, "-");
|
||
}
|
||
|
||
function createSlugger() {
|
||
const seen = new Map();
|
||
return (headingText) => {
|
||
const base = slugify(headingText) || "section";
|
||
const current = seen.get(base) || 0;
|
||
seen.set(base, current + 1);
|
||
return current === 0 ? base : `${base}-${current + 1}`;
|
||
};
|
||
}
|
||
|
||
function setStatus(text) {
|
||
const statusEl = document.getElementById("status");
|
||
statusEl.textContent = text || "";
|
||
}
|
||
|
||
function buildToc() {
|
||
const tocEl = document.getElementById("toc");
|
||
tocEl.innerHTML = "";
|
||
|
||
const title = document.createElement("div");
|
||
title.className = "toc-title";
|
||
title.textContent = "On this page";
|
||
tocEl.appendChild(title);
|
||
|
||
const headings = Array.from(document.querySelectorAll("#content h1, #content h2, #content h3, #content h4"));
|
||
for (const heading of headings) {
|
||
const level = Number(heading.tagName.slice(1));
|
||
if (!heading.id) continue;
|
||
|
||
const a = document.createElement("a");
|
||
a.href = `#${heading.id}`;
|
||
a.textContent = heading.textContent || heading.id;
|
||
a.className = `depth-${level}`;
|
||
tocEl.appendChild(a);
|
||
}
|
||
}
|
||
|
||
function scrollToHash() {
|
||
const id = (location.hash || "").replace(/^#/, "");
|
||
if (!id) return;
|
||
|
||
const el = document.getElementById(id);
|
||
if (el) el.scrollIntoView({ block: "start" });
|
||
}
|
||
|
||
function setQueryParam(key, value) {
|
||
const url = new URL(location.href);
|
||
if (value == null) url.searchParams.delete(key);
|
||
else url.searchParams.set(key, value);
|
||
history.replaceState(null, "", url.toString());
|
||
}
|
||
|
||
async function loadDoc(docId) {
|
||
const doc = DOCS.find((d) => d.id === docId) || DOCS[0];
|
||
setQueryParam("doc", doc.id);
|
||
|
||
const rawLink = document.getElementById("openRaw");
|
||
rawLink.href = doc.file;
|
||
|
||
setStatus(`Loading: ${doc.title}…`);
|
||
|
||
const slugger = createSlugger();
|
||
const renderer = new marked.Renderer();
|
||
renderer.heading = (text, level) => {
|
||
const id = slugger(text);
|
||
return `<h${level} id="${id}">${text}</h${level}>`;
|
||
};
|
||
|
||
marked.setOptions({
|
||
renderer,
|
||
gfm: true,
|
||
breaks: false,
|
||
mangle: false,
|
||
headerIds: false,
|
||
});
|
||
|
||
try {
|
||
const res = await fetch(doc.file, { cache: "no-cache" });
|
||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||
const markdown = await res.text();
|
||
|
||
const html = marked.parse(markdown);
|
||
const contentEl = document.getElementById("content");
|
||
contentEl.innerHTML = html;
|
||
|
||
buildToc();
|
||
setStatus(`Loaded: ${doc.title}`);
|
||
scrollToHash();
|
||
} catch (err) {
|
||
setStatus(`Failed to load ${doc.title}: ${String(err)}`);
|
||
document.getElementById("content").textContent = "";
|
||
}
|
||
}
|
||
|
||
function init() {
|
||
const select = document.getElementById("docSelect");
|
||
for (const doc of DOCS) {
|
||
const opt = document.createElement("option");
|
||
opt.value = doc.id;
|
||
opt.textContent = doc.title;
|
||
select.appendChild(opt);
|
||
}
|
||
|
||
const initial = new URLSearchParams(location.search).get("doc") || DOCS[0].id;
|
||
select.value = DOCS.some((d) => d.id === initial) ? initial : DOCS[0].id;
|
||
|
||
select.addEventListener("change", () => {
|
||
location.hash = "";
|
||
loadDoc(select.value);
|
||
});
|
||
|
||
window.addEventListener("hashchange", () => scrollToHash());
|
||
|
||
loadDoc(select.value);
|
||
}
|
||
|
||
init();
|