moving to custom swap engine

Signed-off-by: ari melody <ari@arimelody.me>
This commit is contained in:
ari melody 2024-04-16 17:53:24 +01:00
parent 749f9bc8b7
commit 13d802d361
21 changed files with 4361 additions and 225 deletions

View file

@ -1,44 +0,0 @@
const accessibility = JSON.parse(localStorage.getItem("accessibility")) || {};
function toggle_accessibility_setting(name) {
if (accessibility[name]) {
delete accessibility[name];
update_accessibility();
return true;
}
accessibility[name] = true;
update_accessibility();
return true;
}
function set_accessibility_setting(name, value) {
accessibility[name] = value;
update_accessibility();
return true;
}
function clear_accessibility_setting(name) {
if (!accessibility[name]) return false;
delete accessibility[name];
update_accessibility();
return true;
}
function update_accessibility() {
localStorage.setItem("accessibility", JSON.stringify(accessibility));
}
if (accessibility) {
if (accessibility.disable_crt) {
document.querySelector('div#overlay').setAttribute("hidden", true);
document.body.style.textShadow = "none";
document.getElementById('toggle-crt').classList.add("disabled");
}
}
document.getElementById("toggle-crt").addEventListener("click", () => {
toggle_accessibility_setting("disable_crt");
document.querySelector('div#overlay').toggleAttribute("hidden");
document.getElementById('toggle-crt').className = accessibility.disable_crt ? "disabled" : "";
});

42
public/script/config.js Normal file
View file

@ -0,0 +1,42 @@
function toggle_config_setting(config, name) {
if (config[name]) {
delete config[name];
update_config(config);
return true;
}
config[name] = true;
update_config(config);
return true;
}
function set_config_setting(config, name, value) {
config[name] = value;
update_config(config);
return true;
}
function clear_config_setting(config, name) {
if (!config[name]) return false;
delete config[name];
update_config(config);
return true;
}
function update_config(config) {
localStorage.setItem("config", JSON.stringify(config));
}
const config = JSON.parse(localStorage.getItem("config")) || {};
if (config) {
if (config.disable_crt) {
document.querySelector('div#overlay').setAttribute("hidden", true);
document.body.style.textShadow = "none";
document.getElementById('toggle-crt').classList.add("disabled");
}
}
document.getElementById("toggle-crt").addEventListener("click", () => {
toggle_config_setting(config, "disable_crt");
document.querySelector('div#overlay').toggleAttribute("hidden");
document.getElementById('toggle-crt').className = config.disable_crt ? "disabled" : "";
});

View file

@ -1,14 +1,12 @@
const header_links = document.getElementById("header-links");
const hamburger = document.getElementById("header-links-toggle");
function toggle_header_links() {
header_links.classList.toggle("open");
}
document.addEventListener("click", event => {
if (!header_links.contains(event.target) && !hamburger.contains(event.target) && !header_links.href) {
header_links.classList.remove("open");
}
});
hamburger.addEventListener("click", event => { toggle_header_links(); });
hamburger.addEventListener("click", event => {
header_links.classList.toggle("open");
});

View file

