/** @typedef {{ path: string; title: string; render: (root: HTMLElement) => void | (() => void) }} Route */ /** @type {Route[]} */ const routes = []; /** @type {(() => void) | null} */ let cleanup = null; /** @param {Route} route */ export function registerRoute(route) { routes.push(route); } /** @param {string} hashPath */ export function navigate(hashPath) { const path = hashPath.startsWith('#') ? hashPath : `#${hashPath}`; if (location.hash !== path) { location.hash = path; } else { renderCurrent(); } } function currentPath() { const hash = location.hash.replace(/^#/, '') || '/publish'; return hash.startsWith('/') ? hash : `/${hash}`; } function findRoute() { const path = currentPath(); return routes.find((r) => r.path === path) ?? routes.find((r) => r.path === '/publish'); } function updateNav() { const path = currentPath(); document.querySelectorAll('.nav a').forEach((a) => { const href = a.getAttribute('href') ?? ''; a.classList.toggle('active', href === `#${path}`); }); } function renderCurrent() { const route = findRoute(); const root = document.getElementById('page-root'); if (!root || !route) return; if (cleanup) { cleanup(); cleanup = null; } root.innerHTML = ''; document.title = `${route.title} — TTRPG Player Publisher`; updateNav(); const result = route.render(root); if (typeof result === 'function') { cleanup = result; } } export function startRouter() { window.addEventListener('hashchange', renderCurrent); if (!location.hash) { location.hash = '#/publish'; } else { renderCurrent(); } } export function getCurrentPath() { return currentPath(); }