From b1743799905416266f6fc94618a3b3064841668b Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Thu, 24 Sep 2026 14:14:07 -0400 Subject: [PATCH] fix(components): resolve gallery preview URLs against the site root Quarto rewrites an img's src to a relative path but leaves data-* attributes site-root absolute, so the hover GIF was fetched from /components/... instead of /py/components/... in production (404) and the poster broke on pointerleave. --- components/_partials/components-list.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/components/_partials/components-list.js b/components/_partials/components-list.js index b7b4bdf5..3aa1cd6c 100644 --- a/components/_partials/components-list.js +++ b/components/_partials/components-list.js @@ -25,11 +25,22 @@ const gifDuration = (buffer) => { return total; }; +// Quarto rewrites `src` to a relative path but leaves `data-*` site-root +// absolute, which misses the /py prefix in production. Resolve them against +// the site root Quarto records in every page. +const siteRoot = new URL( + document.querySelector('meta[name="quarto:offset"]')?.content ?? "./", + document.baseURI, +); +const fromSiteRoot = (path) => new URL(path.replace(/^\//, ""), siteRoot).href; + document.querySelectorAll(".component-list-card").forEach((card) => { const image = card.querySelector(".component-list-preview"); const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); if (!image) return; + const staticSrc = fromSiteRoot(image.dataset.staticSrc); + const animatedSrc = fromSiteRoot(image.dataset.animatedSrc); // Fetch the GIF once, then give it a fresh blob URL on every hover: a new URL // always plays from frame 0 (re-setting a cached GIF's src does not restart @@ -54,7 +65,7 @@ document.querySelectorAll(".component-list-card").forEach((card) => { }; const loadGif = () => - (gif ??= fetch(image.dataset.animatedSrc) + (gif ??= fetch(animatedSrc) .then((response) => (response.ok ? download(response) : Promise.reject())) .then(async (blob) => { const duration = gifDuration(await blob.arrayBuffer()); @@ -64,7 +75,7 @@ document.querySelectorAll(".component-list-card").forEach((card) => { const showPoster = () => { previewing = false; - image.src = image.dataset.staticSrc; + image.src = staticSrc; card.classList.remove("is-previewing", "is-playing"); if (blobUrl) URL.revokeObjectURL(blobUrl); blobUrl = undefined; @@ -98,7 +109,7 @@ document.querySelectorAll(".component-list-card").forEach((card) => { if (blobUrl && image.src === blobUrl) card.classList.add("is-playing"); }); image.addEventListener("error", () => { - if (image.src !== new URL(image.dataset.staticSrc, document.baseURI).href) { + if (image.src !== staticSrc) { showPoster(); } });