@ -0,0 +1,147 @@
// This adds the "preload" extension to htmx. By default, this will
// preload the targets of any tags with `href` or `hx-get` attributes
// if they also have a `preload` attribute as well. See documentation
// for more details
htmx.defineExtension("preload", {
onEvent: function(name, event) {
// Only take actions on "htmx:afterProcessNode"
if (name !== "htmx:afterProcessNode") {
return;
}
// SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY
// attr gets the closest non-empty value from the attribute.
var attr = function(node, property) {
if (node == undefined) {return undefined;}
return node.getAttribute(property) || node.getAttribute("data-" + property) || attr(node.parentElement, property)
}
// load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're
// preloading an htmx resource (this sends the same HTTP headers as a regular htmx request)
var load = function(node) {
// Called after a successful AJAX request, to mark the
// content as loaded (and prevent additional AJAX calls.)
var done = function(html) {
if (!node.preloadAlways) {
node.preloadState = "DONE"
}
if (attr(node, "preload-images") == "true") {
document.createElement("div").innerHTML = html // create and populate a node to load linked resources, too.
}
}
return function() {
// If this value has already been loaded, then do not try again.
if (node.preloadState !== "READY") {
return;
}
// Special handling for HX-GET - use built-in htmx.ajax function
// so that headers match other htmx requests, then set
// node.preloadState = TRUE so that requests are not duplicated
// in the future
var hxGet = node.getAttribute("hx-get") || node.getAttribute("data-hx-get")
if (hxGet) {
htmx.ajax("GET", hxGet, {
source: node,
handler:function(elt, info) {
done(info.xhr.responseText);
}
});
return;
}
// Otherwise, perform a standard xhr request, then set
// node.preloadState = TRUE so that requests are not duplicated
// in the future.
if (node.getAttribute("href")) {
var r = new XMLHttpRequest();
r.open("GET", node.getAttribute("href"));
r.onload = function() {done(r.responseText);};
r.send();
return;
}
}
}
// This function processes a specific node and sets up event handlers.
// We'll search for nodes and use it below.
var init = function(node) {
// If this node DOES NOT include a "GET" transaction, then there's nothing to do here.
if (node.getAttribute("href") + node.getAttribute("hx-get") + node.getAttribute("data-hx-get") == "") {
return;
}
// Guarantee that we only initialize each node once.
if (node.preloadState !== undefined) {
return;
}
// Get event name from config.
var on = attr(node, "preload") || "mousedown"
const always = on.indexOf("always") !== -1
if (always) {
on = on.replace('always', '').trim()
}
// FALL THROUGH to here means we need to add an EventListener
// Apply the listener to the node
node.addEventListener(on, function(evt) {
if (node.preloadState === "PAUSE") { // Only add one event listener
node.preloadState = "READY"; // Required for the `load` function to trigger
// Special handling for "mouseover" events. Wait 100ms before triggering load.
if (on === "mouseover") {
window.setTimeout(load(node), 100);
} else {
load(node)() // all other events trigger immediately.
}
}
})
// Special handling for certain built-in event handlers
switch (on) {
case "mouseover":
// Mirror `touchstart` events (fires immediately)
node.addEventListener("touchstart", load(node));
// WHhen the mouse leaves, immediately disable the preload
node.addEventListener("mouseout", function(evt) {
if ((evt.target === node) && (node.preloadState === "READY")) {
node.preloadState = "PAUSE";
}
})
break;
case "mousedown":
// Mirror `touchstart` events (fires immediately)
node.addEventListener("touchstart", load(node));
break;
}
// Mark the node as ready to run.
node.preloadState = "PAUSE";
node.preloadAlways = always;
htmx.trigger(node, "preload:init") // This event can be used to load content immediately.
}
// Search for all child nodes that have a "preload" attribute
event.target.querySelectorAll("[preload]").forEach(function(node) {
// Initialize the node with the "preload" attribute
init(node)
// Initialize all child elements that are anchors or have `hx-get` (use with care)
node.querySelectorAll("a,[hx-get],[data-hx-get]").forEach(init)
})
}
})

