bethaus-app/static/page_router.js

136 lines
3.9 KiB
JavaScript

(() => {
const pageContent = document.getElementById('page-content');
if (!pageContent) return;
const isModifiedEvent = (event) =>
event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0;
const isSameOrigin = (url) => {
try {
const target = new URL(url, window.location.href);
return target.origin === window.location.origin;
} catch {
return false;
}
};
const cloneWithAttributes = (node) => {
const clone = document.createElement(node.tagName.toLowerCase());
Array.from(node.attributes).forEach(attr => {
clone.setAttribute(attr.name, attr.value);
});
if (node.tagName === 'SCRIPT' && node.textContent) {
clone.textContent = node.textContent;
}
if (node.tagName === 'STYLE' && node.textContent) {
clone.textContent = node.textContent;
}
return clone;
};
const replaceHeadExtras = async (doc) => {
const oldNodes = Array.from(document.head.querySelectorAll('[data-page-head]'));
oldNodes.forEach(node => node.remove());
const newNodes = Array.from(doc.head.querySelectorAll('[data-page-head]'));
const loadPromises = [];
newNodes.forEach(node => {
const clone = cloneWithAttributes(node);
if (clone.tagName === 'SCRIPT' && clone.src) {
loadPromises.push(new Promise(resolve => {
clone.onload = resolve;
clone.onerror = resolve;
}));
}
document.head.appendChild(clone);
});
if (loadPromises.length) {
await Promise.all(loadPromises);
}
};
const loadPage = async (url, { push = true, scroll = true } = {}) => {
try {
const response = await fetch(url, {
headers: { 'X-Requested-With': 'PageRouter' }
});
if (!response.ok) {
window.location.href = url;
return;
}
const html = await response.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const nextContent = doc.getElementById('page-content');
if (!nextContent) {
window.location.href = url;
return;
}
const nextPageId = nextContent.dataset.pageId || '';
const currentId = window.PageRegistry?.getCurrent?.() || pageContent.dataset.pageId || '';
if (window.PageRegistry?.destroyCurrent) {
await window.PageRegistry.destroyCurrent({ from: currentId, to: nextPageId, url });
}
document.title = doc.title;
await replaceHeadExtras(doc);
pageContent.innerHTML = nextContent.innerHTML;
pageContent.dataset.pageId = nextPageId;
if (push) {
history.pushState({ url }, '', url);
}
if (scroll) {
window.scrollTo(0, 0);
}
if (window.PageRegistry?.initPage) {
await window.PageRegistry.initPage(nextPageId, { url, doc });
}
} catch (err) {
window.location.href = url;
}
};
document.addEventListener('click', (event) => {
const link = event.target.closest('a[data-ajax-nav]');
if (!link) return;
if (isModifiedEvent(event)) return;
if (link.target && link.target !== '_self') return;
if (link.hasAttribute('download')) return;
const href = link.getAttribute('href');
if (!href || href.startsWith('#')) return;
if (!isSameOrigin(href)) return;
event.preventDefault();
loadPage(href, { push: true, scroll: true });
});
window.addEventListener('popstate', (event) => {
const url = (event.state && event.state.url) || window.location.href;
loadPage(url, { push: false, scroll: false });
});
const boot = () => {
const initialId = pageContent.dataset.pageId || '';
if (window.PageRegistry?.initPage) {
window.PageRegistry.initPage(initialId, { url: window.location.href, doc: document });
}
};
window.PageRouter = { loadPage };
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();