aboutsummaryrefslogtreecommitdiff
path: root/static/js/domfilter.ts
diff options
context:
space:
mode:
Diffstat (limited to 'static/js/domfilter.ts')
-rw-r--r--static/js/domfilter.ts145
1 files changed, 145 insertions, 0 deletions
diff --git a/static/js/domfilter.ts b/static/js/domfilter.ts
new file mode 100644
index 0000000..8900e2f
--- /dev/null
+++ b/static/js/domfilter.ts
@@ -0,0 +1,145 @@
+/*
+ * DOM Filter Copyright (C) 2024 Thedro Neely
+ * Licence: AGPL | https://www.gnu.org/licenses/agpl-3.0.txt
+*/
+
+(function () {
+ type millisecond = number;
+ const timeout: millisecond = 300;
+
+ const state = "on";
+ const key = "config.navigation.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])) {
+ // console.log("INFO: Persist:", 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) {
+ if (filter(url, http) === false) return self.location.href = url;
+ 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 i = 0; i < links.length; i++) {
+ const active = links[i].contains(event.target);
+ const change = self.location.href !== links[i].href;
+ const view = links[i].attributes.hasOwnProperty("download") === false;
+ const local = self.location.origin === links[i].origin && links[i].target !== "_self";
+ const hashed = self.location.pathname === links[i].pathname && links[i].href.includes("#");
+ if (active && local && change && view && (hashed === false)) {
+ event.preventDefault();
+ const url = links[i].href;
+ links[i].style.cursor = "wait";
+ fetch(url, "GET", function (http) {
+ links[i].removeAttribute("style")
+ if (filter(url, http) === false) return self.location.href = url;
+ history.pushState({}, "", links[i].href);
+ persist();
+ self.document.dispatchEvent(new CustomEvent("URLChangedCustomEvent", { bubbles: true }));
+ });
+ }
+ }
+ });
+})();
+
+/*
+ * Copyright (C) 2024 Thedro Neely
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/