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 --- assets/js/index.js | 234 ++++++++++------------------------------------- static/js/instantpage.ts | 222 ++++++++++---------------------------------- 2 files changed, 98 insertions(+), 358 deletions(-) diff --git a/assets/js/index.js b/assets/js/index.js index f6c1686..4268a24 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -165,6 +165,21 @@ } })(); (function() { + 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() { @@ -177,200 +192,47 @@ return http; } 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; - 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") { - 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) { - lastTouchTimestamp = performance.now(); - const linkElement = event.target.closest("a"); - if (!isPreloadable(linkElement)) { - return; - } - preload(linkElement.href); - } - function mouseoverListener(event) { - if (performance.now() - lastTouchTimestamp < 1111) { - return; - } - const linkElement = event.target.closest("a"); - if (!isPreloadable(linkElement)) { - return; - } - linkElement.addEventListener("mouseout", mouseoutListener, { - passive: true + [ + "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); }); - 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 < 1111) { - 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) { + }); + function preloadable(url) { + switch(true){ + case url === null || url.href === null: return; - } - event.preventDefault(); - }, { - capture: true, - passive: false, - once: true - }); - const customEvent = new MouseEvent("click", { - view: window, - bubbles: true, - cancelable: false, - detail: 1337 - }); - 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; + 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; } - 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() {}); } }); })(); 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