From d23d5bc6dae8035357f1dea088392ab3d2546f8e Mon Sep 17 00:00:00 2001 From: tdro Date: Mon, 12 Feb 2024 20:15:17 -0500 Subject: static/js: Add custom DOM filter Fallback to native navigation if transition in 300ms is not guaranteed --- assets/js/index.js | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) (limited to 'assets') diff --git a/assets/js/index.js b/assets/js/index.js index 91d3e61..15f720a 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -751,6 +751,108 @@ }); }); })(); +(function() { + const timeout = 300; + const state = "on"; + const key = "config.speed.fast"; + function fetch(url, method, callback) { + const http = new XMLHttpRequest(); + http.onreadystatechange = function() { + if (callback && http.readyState === 4) { + if (http.status === 200) callback(http); + else { + console.error("ERROR: Unable to navigate", http); + self.location.href = url; + } + } + }; + http.open(method, url); + http.timeout = timeout; + http.send(); + return http; + } + function styles(node) { + return node.nodeName === "LINK" && node.rel && node.rel.includes("stylesheet"); + } + function scripts(node) { + return node.nodeName === "SCRIPT" && node.hasAttribute("src"); + } + function filter(url, http) { + let live = document; + let node = live.head.childNodes.length; + let next = new DOMParser().parseFromString(http.responseText, "text/html"); + if (next.doctype === null || !http.getResponseHeader("content-type").includes("text/html")) return false; + while(node--){ + if (styles(live.head.childNodes[node]) || scripts(live.head.childNodes[node])) {} else { + live.head.removeChild(live.head.childNodes[node]); + } + } + while(next.head.firstChild){ + if (styles(next.head.firstChild) || scripts(next.head.firstChild)) { + next.head.removeChild(next.head.firstChild); + } else { + live.head.appendChild(next.head.firstChild); + } + } + while(live.documentElement.attributes.length > 0){ + live.documentElement.removeAttribute(live.documentElement.attributes[live.documentElement.attributes.length - 1].name); + } + while(next.documentElement.attributes.length > 0){ + live.documentElement.setAttribute(next.documentElement.attributes[next.documentElement.attributes.length - 1].name, next.documentElement.attributes[next.documentElement.attributes.length - 1].value); + next.documentElement.removeAttribute(next.documentElement.attributes[next.documentElement.attributes.length - 1].name); + } + live.body.parentElement.replaceChild(next.body, live.body); + } + function persist() { + let persist = document.createElement("link"); + persist.rel = "location"; + persist.target = JSON.stringify(self.location); + document.head.appendChild(persist); + } + self.addEventListener("DOMContentLoaded", function() { + if (localStorage[key] !== state) return; + persist(); + }); + self.addEventListener("popstate", function(event) { + if (localStorage[key] !== state) return; + const link = JSON.parse(document.querySelector('link[rel="location"]').target); + const url = event.target.location; + const hashed = link.pathname === url.pathname; + if (hashed) return; + fetch(url, "GET", function(http) { + filter(url.href, http); + persist(); + self.document.dispatchEvent(new CustomEvent("URLChangedCustomEvent", { + bubbles: true + })); + }); + }); + self.addEventListener("click", function(event) { + if (localStorage[key] !== state) return; + const links = document.querySelectorAll("a"); + for(let i1 = 0; i1 < links.length; i1++){ + const active = links[i1].contains(event.target); + const change = self.location.href !== links[i1].href; + const view = links[i1].attributes.hasOwnProperty("download") === false; + const local = self.location.origin === links[i1].origin && links[i1].target !== "_self"; + const hashed = self.location.pathname === links[i1].pathname && links[i1].href.includes("#"); + if (active && local && change && view && hashed === false) { + event.preventDefault(); + const url = links[i1].href; + links[i1].style.cursor = "wait"; + fetch(url, "GET", function(http) { + links[i1].removeAttribute("style"); + if (filter(url, http) === false) return self.location.href = url; + history.pushState({}, "", links[i1].href); + persist(); + self.document.dispatchEvent(new CustomEvent("URLChangedCustomEvent", { + bubbles: true + })); + }); + } + } + }); +})(); (function() { const relative = new Intl.RelativeTimeFormat("en", { localeMatcher: "best fit", -- cgit v1.2.3