diff options
Diffstat (limited to 'smoke/astro.build-main/src/scripts/spa.ts')
-rw-r--r-- | smoke/astro.build-main/src/scripts/spa.ts | 335 |
1 files changed, 0 insertions, 335 deletions
diff --git a/smoke/astro.build-main/src/scripts/spa.ts b/smoke/astro.build-main/src/scripts/spa.ts deleted file mode 100644 index 30403e4e9..000000000 --- a/smoke/astro.build-main/src/scripts/spa.ts +++ /dev/null @@ -1,335 +0,0 @@ -function onHashChange() { - document.querySelector(`${window.location.hash}`).scrollIntoView({ block: 'start', behavior: 'smooth' }); -} -window.addEventListener('DOMContentLoaded', () => { - if (window.location.hash) { - onHashChange(); - } -}) -window.addEventListener('hashchange', (event) => { - event.preventDefault(); - onHashChange() -}) - -const isElement = (target: EventTarget): target is Element => target instanceof Element; -const isLocalUrl = (href: string) => { - 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) {} - return false; -} -const isRelativeHref = (href: string) => { - return href[0] === '.' || !(href.startsWith('http') || href.startsWith('/')); -} -const getUrl = ({ target }: Event): URL|undefined => { - if (!isElement(target)) return; - const a = target.closest('a'); - if (!a) return; - const { href } = a; - if (!isLocalUrl(href)) return; - return new URL(href); -} - -const cache = new Map<string, string>(); -if (window.matchMedia('(hover: hover)').matches) { - let activeUrl: URL; - let activeTimeout: any; - const set = (url: URL) => { - if (cache.has(url.toString())) return; - activeUrl = url; - activeTimeout = setTimeout(() => { - fetch(url.toString()).then((res) => res.text()).then(text => cache.set(url.toString(), text)); - }, 600); - } - const clear = () => { - if (activeTimeout) { - clearTimeout(activeTimeout); - activeUrl = null; - activeTimeout = null; - } - } - window.addEventListener('mousemove', (event) => { - const url = getUrl(event); - if (!url) return clear(); - if (!activeUrl || url.toString() === activeUrl.toString()) { - if (activeTimeout) return; - return set(url); - } - }) -} - -const p = new DOMParser(); -window.addEventListener('click', async (event) => { - const url = getUrl(event); - if (!url) return; - event.preventDefault(); - try { - navigate(url); - } catch (e) { - window.location.assign(url); - } -}) -window.addEventListener('popstate', () => { - if (window.location.hash) return true; - try { - navigate(new URL(window.location.toString()), true); - } catch (e) { - window.location.reload(); - } - return true; -}) - -async function navigate(url: URL, isBack: boolean = false) { - if (!isBack) { - history.pushState({}, '', url); - } - await document.querySelector('#root').animate({ opacity: 0 }, { duration: 120, easing: 'ease-out' }).finished; - document.documentElement.classList.add('transition'); - if (!isBack) { - window.scrollTo({ top: 0 }) - } - let contents = cache.get(`${url}`) || await fetch(`${url}`) - .then(res => res.text()) - .catch(() => { - window.location.assign(url); - }); - if (!contents) return; - const html = p.parseFromString(contents, 'text/html'); - if (document.body.classList.contains('🥚')) { - html.body.classList.add('🥚'); - } - html.body.classList.add('js'); - html.documentElement.classList.add('transition'); - await diff(document, html, new URL(window.location.toString()), url); - await document.querySelector('#root').animate({ opacity: 1 }, { duration: 80, easing: 'ease-in' }).finished; - document.documentElement.classList.remove('transition'); - window.dispatchEvent(new CustomEvent('astro:navchange')); -} - -function waitForLoad(elements: HTMLElement[]): Promise<void> { - return new Promise((resolve) => { - const styles = elements.filter(el => el.localName === 'link' && el.hasAttribute('href') && el.getAttribute('rel') === 'stylesheet'); - const max = styles.length; - let count = 0; - if (max === 0) { - resolve(); - } - - styles.forEach(link => { - link.addEventListener('load', () => { - count++; - if (count >= max) { - resolve(); - } - }, { once: true }); - }) - }) -} - -const s = new XMLSerializer(); -async function diff(a: Document, b: Document, aURL: URL, bURL: URL) { - const keys = new Map(); - const remove = new Map(); - const add = new Map(); - const duplicates = new Set<string>(); - - function sync(from: Element, to: Element) { - for (const attr of to.attributes) { - let { namespaceURI, name, value } = attr; - if (namespaceURI) { - name = attr.localName || name; - const oldValue = from.getAttributeNS(namespaceURI, name); - if (oldValue !== value) { - if (attr.prefix === 'xmlns'){ - name = attr.name; - } - from.setAttributeNS(namespaceURI, name, value); - } - } else { - const oldValue = from.getAttribute(name); - if (oldValue !== value) { - from.setAttribute(name, value); - } - } - } - for (const attr of from.attributes) { - let { namespaceURI, name } = attr; - if (namespaceURI) { - name = attr.localName || name; - if (!to.hasAttributeNS(namespaceURI, name)) { - from.removeAttributeNS(namespaceURI, name) - } - } else { - if (!to.hasAttribute(name)) { - from.removeAttribute(name); - } - } - } - - if (from.localName === 'title') { - from.textContent = to.textContent; - } - } - - // Head logic - function getHeadKey(node: Element) { - switch (node.localName) { - case 'title': return 'title'; - case 'meta': { - if (node.hasAttribute('name')) { - return `meta[name=${node.getAttribute('name')}]`; - } - if (node.hasAttribute('property')) { - return `meta[property=${node.getAttribute('property')}]`; - } - return; - } - } - } - - for (const child of a.head.children) { - let key = getHeadKey(child); - if (key) { - keys.set(key, child); - continue; - } - if (child.localName === 'script' && child.hasAttribute('src')) { - const src = child.getAttribute('src'); - if (isRelativeHref(src)) { - const absURL = new URL(src, aURL); - key = `script[src=${absURL.pathname}]`; - child.setAttribute('src', absURL.pathname); - } else { - key = `script[src=${src}]` - } - } else if (child.localName === 'link' && child.hasAttribute('href')) { - const href = child.getAttribute('href'); - const rel = child.getAttribute('rel'); - if (isRelativeHref(href)) { - const absURL = new URL(href, aURL); - key = `link[rel=${rel}][href=${absURL.pathname}]`; - child.setAttribute('href', absURL.pathname); - } else { - key = `link[rel=${rel}][href=${href}]` - } - } else { - key = s.serializeToString(child); - } - - - remove.set(key, child); - } - - for (const child of b.head.children) { - let key = getHeadKey(child); - if (key) { - sync(keys.get(key), child); - continue; - } - if (child.localName === 'script' && child.hasAttribute('src')) { - const src = child.getAttribute('src'); - if (isRelativeHref(src)) { - const absURL = new URL(src, bURL); - key = `script[src=${absURL.pathname}]`; - child.setAttribute('src', absURL.pathname); - } else { - key = `script[src=${src}]` - } - } else if (child.localName === 'link' && child.hasAttribute('href')) { - const href = child.getAttribute('href'); - const rel = child.getAttribute('rel'); - if (isRelativeHref(href)) { - const absURL = new URL(href, bURL); - key = `link[rel=${rel}][href=${absURL.pathname}]`; - child.setAttribute('href', absURL.pathname); - } else { - key = `link[rel=${rel}][href=${href}]` - } - } else { - key = s.serializeToString(child); - } - - if (remove.has(key)) { - remove.delete(key); - duplicates.add(key); - } else if (!duplicates.has(key) && !add.has(key)) { - add.set(key, child); - } - } - - for (const node of remove.values()) { - node.remove(); - } - for (const node of add.values()) { - a.head.appendChild(node); - } - // Sync attributes - for (const el of ['documentElement', 'head', 'body']) { - sync(a[el], b[el]); - } - await waitForLoad(Array.from(add.values())); - - - // Body - const oldBody = new Map<string, Element>(); - const newBody = new Set<Element>(); - for (const child of a.body.children) { - let key: string; - if (child.localName === 'script' && child.hasAttribute('src')) { - const src = child.getAttribute('src'); - if (isRelativeHref(src)) { - const absURL = new URL(src, bURL); - key = `script[src=${absURL.pathname}]`; - child.setAttribute('src', absURL.pathname); - } else { - key = `script[src=${src}]` - } - } else { - key = s.serializeToString(child); - } - oldBody.set(key, child); - } - - outer: for (const child of b.body.children) { - let key: string; - if (child.localName === 'script' && child.hasAttribute('src')) { - const src = child.getAttribute('src'); - if (isRelativeHref(src)) { - const absURL = new URL(src, bURL); - key = `script[src=${absURL.pathname}]`; - child.setAttribute('src', absURL.pathname); - } else { - key = `script[src=${src}]` - } - } else { - key = s.serializeToString(child); - } - - if (oldBody.has(key)) { - oldBody.delete(key); - continue outer; - } - - for (const [oldKey, oldChild] of oldBody.entries()) { - if (oldChild.localName === child.localName) { - sync(oldChild, child); - oldChild.replaceChildren(...child.children); - oldBody.delete(oldKey); - continue outer; - } - } - newBody.add(child); - } - for (const node of oldBody.values()) { - node.remove(); - } - for (const node of newBody.values()) { - a.body.appendChild(node); - } -} |