(() => { 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(); } })();