3922
public/script/lib/htmx.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
import "./header.js";
import "./accessibility.js";
import "./config.js";
function type_out(e) {
const text = e.innerText;
@ -34,46 +34,50 @@ function type_out(e) {
}
function fill_list(list) {
const items = list.querySelectorAll("li a, li span");
items.innerText = "";
const delay = 100;
const items = list.querySelectorAll("li a, li span");
items.innerText = "";
const delay = 100;
items.forEach((item, iter) => {
item.style.animationDelay = `${iter * delay}ms`;
item.style.animationPlayState = "playing";
});
items.forEach((item, iter) => {
item.style.animationDelay = `${iter * delay}ms`;
item.style.animationPlayState = "playing";
});
}
[...document.querySelectorAll("h1, h2, h3, h4, h5, h6")]
.filter((e) => e.innerText != "")
.forEach((e) => {
type_out(e);
});
[...document.querySelectorAll("ol, ul")]
.filter((e) => e.innerText != "")
.forEach((e) => {
fill_list(e);
});
function start() {
[...document.querySelectorAll("h1, h2, h3, h4, h5, h6")]
.filter((e) => e.innerText != "")
.forEach((e) => {
type_out(e);
});
[...document.querySelectorAll("ol, ul")]
.filter((e) => e.innerText != "")
.forEach((e) => {
fill_list(e);
});
document.addEventListener("htmx:afterSwap", async event => {
const res = await event.detail.xhr.response;
var new_head = res.substring(res.indexOf("<head>")+1, res.indexOf("</head>"));
if (new_head) {
document.head.innerHTML = new_head;
}
window.scrollY = 0;
});
document.addEventListener("htmx:afterSwap", async event => {
const res = await event.detail.xhr.response;
var new_head = res.substring(res.indexOf("<head>")+1, res.indexOf("</head>"));
if (new_head) {
document.head.innerHTML = new_head;
}
window.scrollY = 0;
});
const top_button = document.getElementById("backtotop");
window.onscroll = () => {
if (!top_button) return;
const btt_threshold = 100;
if (
document.body.scrollTop > btt_threshold ||
const top_button = document.getElementById("backtotop");
window.onscroll = () => {
if (!top_button) return;
const btt_threshold = 100;
if (
document.body.scrollTop > btt_threshold ||
document.documentElement.scrollTop > btt_threshold
) {
top_button.classList.add("active");
} else {
top_button.classList.remove("active");
) {
top_button.classList.add("active");
} else {
top_button.classList.remove("active");
}
}
}
start();

View file

@ -1,44 +1,24 @@
import "./main.js";
const bg = document.getElementById("background");
bg.style.backgroundImage = `url(${bg.dataset.url})`;
bg.removeAttribute("data-url");
const share_btn = document.getElementById("share");
share_btn.onclick = (e) => {
navigator.clipboard.writeText(window.location.href);
share_btn.classList.remove('active');
void share_btn.offsetWidth;
share_btn.classList.add('active');
}
const go_back_btn = document.getElementById("go-back")
go_back_btn.innerText = "<";
go_back_btn.addEventListener("click", () => {
window.history.back();
});
apply_funny_bob_to_upcoming_tags();
function apply_funny_bob_to_upcoming_tags() {
const upcomingTags = document.querySelectorAll("#type.upcoming");
for (var i = 0; i < upcomingTags.length; i++) {
const tag = upcomingTags[i];
const upcoming_tags = document.querySelectorAll("#type.upcoming");
for (var i = 0; i < upcoming_tags.length; i++) {
const tag = upcoming_tags[i];
const chars = tag.innerText.split("");
const result = chars.map((c, i) => `<span style="animation-delay: -${i * 100}ms;">${c}</span>`);
tag.innerHTML = result.join("");
}
}
const extras_pairs = Array.from(document.querySelectorAll("div#info > div").values()).map(container => {
return {
container,
button: document.getElementById("extras").querySelector(`ul li a[href="#${container.id}"]`)
};
});
const info_container = document.getElementById("info")
info_container.addEventListener("scroll", update_extras_buttons);
function update_extras_buttons() {
const extras_pairs = Array.from(document.querySelectorAll("div#info > div").values()).map(container => {
return {
container,
button: document.getElementById("extras").querySelector(`ul li a[href="#${container.id}"]`)
};
});
const info_container = document.getElementById("info")
info_container.addEventListener("scroll", update_extras_buttons);
const info_rect = info_container.getBoundingClientRect();
const info_y = info_rect.y;
const font_size = parseFloat(getComputedStyle(document.documentElement).fontSize);
@ -52,57 +32,38 @@ function update_extras_buttons() {
if (scroll_diff <= 0) current = pair;
})
current.button.classList.add("active");
}
update_extras_buttons();
document.querySelectorAll("div#extras ul li a[href]").forEach(link => {
link.addEventListener("click", event => {
event.preventDefault();
location.replace(link.href);
document.querySelectorAll("div#extras ul li a[href]").forEach(link => {
link.addEventListener("click", event => {
event.preventDefault();
location.replace(link.href);
});
});
});
}
/*
* handling track previews (currently not implemented)
const previews = document.querySelectorAll("[id^=preview-]");
for (const preview of previews) {
preview.addEventListener("click", (e) => {
if (e.target.classList.contains('playing')) {
stopPreview(e.target);
} else {
playPreview(e.target);
}
function bind_go_back_btn() {
const go_back_btn = document.getElementById("go-back")
go_back_btn.innerText = "<";
go_back_btn.addEventListener("click", () => {
window.history.back();
});
preview.querySelector('audio').addEventListener("ended", () => { stopPreview(preview); });
}
var stupidsounds = false;
function stopPreviews() {
for (const preview of previews) stopPreview(preview);
function bind_share_btn() {
const share_btn = document.getElementById("share");
share_btn.onclick = (e) => {
navigator.clipboard.writeText(window.location.href);
share_btn.classList.remove('active');
void share_btn.offsetWidth;
share_btn.classList.add('active');
}
}
function playPreview(preview) {
if (!stupidsounds) stopPreviews();
const btn = preview.querySelector('i');
btn.classList.remove("play", "fa-play");
btn.classList.add("pause", "fa-pause");
const audio = preview.querySelector('audio');
audio.play();
preview.classList.add('playing');
function start() {
bind_share_btn();
bind_go_back_btn();
apply_funny_bob_to_upcoming_tags();
update_extras_buttons();
}
function stopPreview(preview) {
const btn = preview.querySelector('i');
btn.classList.remove("pause", "fa-pause");
btn.classList.add("play", "fa-play");
const audio = preview.querySelector('audio');
audio.pause();
audio.currentTime = 0;
preview.classList.remove('playing');
}
stopPreviews();
*/
start();

53
public/script/swap.js Normal file
View file

@ -0,0 +1,53 @@
async function swap(url) {
const res = await fetch(url);
if (res.status !== 200) return;
if (!res.headers.get("content-type").startsWith("text/html")) return;
const text = await res.text();
const content = new DOMParser().parseFromString(text, "text/html");
const stylesheets = [...content.querySelectorAll("link[rel='stylesheet']")];
// swap title
document.title = content.title;
// swap body html
document.body.innerHTML = content.body.innerHTML;
// swap head html
document.head.innerHTML = content.head.innerHTML;
// swap stylesheets
// const old_sheets = document.head.querySelectorAll("link[rel='stylesheet']");
// stylesheets.forEach(stylesheet => { document.head.appendChild(stylesheet) });
// old_sheets.forEach(stylesheet => { stylesheet.remove(); });
// push history
window.history.pushState({}, "", url);
bind(document.body);
}
function bind(content) {
if (typeof(content) !== 'object' || content.nodeType !== Node.ELEMENT_NODE) return;
content.querySelectorAll("a[href]").forEach(element => {
if (element.href.includes("#")) return;
if (!element.href.startsWith(window.location.origin)) return;
const href = element.href.substring(window.location.origin.length);
if (href.includes(".")) return;
element.addEventListener("click", event => {
event.preventDefault();
swap(element.href);
})
});
content.querySelectorAll("[swap-url]").forEach(element => {
const href = element.attributes.getNamedItem('swap-url').value;
element.addEventListener("click", event => {
event.preventDefault();
swap(href);
});
});
}
bind(document.body);