From 311f05450ea4860a6c9ba1cbeedf862284b8f223 Mon Sep 17 00:00:00 2001 From: tdro Date: Sat, 16 Mar 2024 15:22:06 -0400 Subject: static/js/instantpage: Pare down --- static/js/instantpage.ts | 222 +++++++++++------------------------------------ 1 file changed, 50 insertions(+), 172 deletions(-) (limited to 'static') diff --git a/static/js/instantpage.ts b/static/js/instantpage.ts index b76cef8..5d2f59a 100644 --- a/static/js/instantpage.ts +++ b/static/js/instantpage.ts @@ -6,6 +6,35 @@ (function () { + /*/ + * Element.closest() polyfill + * https://developer.mozilla.org/en-US/docs/Web/API/Element/closest + * https://github.com/idmadj/element-closest-polyfill#readme + * Copyright (C) Abdelmadjid Hammou + * Licence: ISC | https://www.isc.org/licenses/ + /*/ + + if (typeof Element !== "undefined") { + if (!Element.prototype.matches) { + Element.prototype.matches = Element.prototype.msMatchesSelector || + Element.prototype.webkitMatchesSelector; + } + if (!Element.prototype.closest) { + Element.prototype.closest = function (s) { + var el = this; + do { + if (el.matches(s)) return el; + el = el.parentElement || el.parentNode; + } while (el !== null && el.nodeType === 1); + return null; + }; + } + } + + /*/ + * ----------------------------------------------------------------- + /*/ + function fetch(url, method, callback) { const http = new XMLHttpRequest(); http.onreadystatechange = function () { @@ -19,186 +48,35 @@ } self.addEventListener("DOMContentLoaded", function () { - const prefetches = new Set(); - const prefetchElement = document.createElement("link"); - - const isSupported = prefetchElement.relList - && prefetchElement.relList.supports - && prefetchElement.relList.supports("prefetch") - && window.IntersectionObserver - && "isIntersecting" in IntersectionObserverEntry.prototype; - - const allowQueryString = "instantAllowQueryString" in document.body.dataset; - const allowExternalLinks = "instantAllowExternalLinks" in document.body.dataset; - const useWhitelist = "instantWhitelist" in document.body.dataset; - const mousedownShortcut = "instantMousedownShortcut" in document.body.dataset; - - const DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION = 1111; - - let mouseoverTimer; - let lastTouchTimestamp; - let delayOnHover = 65; - let useMousedown = false; - let useMousedownOnly = false; - let useViewport = false; - - if ("instantIntensity" in document.body.dataset) { - const intensity = document.body.dataset.instantIntensity; - - if (intensity.substr(0, "mousedown".length) == "mousedown") { - useMousedown = true; - if (intensity == "mousedown-only") { useMousedownOnly = true; } - } else if (intensity.substr(0, "viewport".length) == "viewport") { - if (!(navigator.connection - && (navigator.connection.saveData - || (navigator.connection.effectiveType - && navigator.connection.effectiveType.includes("2g"))))) { - if (intensity == "viewport") { - /* - Biggest iPhone resolution (which we want): 414 × 896 = 370944 - Small 7" tablet resolution (which we don’t want): 600 × 1024 = 614400 - Note that the viewport (which we check here) is smaller than the resolution due to the UI’s chrome - */ - if (document.documentElement.clientWidth * document.documentElement.clientHeight < 450000) { useViewport = true; } - } else if (intensity == "viewport-all") { - useViewport = true; - } - } - } else { - const milliseconds = parseInt(intensity); - if (!isNaN(milliseconds)) { delayOnHover = milliseconds; } - } - } - - if (isSupported) { - const eventListenersOptions = { capture: true, passive: true }; - - if (!useMousedownOnly) { document.addEventListener( "touchstart", touchstartListener, eventListenersOptions,); } - - if (!useMousedown) { - document.addEventListener( "mouseover", mouseoverListener, eventListenersOptions,); - } else if (!mousedownShortcut) { - document.addEventListener( "mousedown", mousedownListener, eventListenersOptions,); - } - - if (mousedownShortcut) { document.addEventListener( "mousedown", mousedownShortcutListener, eventListenersOptions,); } - - if (useViewport) { - let triggeringFunction; - if (window.requestIdleCallback) { - triggeringFunction = function (callback) { requestIdleCallback(callback, { timeout: 1500 }); } - } else { - triggeringFunction = function (callback) { callback(); } - } - - triggeringFunction(function () { - const intersectionObserver = new IntersectionObserver( - function (entries) { - entries.forEach(function (entry) { - if (entry.isIntersecting) { - const linkElement = entry.target; - intersectionObserver.unobserve(linkElement); - preload(linkElement.href); - } - }); - }, - ); - - document.querySelectorAll("a").forEach(function (linkElement) { - if (isPreloadable(linkElement)) { intersectionObserver.observe(linkElement); } - }); - }); - } - } - - function touchstartListener(event) { - /* - Chrome on Android calls mouseover before touchcancel so `lastTouchTimestamp` - must be assigned on touchstart to be measured on mouseover. - */ - lastTouchTimestamp = performance.now(); - const linkElement = event.target.closest("a"); - if (!isPreloadable(linkElement)) { return; } - preload(linkElement.href); - } - - function mouseoverListener(event) { - if (performance.now() - lastTouchTimestamp < DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION) { return; } - const linkElement = event.target.closest("a"); - if (!isPreloadable(linkElement)) { return; } - linkElement.addEventListener("mouseout", mouseoutListener, { passive: true }); - - mouseoverTimer = setTimeout(function () { - preload(linkElement.href); - mouseoverTimer = undefined; - }, delayOnHover); - } - - function mousedownListener(event) { - const linkElement = event.target.closest("a"); - if (!isPreloadable(linkElement)) { return; } - preload(linkElement.href); - } - - function mouseoutListener(event) { - if (event.relatedTarget && event.target.closest("a") == event.relatedTarget.closest("a")) { return; } - if (mouseoverTimer) { clearTimeout(mouseoverTimer); mouseoverTimer = undefined; } - } - - function mousedownShortcutListener(event) { - if (performance.now() - lastTouchTimestamp < DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION) { return; } - - const linkElement = event.target.closest("a"); - - if (event.which > 1 || event.metaKey || event.ctrlKey) { return; } - - if (!linkElement) { return; } - - linkElement.addEventListener("click", function (event) { - if (event.detail == 1337) { return; } - event.preventDefault(); - }, { capture: true, passive: false, once: true }); - - const customEvent = new MouseEvent("click", { - view: window, - bubbles: true, - cancelable: false, - detail: 1337, + ["mouseout", "mousedown", "touchstart"].forEach(function (event) { + self.addEventListener(event, function (event) { + const url = event.target.closest("a"); + if (preloadable(url) === undefined) return; + preload(url.href); }); - - linkElement.dispatchEvent(customEvent); - } - - function isPreloadable(linkElement) { - if (!linkElement || !linkElement.href) { return; } - - if (useWhitelist && !("instant" in linkElement.dataset)) { return; } - - if (!allowExternalLinks && linkElement.origin != location.origin && !("instant" in linkElement.dataset)) { return; } - - if (!["http:", "https:"].includes(linkElement.protocol)) { return; } - - if (linkElement.protocol == "http:" && location.protocol == "https:") { return; } - - if (!allowQueryString && linkElement.search && !("instant" in linkElement.dataset)) { return; } - - if (linkElement.hash && linkElement.pathname + linkElement.search == location.pathname + location.search) { return; } - - if ("noInstant" in linkElement.dataset) { return; } - - return true; + }); + + function preloadable(url) { + switch (true) { + case (url === null || url.href === null): return; + case (url.origin !== location.origin): return; + case (["http:", "https:"].includes(url.protocol) === null): return; + case (url.protocol === "http:" && location.protocol === "https:"): return; + case (url.hash && url.pathname + url.search == location.pathname + location.search): return; + default: return true; + } } function preload(url) { - if (prefetches.has(url)) { return; } const prefetcher = document.createElement("link"); prefetcher.rel = "custom-prefetch"; prefetcher.href = url; - fetch(url, "GET", function () { - document.head.appendChild(prefetcher); - prefetches.add(url); - }); + const selector = `link[rel="${prefetcher.rel}"][href="${prefetcher.href}"]`; + const prefetched = document.head.contains(document.querySelector(selector)); + if (prefetched) { return; } + document.head.appendChild(prefetcher); + fetch(url, "GET", function () {}); } }); })(); -- cgit v1.2.3