aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortdro <tdro@noreply.example.com>2024-02-12 20:15:17 -0500
committertdro <tdro@noreply.example.com>2024-02-12 20:15:17 -0500
commitd23d5bc6dae8035357f1dea088392ab3d2546f8e (patch)
treeb050a87ebf4f073aa2bc8c5b08c2735039994dc0
parentb541593793efc19639835200f185d273620770e6 (diff)
downloadcanory-d23d5bc6dae8035357f1dea088392ab3d2546f8e.tar.gz
canory-d23d5bc6dae8035357f1dea088392ab3d2546f8e.tar.bz2
canory-d23d5bc6dae8035357f1dea088392ab3d2546f8e.zip
static/js: Add custom DOM filter
Fallback to native navigation if transition in 300ms is not guaranteed
-rw-r--r--assets/js/index.js102
-rw-r--r--static/js/domfilter.ts145
-rw-r--r--static/js/index.ts1
3 files changed, 248 insertions, 0 deletions
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
@@ -752,6 +752,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",
numeric: "always",
diff --git a/static/js/domfilter.ts b/static/js/domfilter.ts
new file mode 100644
index 0000000..a81529b
--- /dev/null
+++ b/static/js/domfilter.ts
@@ -0,0 +1,145 @@
+/**
+ * DOM Filter Copyright (C) 2024 Thedro Neely
+ * License: AGPL | https://www.gnu.org/licenses/agpl-3.0.txt
+ */
+
+(function () {
+ type millisecond = number;
+ const timeout: millisecond = 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])) {
+ // 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) {
+ 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 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/>.
+*/
diff --git a/static/js/index.ts b/static/js/index.ts
index 4b0951c..517445c 100644
--- a/static/js/index.ts
+++ b/static/js/index.ts
@@ -7,6 +7,7 @@ import "./fixedsearch.ts";
import "./autoplay.ts";
import "./hoverfix.ts";
import "./forms.ts";
+import "./domfilter.ts";
import "./timeago.ts";
console.log("INFO: Surface Control Complete");