1// Live pages. ssexi streams the forge's events into #live, a status line that2// stays empty while all is well: it shows a push being applied ("cat pushed3// main, updating") or a dropped connection. When this4// repo's pages are rebuilt, the new #content is swapped in without a reload,5// keeping the scroll position, and the status clears shortly after. A6// forge:refresh event on the document asks for the page again the same way.7// Reconnects if the stream drops. A server without the stream (a static file8// server, npm run dev) answers 404; then it stays quiet.9(() => {10 const live = document.getElementById('live');11 if (!live || !window.fetch) return;1213 const refresh = async (): Promise<void> => {14 // Revalidate: a page this build didn't change comes back as a 304.15 const res = await fetch(location.href, { cache: 'no-cache' });16 if (!res.ok) return;17 const next = new DOMParser().parseFromString(await res.text(), 'text/html').getElementById('content');18 const cur = document.getElementById('content');19 if (!next || !cur) return;20 const y = window.scrollY;21 cur.replaceWith(next);22 window.scrollTo(0, y);23 // For the scripts that enhance the content (times, diff files).24 document.dispatchEvent(new Event('forge:content'));25 };2627 // discuss.ts asks again after a post, should no build event reach the page.28 document.addEventListener('forge:refresh', () => void refresh());2930 let quiet: ReturnType<typeof setTimeout> | undefined;31 document.addEventListener('fx:sse:built', (e) => {32 let repo: string | undefined;33 try {34 repo = JSON.parse((e as CustomEvent<{ message: { data: string } }>).detail.message.data).repo;35 } catch {}36 if (!live.dataset.repo || live.dataset.repo === repo) void refresh();37 clearTimeout(quiet);38 quiet = setTimeout(() => (live.innerHTML = ''), 4000);39 });4041 // Silent while connected. If the stream stays down past a short grace42 // period (navigating away also closes it), say so until it's back.43 let wait = 1000;44 let down: ReturnType<typeof setTimeout> | undefined;45 const reconnect = (): void => {46 down ??= setTimeout(() => (live.innerHTML = '<span class="live-off" title="Not receiving updates; reconnecting">offline</span>'), 3000);47 setTimeout(() => live.dispatchEvent(new CustomEvent('fx:inited')), wait);48 wait = Math.min(wait * 2, 30000);49 };50 live.addEventListener('fx:sse:open', () => {51 wait = 1000;52 clearTimeout(down);53 down = undefined;54 live.innerHTML = '';55 });56 for (const ev of ['fx:sse:close', 'fx:sse:error', 'fx:error']) live.addEventListener(ev, reconnect);57 // A response that isn't a stream never goes into the status line.58 live.addEventListener('fx:after', (e) => {59 const cfg = (e as CustomEvent<{ cfg: { sse?: object; response: Response } }>).detail.cfg;60 if (cfg.sse) return;61 e.preventDefault();62 if (cfg.response.status !== 404) reconnect();63 });64})();