134 lines
3.9 KiB
JavaScript
134 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 });
|
|
}
|
|
};
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', boot);
|
|
} else {
|
|
boot();
|
|
}
|
|
})();
|