// Hash-based client-side router // Usage: navigate('#/templates') or window.location.hash = '#/templates' import { escHtml } from './utils.js'; const _routes = {}; let _current = null; // Register a route: router.register('#/templates', loadFn) export function register(hash, loadFn) { _routes[hash] = loadFn; } // Navigate to a hash route export function navigate(hash) { window.location.hash = hash; } // Navigate and pass data to the view (stored temporarily) let _routeData = null; export function navigateWith(hash, data) { _routeData = data; navigate(hash); } export function getRouteData() { const d = _routeData; _routeData = null; return d; } // Parse route: '#/templates/abc123' → { base: '#/templates', param: 'abc123' } function parseHash(hash) { const clean = hash || '#/templates'; const parts = clean.split('/'); if (parts.length >= 3) { return { base: parts.slice(0, 3).join('/'), param: parts[3] || null }; } return { base: clean, param: null }; } // Route to the current hash async function route() { const { base, param } = parseHash(window.location.hash); const key = param ? base : (window.location.hash || '#/templates'); const baseKey = base; const loader = _routes[key] || _routes[baseKey] || _routes['#/templates']; if (!loader) return; _current = key; updateActiveNav(baseKey); const outlet = document.getElementById('router-outlet'); if (outlet) outlet.classList.remove('view-enter'); try { await loader(param); } catch (err) { console.error('Router error:', err); const outlet = document.getElementById('router-outlet'); if (outlet) outlet.innerHTML = `
⚠️
Failed to load view
${escHtml(err.message)}
`; } if (outlet) { outlet.classList.add('view-enter'); // Remove class after animation to allow re-trigger setTimeout(() => outlet.classList.remove('view-enter'), 200); } } // Highlight active nav item function updateActiveNav(hash) { document.querySelectorAll('.nav-item').forEach(el => { el.classList.toggle('active', el.dataset.route === hash); }); // Update breadcrumb const label = document.querySelector(`.nav-item[data-route="${hash}"] .nav-label`); const breadcrumbCurrent = document.getElementById('breadcrumb-current'); if (breadcrumbCurrent && label) { breadcrumbCurrent.textContent = label.textContent.trim(); } } // Init: listen for hash changes and route on load export function init() { window.addEventListener('hashchange', route); // Route immediately if (!window.location.hash || window.location.hash === '#') { window.location.hash = '#/templates'; } else { route(); } } export function current() { return _current; }