Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
latest
ngasull/classicThis package works with DenoIt is unknown whether this package works with Cloudflare Workers, Node.js, Bun
JSR Score
41%
Published
2 months ago (0.1.1)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176import { adoptNode, call, document, domParse, forEach, listen, location, preventDefault, Promise, querySelector, querySelectorAll, remove, replaceWith, TRUE, window, } from "jsr:@classic/util@0"; const suspenseDelay = 500; let currentNavigateQ: Promise<unknown> | 0; let fetchCache: Record<string, Document | undefined> = {}; const ccRoute = "cc-route"; const fetchingClass = "cc-fetching"; const navigate = async (href: string) => { let url = new URL(href, location.origin), rootSlot = querySelector(ccRoute)!, slot = rootSlot, child: Element | null, pathAttr: string | null, currentFrom: string[] = [], // Contains dynamic routes too navigateQ: Promise<void | Document> | Document, resQ: Promise<Document>, resFrom: string | null, rootClassList = document.documentElement.classList; // Fallback to regular navigation if page defines no route if (!rootSlot) navigateFallback(href); while ((child = querySelector(ccRoute, slot))) { pathAttr = slot.getAttribute("path"); if (!pathAttr) break; currentFrom.push(pathAttr); slot = child; } url.searchParams.set("cc-from", currentFrom.join("/")); navigateQ = currentNavigateQ = Promise.race([ new Promise<void>((resolve) => setTimeout(resolve, suspenseDelay)), resQ = Promise.resolve( fetchCache[url as any]?.cloneNode(TRUE) as Document ?? fetch(url).then((res): Promise<Document> => res.redirected ? Promise.reject(navigate(res.url)) : ( resFrom = res.headers.get("CC-From"), res.text().then((html) => currentNavigateQ == navigateQ ? domParse(html) : Promise.reject() ) ) ), ).finally(() => currentNavigateQ = 0), ]); if (location.href != href) history.pushState(0, "", href); if (!await navigateQ) rootClassList.add(fetchingClass); let receivedDoc = await resQ, receivedSlot = querySelector(ccRoute, receivedDoc.body), title = receivedDoc.title, currentHead: Record<string, Element> = {}, i = 0, seg: string; remove(rootClassList, fetchingClass); if (!receivedSlot) navigateFallback(href); // ! \\ `reqQ` needs to be awaited from here, so `resFrom` is available // ! \\ If `resFrom` is null, it means SSG or cache result slot = rootSlot; for (seg of resFrom! ? resFrom.split("/") : []) { // We must already have all the layouts assumed by CC-From if ( seg != currentFrom[i] || !(slot = querySelector(ccRoute, slot)!) ) navigateFallback(href); i++; } if (!slot) navigateFallback(href); if (title) document.title = title; forEachSourceable(document.head, (el, key) => currentHead[key] = el); forEachSourceable( receivedDoc.head, (el, key) => !currentHead[key] && document.head.append(adoptNode(el)), ); replaceWith(slot, adoptNode(receivedSlot!)); // Scripts parsed with DOMParser are not marked to be run forEach( querySelectorAll<HTMLScriptElement>("script", receivedSlot!), reviveScript, ); }; const navigateFallback = (href: string) => { throw location.href = href; }; const forEachSourceable = ( head: HTMLHeadElement, cb: (el: HTMLLinkElement | HTMLScriptElement, key: string) => void, ) => forEach( querySelectorAll<HTMLLinkElement | HTMLScriptElement>( `link,script`, head, ), (el, tagName?: any) => cb( el, `${tagName = el.tagName}:${ tagName == "LINK" ? (el as HTMLLinkElement).href : (el as HTMLScriptElement).src }`, ), ); const reviveScript = (script: HTMLScriptElement) => { let copy = document.createElement("script"); copy.text = script.text; replaceWith(script, copy); }; const isLocal = (href: string) => { let origin = location.origin; return new URL(href, origin).origin == origin; }; export const init = (): () => void => { let t: EventTarget | null, subs: Array<() => void> = [ listen( document.body, "click", (e) => !e.ctrlKey && !e.shiftKey && (t = e.target) instanceof HTMLAnchorElement && isLocal(t.href) && (preventDefault(e), navigate(t.href)), ), listen( document.body, "submit", (e) => (t = e.target) instanceof HTMLFormElement && t.method == "get" && !e.defaultPrevented && isLocal(t.action) && (preventDefault(e), navigate(t.action)), ), listen(window, "popstate", () => navigate(location.href)), ]; return () => subs.map(call); };