| | |
| | | import micromorph from "micromorph" |
| | | import { CanonicalSlug, RelativeURL, getCanonicalSlug } from "../../util/path" |
| | | import { FullSlug, RelativeURL, getFullSlug } from "../../util/path" |
| | | |
| | | // adapted from `micromorph` |
| | | // https://github.com/natemoo-re/micromorph |
| | |
| | | try { |
| | | const url = new URL(href) |
| | | if (window.location.origin === url.origin) { |
| | | if (url.pathname === window.location.pathname) { |
| | | return !url.hash |
| | | } |
| | | return true |
| | | } |
| | | } catch (e) { } |
| | | } catch (e) {} |
| | | return false |
| | | } |
| | | |
| | |
| | | return { url: new URL(href), scroll: "routerNoscroll" in a.dataset ? false : undefined } |
| | | } |
| | | |
| | | function notifyNav(url: CanonicalSlug) { |
| | | function notifyNav(url: FullSlug) { |
| | | const event: CustomEventMap["nav"] = new CustomEvent("nav", { detail: { url } }) |
| | | document.dispatchEvent(event) |
| | | } |
| | |
| | | }) |
| | | |
| | | if (!contents) return |
| | | if (!isBack) { |
| | | history.pushState({}, "", url) |
| | | window.scrollTo({ top: 0 }) |
| | | } |
| | | |
| | | const html = p.parseFromString(contents, "text/html") |
| | | let title = html.querySelector("title")?.textContent |
| | |
| | | announcer.dataset.persist = "" |
| | | html.body.appendChild(announcer) |
| | | |
| | | // morph body |
| | | micromorph(document.body, html.body) |
| | | |
| | | // scroll into place and add history |
| | | if (!isBack) { |
| | | if (url.hash) { |
| | | const el = document.getElementById(decodeURIComponent(url.hash.substring(1))) |
| | | el?.scrollIntoView() |
| | | } else { |
| | | window.scrollTo({ top: 0 }) |
| | | } |
| | | } |
| | | |
| | | // now, patch head |
| | | const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])") |
| | | elementsToRemove.forEach((el) => el.remove()) |
| | | const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])") |
| | | elementsToAdd.forEach((el) => document.head.appendChild(el)) |
| | | |
| | | notifyNav(getCanonicalSlug(window)) |
| | | // delay setting the url until now |
| | | // at this point everything is loaded so changing the url should resolve to the correct addresses |
| | | if (!isBack) { |
| | | history.pushState({}, "", url) |
| | | } |
| | | notifyNav(getFullSlug(window)) |
| | | delete announcer.dataset.persist |
| | | } |
| | | |
| | |
| | | } |
| | | }) |
| | | |
| | | window.addEventListener("popstate", () => { |
| | | if (window.location.hash) return |
| | | window.addEventListener("popstate", (event) => { |
| | | const { url } = getOpts(event) ?? {} |
| | | if (window.location.hash && window.location.pathname === url?.pathname) return |
| | | try { |
| | | navigate(new URL(window.location.toString()), true) |
| | | } catch (e) { |
| | |
| | | } |
| | | |
| | | createRouter() |
| | | notifyNav(getCanonicalSlug(window)) |
| | | notifyNav(getFullSlug(window)) |
| | | |
| | | if (!customElements.get("route-announcer")) { |
| | | const attrs = { |