118 lines
4.2 KiB
JavaScript
118 lines
4.2 KiB
JavaScript
const swap_event = new Event("swap");
|
|
|
|
let caches = {};
|
|
|
|
async function cached_fetch(url) {
|
|
let cached = caches[url];
|
|
|
|
const res = cached === undefined ? await fetch(url) : await fetch(url, {
|
|
headers: {
|
|
"If-Modified-Since": cached.last_modified
|
|
}
|
|
});
|
|
|
|
if (res.status === 304 && cached !== undefined) {
|
|
return cached.content;
|
|
}
|
|
if (res.status !== 200) return;
|
|
if (!res.headers.get("content-type").startsWith("text/html")) return;
|
|
|
|
const text = await res.text();
|
|
if (res.headers.get("last-modified")) {
|
|
caches[url] = {
|
|
content: text,
|
|
last_modified: res.headers.get("last-modified")
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
async function swap(url, stateful) {
|
|
if (typeof url !== 'string') return;
|
|
|
|
const segments = window.location.href.split("/");
|
|
if (url.startsWith(window.location.origin) && segments[segments.length - 1].includes("#")) {
|
|
window.location.href = url;
|
|
return;
|
|
}
|
|
|
|
if (stateful && window.location.href.endsWith(url)) return;
|
|
|
|
const text = await cached_fetch(url);
|
|
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 stylesheets
|
|
const old_sheets = document.head.querySelectorAll("link[rel='stylesheet']");
|
|
stylesheets.forEach(stylesheet => {
|
|
let exists = false;
|
|
old_sheets.forEach(old_sheet => {
|
|
if (old_sheet.href === stylesheet.href) exists = true;
|
|
});
|
|
if (!exists) document.head.appendChild(stylesheet);
|
|
});
|
|
old_sheets.forEach(old_sheet => {
|
|
let exists = false;
|
|
stylesheets.forEach(stylesheet => {
|
|
if (stylesheet.href === old_sheet.href) exists = true;
|
|
});
|
|
if (!exists) old_sheet.remove();
|
|
});
|
|
// push history
|
|
if (stateful) history.pushState(url, "", url);
|
|
|
|
bind(document.body);
|
|
}
|
|
|
|
function bind(content) {
|
|
if (typeof content !== 'object' || content.nodeType !== Node.ELEMENT_NODE) return;
|
|
|
|
content.querySelectorAll("[swap-url]").forEach(element => {
|
|
const href = element.attributes.getNamedItem('swap-url').value;
|
|
|
|
element.addEventListener("click", event => {
|
|
event.preventDefault();
|
|
swap(href, true);
|
|
});
|
|
|
|
[...element.querySelectorAll("a[href], [swap-url]")].forEach(element => {
|
|
if (element.href) {
|
|
if (!element.href.endsWith(href)) return;
|
|
element.attributes.removeNamedItem("href");
|
|
}
|
|
const swap_url = element.attributes.getNamedItem("swap-url");
|
|
if (swap_url) {
|
|
if (!swap_url.endsWith(href)) return;
|
|
element.attributes.removeNamedItem("swap-url");
|
|
}
|
|
});
|
|
});
|
|
|
|
content.querySelectorAll("a[href]:not([swap-url])").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, true);
|
|
});
|
|
});
|
|
|
|
document.dispatchEvent(swap_event);
|
|
}
|
|
|
|
window.addEventListener("popstate", event => {
|
|
swap(event.state, false);
|
|
});
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
bind(document.body);
|
|
});
|