"use strict"; /* * Themed embed for the bggpipe "Game Shelves" export. * * The export under /games/ is a self-contained static site regenerated by * bggpipe; nothing here edits it. This loader fetches an export page, lifts * out its
, rewrites the export's relative URLs to absolute /games/… * paths, and drops the result into the themed /mygames/ page. Individual game * links are rewired to ?g= so the blog chrome stays on every page. */ (function () { const BASE = "/games/"; // where the export is served const mount = document.getElementById("games-embed"); if (!mount) return; // Which export page to show: ?g= for a game, otherwise the index. function targetFor(slug) { return slug ? `${BASE}${slug}/` : BASE; } // Resolve a relative href/src from the export page against its real URL, // returning an absolute path so it works from /mygames/. function absolutize(value, pageUrl) { if (!value) return value; // Leave absolute URLs, anchors, and protocol-relative links alone. if (/^([a-z]+:|\/\/|#|\/)/i.test(value)) return value; return new URL(value, new URL(pageUrl, location.origin)).pathname + (new URL(value, new URL(pageUrl, location.origin)).search || ""); } // Turn an export game link (e.g. "agricola/" or "../agricola/") into the // themed equivalent (?g=agricola). Returns null if it isn't a game link. function slugFromGameHref(href, pageUrl) { const abs = new URL(href, new URL(pageUrl, location.origin)).pathname; if (!abs.startsWith(BASE)) return null; const rest = abs.slice(BASE.length).replace(/\/$/, ""); // A game link is a single path segment that isn't an asset folder. if (!rest || rest.includes("/") || rest === "art") return null; return rest; } function render(slug) { // no-cache: revalidate against the server so a re-export shows up on // the next visit instead of whenever the heuristic cache expires fetch(targetFor(slug), { cache: "no-cache" }) .then((r) => { if (!r.ok) throw new Error(`${r.status} for ${targetFor(slug)}`); return r.text(); }) .then((html) => { const pageUrl = targetFor(slug); const doc = new DOMParser().parseFromString(html, "text/html"); const main = doc.querySelector("main"); if (!main) throw new Error("no
in export page"); // Rewrite asset URLs (images, etc.) to absolute /games/ paths. main.querySelectorAll("[src]").forEach((el) => { el.setAttribute("src", absolutize(el.getAttribute("src"), pageUrl)); }); // Rewrite links: game links become ?g=; everything else // (BGG, back-to-library, etc.) gets absolutized. main.querySelectorAll("a[href]").forEach((a) => { const href = a.getAttribute("href"); const gameSlug = slugFromGameHref(href, pageUrl); const backToLibrary = new URL(href, new URL(pageUrl, location.origin)).pathname .replace(/\/$/, "") === BASE.replace(/\/$/, ""); if (backToLibrary) { a.setAttribute("href", "?"); } else if (gameSlug) { a.setAttribute("href", `?g=${encodeURIComponent(gameSlug)}`); } else { a.setAttribute("href", absolutize(href, pageUrl)); } }); mount.innerHTML = ""; mount.append(...main.childNodes); wireLinks(); wireSearch(); document.title = doc.title.replace(/ · The Game Shelves$/, "") + " · Eric's Game Shelves"; window.scrollTo(0, 0); }) .catch((err) => { mount.innerHTML = `

Sorry, the game library couldn't load. ` + `Open it directly.

`; console.error("games embed:", err); }); } // Intercept in-page (?g=…) links so navigation stays client-side and themed. function wireLinks() { mount.querySelectorAll('a[href^="?"]').forEach((a) => { a.addEventListener("click", (e) => { e.preventDefault(); const url = new URL(a.getAttribute("href"), location.href); const slug = url.searchParams.get("g"); history.pushState({ slug }, "", url); render(slug); }); }); } // The export's index carries a search box; its inline